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',
|
||||
];
|
||||
|
||||
protected $with = [
|
||||
'attachments',
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
|
||||
@ -10,32 +10,51 @@
|
||||
|
||||
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) {
|
||||
$existingAttachment = Attachment::where('attachmentable_id', $featureId)
|
||||
->where('attachmentable_type', $classModel)
|
||||
->first();
|
||||
if (is_string($file) && strpos($file, 'data:image') === 0) {
|
||||
$imageData = explode(',', $file)[1];
|
||||
$image = base64_decode($imageData);
|
||||
$extension = 'png';
|
||||
$fileName = Str::uuid();
|
||||
$fullFileName = $fileName.'.'.$extension;
|
||||
|
||||
if ($existingAttachment) {
|
||||
$oldFilePath = $path.'/'.$existingAttachment->created_at->format('Y-m-d').'/'.$existingAttachment->name.'.'.$existingAttachment->extension;
|
||||
Storage::disk('public')->delete($oldFilePath);
|
||||
$existingAttachment = Attachment::where('attachmentable_id', $featureId)
|
||||
->where('attachmentable_type', $classModel)
|
||||
->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%;
|
||||
margin: 0 auto;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
/* -webkit-box-orient: vertical; */
|
||||
/* overflow: hidden; */
|
||||
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 ($) {
|
||||
$('#datatable').DataTable({
|
||||
// scrollX: true,
|
||||
// scrollCollapse: true,
|
||||
stateSave: true,
|
||||
language: {
|
||||
emptyTable: "Tidak ada data yang tersedia pada tabel ini.",
|
||||
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.">
|
||||
<meta name="keywords" content="barbershop, potong rambut, cukur rambut, booking barbershop, layanan grooming">
|
||||
<meta name="author" content="Barbershop">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>{{ $pageTitle }} - Barbershop</title>
|
||||
|
||||
@ -71,7 +71,7 @@
|
||||
<li class="sidebar-list">
|
||||
<i class="fa fa-thumb-tack"></i>
|
||||
<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>
|
||||
</a>
|
||||
</li>
|
||||
@ -108,6 +108,18 @@
|
||||
<span>Kas</span>
|
||||
</a>
|
||||
</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>
|
||||
</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\ExpenseController;
|
||||
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\CustomerController;
|
||||
use App\Http\Controllers\Admin\Master\ServiceController;
|
||||
@ -88,4 +89,12 @@
|
||||
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