feat(attendance): crud,open cam js,datatable server side dan lainnya yang berhubungan dengan kehadiran
This commit is contained in:
parent
a6200884ce
commit
048131b2bb
101
app/Http/Controllers/Admin/Manage/AttendanceController.php
Normal file
101
app/Http/Controllers/Admin/Manage/AttendanceController.php
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin\Manage;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\Admin\Manage\AttendanceRequest;
|
||||||
|
use App\Models\Attendance;
|
||||||
|
use App\Traits\UploadAttachment;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Yajra\DataTables\Facades\DataTables;
|
||||||
|
|
||||||
|
class AttendanceController extends Controller
|
||||||
|
{
|
||||||
|
use UploadAttachment;
|
||||||
|
|
||||||
|
public function index(Request $request): View|JsonResponse
|
||||||
|
{
|
||||||
|
if ($request->ajax()) {
|
||||||
|
$attendances = Attendance::query();
|
||||||
|
|
||||||
|
return DataTables::eloquent($attendances)
|
||||||
|
->addIndexColumn()
|
||||||
|
->addColumn('working_time', function ($item) {
|
||||||
|
if ($item->check_in) {
|
||||||
|
$checkIn = Carbon::parse($item->check_in);
|
||||||
|
|
||||||
|
$checkOut = $item->check_out ? Carbon::parse($item->check_out) : Carbon::today()->endOfDay(); // Akhir hari: 23:59
|
||||||
|
|
||||||
|
$diff = $checkIn->diff($checkOut);
|
||||||
|
|
||||||
|
if ($diff->h > 0) {
|
||||||
|
return $diff->format('%h jam %i menit');
|
||||||
|
} else {
|
||||||
|
return $diff->format('%i menit');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return '-';
|
||||||
|
})
|
||||||
|
->addColumn('check_in_image', function ($item) {
|
||||||
|
if ($item->check_in) {
|
||||||
|
$imageUrl = Storage::url("attendances/{$item->attachments->first()->formatted_attachment}");
|
||||||
|
|
||||||
|
return '<a href="'.$imageUrl.'" ><img src="'.$imageUrl.'" alt="Check-in Image" height="77" width="77"></a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '-';
|
||||||
|
})
|
||||||
|
->addColumn('check_out_image', function ($item) {
|
||||||
|
if ($item->check_out) {
|
||||||
|
$imageUrl = Storage::url("attendances/{$item->attachments()->latest()->first()->formatted_attachment}");
|
||||||
|
|
||||||
|
return '<a href="'.$imageUrl.'" ><img src="'.$imageUrl.'" alt="Check-out Image" height="77" width="77"></a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '-';
|
||||||
|
})
|
||||||
|
->rawColumns(['check_in_image', 'check_out_image'])
|
||||||
|
->make();
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('pages.admin.manage.attendances', [
|
||||||
|
'pageTitle' => 'Kehadiran',
|
||||||
|
'checkInAttendance' => Attendance::where('user_id', Auth::id())->whereDate('created_at', today())->first(),
|
||||||
|
'checkOutAttendance' => Attendance::where('user_id', Auth::id())->whereDate('created_at', today())->whereNotNull('check_out')->first(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(AttendanceRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
$validatedData = $request->validated();
|
||||||
|
$checkInAttendance = Attendance::where('user_id', Auth::id())->whereDate('created_at', today())->first();
|
||||||
|
DB::transaction(function () use ($validatedData, $checkInAttendance) {
|
||||||
|
if (! $checkInAttendance) {
|
||||||
|
$newAttendance = Attendance::create([
|
||||||
|
'user_id' => Auth::id(),
|
||||||
|
'barbershop_id' => Auth::user()->barbershop_id,
|
||||||
|
'check_in' => now(),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$newAttendance = $checkInAttendance;
|
||||||
|
$newAttendance->update([
|
||||||
|
'check_out' => now(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->uploadAttachment($validatedData['image'], 'attendances', Attendance::class, $newAttendance->id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Image saved successfully!',
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/Http/Requests/Admin/Manage/AttendanceRequest.php
Normal file
33
app/Http/Requests/Admin/Manage/AttendanceRequest.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests\Admin\Manage;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
|
||||||
|
class AttendanceRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return Auth::check();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'image' => ['required', function ($attribute, $value, $fail) {
|
||||||
|
if (! preg_match('/^data:image\/(\w+);base64,/', $value)) {
|
||||||
|
$fail('The '.$attribute.' is not a valid image.');
|
||||||
|
} else {
|
||||||
|
$imageData = explode(',', $value)[1];
|
||||||
|
$image = base64_decode($imageData);
|
||||||
|
|
||||||
|
$imageInfo = @getimagesizefromstring($image);
|
||||||
|
if ($imageInfo === false) {
|
||||||
|
$fail('The '.$attribute.' is not a valid image.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,6 +19,10 @@ class Attendance extends Model
|
|||||||
'check_out',
|
'check_out',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $with = [
|
||||||
|
'attachments',
|
||||||
|
];
|
||||||
|
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
|
|||||||
@ -10,32 +10,51 @@
|
|||||||
|
|
||||||
trait UploadAttachment
|
trait UploadAttachment
|
||||||
{
|
{
|
||||||
public function uploadAttachment(UploadedFile $file, string $path, string $classModel, string $featureId): void
|
public function uploadAttachment(UploadedFile|string $file, string $path, string $classModel, string $featureId): void
|
||||||
{
|
{
|
||||||
DB::transaction(function () use ($file, $path, $classModel, $featureId) {
|
DB::transaction(function () use ($file, $path, $classModel, $featureId) {
|
||||||
$existingAttachment = Attachment::where('attachmentable_id', $featureId)
|
if (is_string($file) && strpos($file, 'data:image') === 0) {
|
||||||
->where('attachmentable_type', $classModel)
|
$imageData = explode(',', $file)[1];
|
||||||
->first();
|
$image = base64_decode($imageData);
|
||||||
|
$extension = 'png';
|
||||||
|
$fileName = Str::uuid();
|
||||||
|
$fullFileName = $fileName.'.'.$extension;
|
||||||
|
|
||||||
if ($existingAttachment) {
|
$existingAttachment = Attachment::where('attachmentable_id', $featureId)
|
||||||
$oldFilePath = $path.'/'.$existingAttachment->created_at->format('Y-m-d').'/'.$existingAttachment->name.'.'.$existingAttachment->extension;
|
->where('attachmentable_type', $classModel)
|
||||||
Storage::disk('public')->delete($oldFilePath);
|
->first();
|
||||||
|
|
||||||
$existingAttachment->delete();
|
if ($existingAttachment) {
|
||||||
|
if ($classModel !== 'App\\Models\\Attendance') {
|
||||||
|
$oldFilePath = $path.'/'.$existingAttachment->created_at->format('Y-m-d').'/'.$existingAttachment->name.'.'.$existingAttachment->extension;
|
||||||
|
Storage::disk('public')->delete($oldFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$date = now()->format('Y-m-d');
|
||||||
|
Storage::disk('public')->put($path.'/'.$date.'/'.$fullFileName, $image);
|
||||||
|
|
||||||
|
Attachment::create([
|
||||||
|
'attachmentable_id' => $featureId,
|
||||||
|
'attachmentable_type' => $classModel,
|
||||||
|
'name' => $fileName,
|
||||||
|
'extension' => $extension,
|
||||||
|
'size' => strlen($image),
|
||||||
|
]);
|
||||||
|
} elseif ($file instanceof UploadedFile) {
|
||||||
|
$date = now()->format('Y-m-d');
|
||||||
|
$extension = $file->getClientOriginalExtension();
|
||||||
|
$fileName = Str::uuid();
|
||||||
|
$file->storeAs($path.'/'.$date, $fileName.'.'.$extension, 'public');
|
||||||
|
|
||||||
|
Attachment::create([
|
||||||
|
'attachmentable_id' => $featureId,
|
||||||
|
'attachmentable_type' => $classModel,
|
||||||
|
'name' => $fileName,
|
||||||
|
'extension' => $extension,
|
||||||
|
'size' => $file->getSize(),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$date = now()->format('Y-m-d');
|
|
||||||
$extension = $file->getClientOriginalExtension();
|
|
||||||
$fileName = Str::uuid();
|
|
||||||
$file->storeAs($path.'/'.$date, $fileName.'.'.$extension, 'public');
|
|
||||||
|
|
||||||
Attachment::create([
|
|
||||||
'attachmentable_id' => $featureId,
|
|
||||||
'attachmentable_type' => $classModel,
|
|
||||||
'name' => $fileName,
|
|
||||||
'extension' => $extension,
|
|
||||||
'size' => $file->getSize(),
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10744,8 +10744,8 @@ @media (max-width: 1199px) {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
-webkit-line-clamp: 3;
|
-webkit-line-clamp: 3;
|
||||||
-webkit-box-orient: vertical;
|
/* -webkit-box-orient: vertical; */
|
||||||
overflow: hidden;
|
/* overflow: hidden; */
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
116
public/assets/admin/js/custom/attendance.js
Normal file
116
public/assets/admin/js/custom/attendance.js
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
let webcamStream;
|
||||||
|
let imageData;
|
||||||
|
|
||||||
|
$('#attendance-btn').on('click', async function () {
|
||||||
|
try {
|
||||||
|
webcamStream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: true
|
||||||
|
});
|
||||||
|
$('#webcam').prop('srcObject', webcamStream);
|
||||||
|
$('#webcam-container').removeClass('d-none');
|
||||||
|
$('#webcam').removeClass('d-none');
|
||||||
|
$('#btn-capture').removeClass('d-none');
|
||||||
|
$('#canvas-preview').addClass('d-none');
|
||||||
|
$('#action-buttons').addClass('d-none');
|
||||||
|
} catch (error) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Webcam Error',
|
||||||
|
text: 'Tidak dapat mengakses webcam Anda. Silakan periksa izin kamera Anda.',
|
||||||
|
confirmButtonText: 'Tutup'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn-capture').on('click', function () {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const webcam = $('#webcam')[0];
|
||||||
|
canvas.width = webcam.videoWidth;
|
||||||
|
canvas.height = webcam.videoHeight;
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
context.translate(canvas.width, 0);
|
||||||
|
context.scale(-1, 1);
|
||||||
|
context.drawImage(webcam, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
imageData = canvas.toDataURL('image/png');
|
||||||
|
|
||||||
|
const previewCanvas = $('#canvas-preview')[0];
|
||||||
|
const previewContext = previewCanvas.getContext('2d');
|
||||||
|
previewCanvas.width = canvas.width;
|
||||||
|
previewCanvas.height = canvas.height;
|
||||||
|
previewContext.drawImage(canvas, 0, 0);
|
||||||
|
|
||||||
|
$('#webcam').addClass('d-none');
|
||||||
|
$('#btn-capture').addClass('d-none');
|
||||||
|
|
||||||
|
$('#canvas-preview').removeClass('d-none');
|
||||||
|
$('#action-buttons').removeClass('d-none');
|
||||||
|
|
||||||
|
webcamStream.getTracks().forEach(track => track.stop());
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn-retry').on('click', async function () {
|
||||||
|
try {
|
||||||
|
webcamStream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: true
|
||||||
|
});
|
||||||
|
$('#webcam').prop('srcObject', webcamStream);
|
||||||
|
$('#webcam').removeClass('d-none');
|
||||||
|
$('#btn-capture').removeClass('d-none');
|
||||||
|
$('#canvas-preview').addClass('d-none');
|
||||||
|
$('#action-buttons').addClass('d-none');
|
||||||
|
} catch (error) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Webcam Error',
|
||||||
|
text: 'Gagal membuka kembali webcam. Silakan coba lagi.',
|
||||||
|
confirmButtonText: 'Tutup'
|
||||||
|
});
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#btn-save').on('click', function () {
|
||||||
|
if (!imageData) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'warning',
|
||||||
|
title: 'Tidak Ada Gambar yang Diambil',
|
||||||
|
text: 'Silakan ambil gambar sebelum disimpan.',
|
||||||
|
confirmButtonText: 'Tutup'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/dashboard/manage/attendance/store',
|
||||||
|
type: 'POST',
|
||||||
|
data: {
|
||||||
|
_token: $('meta[name="csrf-token"]').attr('content'),
|
||||||
|
image: imageData
|
||||||
|
},
|
||||||
|
success: function (response) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Gambar Disimpan',
|
||||||
|
text: 'Gambar Anda telah berhasil disimpan!',
|
||||||
|
confirmButtonText: 'OK'
|
||||||
|
}).then(() => {
|
||||||
|
$('#webcam-container').addClass('d-none');
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function (error) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal Menyimpan',
|
||||||
|
text: 'Ada masalah saat menyimpan gambar Anda. Silakan coba lagi.',
|
||||||
|
confirmButtonText: 'Tutup'
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,8 +1,5 @@
|
|||||||
(function ($) {
|
(function ($) {
|
||||||
$('#datatable').DataTable({
|
$('#datatable').DataTable({
|
||||||
// scrollX: true,
|
|
||||||
// scrollCollapse: true,
|
|
||||||
stateSave: true,
|
|
||||||
language: {
|
language: {
|
||||||
emptyTable: "Tidak ada data yang tersedia pada tabel ini.",
|
emptyTable: "Tidak ada data yang tersedia pada tabel ini.",
|
||||||
processing: "Sedang memproses ...",
|
processing: "Sedang memproses ...",
|
||||||
|
|||||||
12
public/assets/admin/js/datatable/custom-table/attendance.js
Normal file
12
public/assets/admin/js/datatable/custom-table/attendance.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
window.columns = [
|
||||||
|
{
|
||||||
|
data: 'DT_RowIndex',
|
||||||
|
orderable: false,
|
||||||
|
searchable: false
|
||||||
|
},
|
||||||
|
{ data: 'check_in' },
|
||||||
|
{ data: 'check_in_image' },
|
||||||
|
{ data: 'check_out' },
|
||||||
|
{ data: 'check_out_image' },
|
||||||
|
{ data: 'working_time' },
|
||||||
|
]
|
||||||
24
public/assets/admin/js/datatable/server-side.js
Normal file
24
public/assets/admin/js/datatable/server-side.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
(function ($) {
|
||||||
|
$('#datatable').DataTable({
|
||||||
|
serverSide: true,
|
||||||
|
processing: true,
|
||||||
|
ajax: $('#datatable').data('ajax'),
|
||||||
|
language: {
|
||||||
|
emptyTable: "Tidak ada data yang tersedia pada tabel ini.",
|
||||||
|
processing: "Sedang memproses ...",
|
||||||
|
search: "",
|
||||||
|
searchPlaceholder: "Ketik untuk mencari",
|
||||||
|
lengthMenu: `<span class="d-none d-sm-inline-block">Lihat</span><div class="form-control-select mt-2"> _MENU_ </div>`,
|
||||||
|
zeroRecords: "Tidak ditemukan data yang sesuai",
|
||||||
|
info: "_START_ - _END_ dari _TOTAL_",
|
||||||
|
infoEmpty: "",
|
||||||
|
infoFiltered: "(total _MAX_)",
|
||||||
|
},
|
||||||
|
columns: columns,
|
||||||
|
pageLength: 10,
|
||||||
|
lengthMenu: [
|
||||||
|
[10, 25, 50, 100],
|
||||||
|
[10, 25, 50, 100]
|
||||||
|
],
|
||||||
|
});
|
||||||
|
})(jQuery);
|
||||||
146
resources/views/pages/admin/manage/attendances.blade.php
Normal file
146
resources/views/pages/admin/manage/attendances.blade.php
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
@extends('layouts.admin')
|
||||||
|
@section('styles')
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ asset('assets/admin/css/vendors/datatables.css') }}">
|
||||||
|
<style>
|
||||||
|
#webcam-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#webcam {
|
||||||
|
transform: scaleX(-1);
|
||||||
|
=-webkit-transform: scaleX(-1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endsection
|
||||||
|
@section('app')
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row d-flex justify-content-center">
|
||||||
|
<div class="col-sm-12 col-xl-6 d-none mb-3" id="webcam-container">
|
||||||
|
<video id="webcam" autoplay playsinline style="width: 100%;"></video>
|
||||||
|
<button id="btn-capture" class="btn btn-primary mt-3">Ambil</button>
|
||||||
|
|
||||||
|
<canvas id="canvas-preview" class="d-none" style="width: 100%; border: 1px solid #ccc;"></canvas>
|
||||||
|
|
||||||
|
<div id="action-buttons" class="d-none mt-3">
|
||||||
|
<button id="btn-retry" class="btn btn-warning">Ulangi</button>
|
||||||
|
<button id="btn-save" class="btn btn-success">Simpan</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 col-xl-6">
|
||||||
|
<div class="card card-absolute">
|
||||||
|
@if (!$checkInAttendance)
|
||||||
|
<div class="card-header bg-primary">
|
||||||
|
<a href="javascript:void(0)" id="attendance-btn">
|
||||||
|
<h5 class="txt-light">Hadir</h5>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex list-behavior-1 align-items-center">
|
||||||
|
@if ($checkInAttendance)
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<a
|
||||||
|
href="{{ Storage::url("attendances/{$checkInAttendance->attachments->first()->formatted_attachment}") }}">
|
||||||
|
<img class="tab-img img-fluid"
|
||||||
|
src="{{ Storage::url("attendances/{$checkInAttendance->attachments->first()->formatted_attachment}") }}"
|
||||||
|
alt="image">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if (!$checkInAttendance)
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<p class="mb-xl-0 mb-sm-4">
|
||||||
|
Selamat pagi, semoga hari Anda penuh semangat! Jangan lupa untuk melakukan absensi
|
||||||
|
kehadiran masuk sebelum memulai aktivitas Anda hari ini.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<p class="mb-xl-0 mb-sm-4">
|
||||||
|
Terima kasih, Anda telah melakukan absensi kehadiran masuk pada pukul
|
||||||
|
{{ $checkInAttendance->check_in }}.Semoga hari Anda produktif
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if ($checkInAttendance)
|
||||||
|
<div class="col-sm-12 col-xl-6">
|
||||||
|
<div class="card card-absolute">
|
||||||
|
@if (!$checkOutAttendance)
|
||||||
|
<div class="card-header bg-primary">
|
||||||
|
<a href="javascript:void(0)" id="attendance-btn">
|
||||||
|
<h5 class="txt-light">Hadir</h5>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex list-behavior-1 align-items-center">
|
||||||
|
@if ($checkOutAttendance)
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<a
|
||||||
|
href="{{ Storage::url("attendances/{$checkOutAttendance->attachments()->latest()->first()->formatted_attachment}") }}">
|
||||||
|
<img class="tab-img img-fluid"
|
||||||
|
src="{{ Storage::url("attendances/{$checkOutAttendance->attachments()->latest()->first()->formatted_attachment}") }}"
|
||||||
|
alt="image">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if (!$checkOutAttendance)
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<p class="mb-xl-0 mb-sm-4">
|
||||||
|
Jangan lupa untuk melakukan absensi kehadiran pulang sebelum meninggalkan
|
||||||
|
tempat kerja.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
<p class="mb-xl-0 mb-sm-4">
|
||||||
|
Terima kasih, Anda telah melakukan absensi kehadiran pulang pada pukul
|
||||||
|
{{ $checkOutAttendance->check_out }}.Semoga istirahat Anda menyegarkan untuk
|
||||||
|
hari berikutnya.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="display" id="datatable" data-ajax="{{ url(url()->current()) }}">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>No</th>
|
||||||
|
<th>Masuk</th>
|
||||||
|
<th>Foto</th>
|
||||||
|
<th>Pulang</th>
|
||||||
|
<th>Foto</th>
|
||||||
|
<th>Total Kerja</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
@section('afterScripts')
|
||||||
|
<script src="{{ asset('assets/admin/js/datatable/jquery.dataTables.min.js?ver=1.0.0') }}"></script>
|
||||||
|
<script src="{{ asset('assets/admin/js/datatable/custom-table/attendance.js?ver=1.0.0') }}"></script>
|
||||||
|
<script src="{{ asset('assets/admin/js/datatable/server-side.js?ver=1.0.0') }}"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
|
<script src="{{ asset('assets/admin/js/custom/attendance.js?ver=1.0.0') }}"></script>
|
||||||
|
@endsection
|
||||||
@ -6,4 +6,5 @@
|
|||||||
content="Nikmati pengalaman cukur rambut terbaik dengan aplikasi barbershop kami. Pesan layanan cukur, potong rambut, dan grooming dengan mudah dan cepat.">
|
content="Nikmati pengalaman cukur rambut terbaik dengan aplikasi barbershop kami. Pesan layanan cukur, potong rambut, dan grooming dengan mudah dan cepat.">
|
||||||
<meta name="keywords" content="barbershop, potong rambut, cukur rambut, booking barbershop, layanan grooming">
|
<meta name="keywords" content="barbershop, potong rambut, cukur rambut, booking barbershop, layanan grooming">
|
||||||
<meta name="author" content="Barbershop">
|
<meta name="author" content="Barbershop">
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
<title>{{ $pageTitle }} - Barbershop</title>
|
<title>{{ $pageTitle }} - Barbershop</title>
|
||||||
|
|||||||
@ -71,7 +71,7 @@
|
|||||||
<li class="sidebar-list">
|
<li class="sidebar-list">
|
||||||
<i class="fa fa-thumb-tack"></i>
|
<i class="fa fa-thumb-tack"></i>
|
||||||
<a class="sidebar-link sidebar-title link-nav" href="{{ route('master.customers') }}">
|
<a class="sidebar-link sidebar-title link-nav" href="{{ route('master.customers') }}">
|
||||||
<i data-feather="user-check"></i>
|
<i data-feather="user-plus"></i>
|
||||||
<span>Pelanggan</span>
|
<span>Pelanggan</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@ -108,6 +108,18 @@
|
|||||||
<span>Kas</span>
|
<span>Kas</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="sidebar-main-title">
|
||||||
|
<div>
|
||||||
|
<h6>Kelola</h6>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="sidebar-list">
|
||||||
|
<i class="fa fa-thumb-tack"></i>
|
||||||
|
<a class="sidebar-link sidebar-title link-nav" href="{{ route('manage.attendances') }}">
|
||||||
|
<i data-feather="user-check"></i>
|
||||||
|
<span>Kehadiran</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-arrow" id="right-arrow"><i data-feather="arrow-right"></i></div>
|
<div class="right-arrow" id="right-arrow"><i data-feather="arrow-right"></i></div>
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
use App\Http\Controllers\Admin\Finance\CashController;
|
use App\Http\Controllers\Admin\Finance\CashController;
|
||||||
use App\Http\Controllers\Admin\Finance\ExpenseController;
|
use App\Http\Controllers\Admin\Finance\ExpenseController;
|
||||||
use App\Http\Controllers\Admin\Finance\PayrollController;
|
use App\Http\Controllers\Admin\Finance\PayrollController;
|
||||||
|
use App\Http\Controllers\Admin\Manage\AttendanceController;
|
||||||
use App\Http\Controllers\Admin\Master\BarbershopController;
|
use App\Http\Controllers\Admin\Master\BarbershopController;
|
||||||
use App\Http\Controllers\Admin\Master\CustomerController;
|
use App\Http\Controllers\Admin\Master\CustomerController;
|
||||||
use App\Http\Controllers\Admin\Master\ServiceController;
|
use App\Http\Controllers\Admin\Master\ServiceController;
|
||||||
@ -88,4 +89,12 @@
|
|||||||
Route::delete('delete/{cash}', [CashController::class, 'delete'])->name('finance.cash.delete');
|
Route::delete('delete/{cash}', [CashController::class, 'delete'])->name('finance.cash.delete');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::prefix('manage')->group(function () {
|
||||||
|
Route::get('/attendances', [AttendanceController::class, 'index'])->name('manage.attendances');
|
||||||
|
Route::prefix('attendance')->group(function () {
|
||||||
|
Route::post('store', [AttendanceController::class, 'store'])->name('manage.attendance.store');
|
||||||
|
Route::delete('delete/{attendance}', [AttendanceController::class, 'delete'])->name('manage.attendance.delete');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user