last
This commit is contained in:
parent
0c08fefabe
commit
43429ea33c
|
|
@ -33,3 +33,22 @@ _ide_helper_models.php
|
||||||
# Research notebooks & scripts
|
# Research notebooks & scripts
|
||||||
*.ipynb
|
*.ipynb
|
||||||
/notebook_face_recognition.py
|
/notebook_face_recognition.py
|
||||||
|
|
||||||
|
# Dokumen laporan TA (bukan bagian kode)
|
||||||
|
/laporan_revisi_text.txt
|
||||||
|
/proposal_text.txt
|
||||||
|
/proposal_images/
|
||||||
|
|
||||||
|
# Script utility sementara
|
||||||
|
/extract_images.py
|
||||||
|
/map_images.py
|
||||||
|
/read_docx.py
|
||||||
|
|
||||||
|
# Folder hosting upload (tidak perlu di repo)
|
||||||
|
/_hosting_upload/
|
||||||
|
|
||||||
|
# Storage zip backup
|
||||||
|
/storage/app/laravel_storage_public.zip
|
||||||
|
|
||||||
|
# Direktori aneh Windows (symlink error)
|
||||||
|
/public/%SystemDrive%/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,246 @@
|
||||||
|
# DESIGN.md — HRIS MP (Menjadi Pengaruh Group)
|
||||||
|
|
||||||
|
> HR Information System — Web Admin Dashboard & Employee Mobile App
|
||||||
|
|
||||||
|
## ⚠️ CRITICAL: This is a LIGHT THEME application. Do NOT use dark mode. All page backgrounds are light gray (#F8FAFC). All cards and content areas are WHITE (#FFFFFF). The sidebar is WHITE with a light gray right border. The dark navy color (#130F26) is ONLY used for: active sidebar menu items, primary action buttons, and the avatar circle. Everything else should be light, clean, and white.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Brand Identity
|
||||||
|
|
||||||
|
- **Product Name**: HRIS MP
|
||||||
|
- **Company**: Menjadi Pengaruh Group
|
||||||
|
- **Design Mood**: Clean, bright, minimal, professional, corporate. Light and airy with plenty of white space.
|
||||||
|
- **Overall Feel**: Like a premium SaaS admin dashboard. Think Linear, Notion, or Vercel Dashboard — clean white interfaces with subtle shadows.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typography
|
||||||
|
|
||||||
|
- **Font**: Inter (Google Fonts), weights 300–700
|
||||||
|
- **Page Title**: 24px, Bold, color #1e293b (dark slate)
|
||||||
|
- **Section Title**: 18px, Bold, color #1e293b
|
||||||
|
- **Card Label**: 14px, Semibold, UPPERCASE, color #64748b (gray)
|
||||||
|
- **Body Text**: 14px, Regular, color #1e293b
|
||||||
|
- **Secondary Text**: 14px, Regular, color #64748b (medium gray)
|
||||||
|
- **Caption/Small**: 12px, color #94a3b8 (light gray)
|
||||||
|
- **Badge Text**: 10px, Bold
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color Palette
|
||||||
|
|
||||||
|
### IMPORTANT: Light Theme Colors
|
||||||
|
|
||||||
|
**Page background**: #F8FAFC (very light gray, almost white)
|
||||||
|
**Card/Content background**: #FFFFFF (pure white)
|
||||||
|
**Sidebar background**: #FFFFFF (pure white) with right border #e2e8f0
|
||||||
|
|
||||||
|
### Accent Color (used sparingly!)
|
||||||
|
- **Primary accent**: #130F26 (very dark navy) — ONLY for active menu items, primary buttons, and avatar
|
||||||
|
- **Primary gradient**: from #130F26 to #2A244A — ONLY for primary action buttons
|
||||||
|
- **Primary hover**: #1E1B3A
|
||||||
|
|
||||||
|
### Text Colors
|
||||||
|
- **Heading text**: #1e293b (dark slate) on white background
|
||||||
|
- **Body text**: #334155 (medium dark) on white background
|
||||||
|
- **Secondary text**: #64748b (medium gray) on white background
|
||||||
|
- **Placeholder text**: #94a3b8 (light gray)
|
||||||
|
- **Disabled text**: #94a3b8
|
||||||
|
|
||||||
|
### Border & Divider Colors
|
||||||
|
- **Card borders**: #f1f5f9 (very subtle, barely visible)
|
||||||
|
- **Input borders**: #e2e8f0 (light gray)
|
||||||
|
- **Table dividers**: #f1f5f9 (very light)
|
||||||
|
- **Sidebar section dividers**: #e2e8f0
|
||||||
|
|
||||||
|
### Semantic Status Colors (for badges and indicators only)
|
||||||
|
- **Success/Approved**: bg #dcfce7, text #16a34a (green)
|
||||||
|
- **Pending/Warning**: bg #fff7ed, text #f97316 (orange)
|
||||||
|
- **Rejected/Error**: bg #fef2f2, text #dc2626 (red)
|
||||||
|
- **Info**: bg #eff6ff, text #2563eb (blue)
|
||||||
|
|
||||||
|
### Action Button Colors
|
||||||
|
- **Edit button**: bg #fffbeb (light amber), text #d97706 (amber)
|
||||||
|
- **Delete button**: bg #fef2f2 (light red), text #dc2626 (red)
|
||||||
|
- **Primary button**: bg gradient #130F26→#2A244A, text white
|
||||||
|
|
||||||
|
### Stat Card Icon Backgrounds (pastel, light colors)
|
||||||
|
- Indigo: bg #eef2ff, icon #4f46e5
|
||||||
|
- Purple: bg #faf5ff, icon #9333ea
|
||||||
|
- Blue: bg #eff6ff, icon #2563eb
|
||||||
|
- Emerald: bg #ecfdf5, icon #059669
|
||||||
|
- Orange: bg #fff7ed, icon #ea580c
|
||||||
|
- Red: bg #fef2f2, icon #dc2626
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Shadows (subtle, not heavy)
|
||||||
|
|
||||||
|
- **Card default**: 0 2px 12px -2px rgba(0,0,0,0.04) — very subtle
|
||||||
|
- **Card hover**: 0 4px 20px -2px rgba(0,0,0,0.05) — slightly more visible
|
||||||
|
- **Button hover glow**: 0 0 15px rgba(19,15,38,0.15) — subtle navy glow
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Layout Structure
|
||||||
|
|
||||||
|
### Overall Page Layout
|
||||||
|
- Left: Fixed sidebar (288px wide, WHITE background, light gray right border)
|
||||||
|
- Right: Scrollable content area (light gray #F8FAFC background)
|
||||||
|
- Top of content: Sticky header bar (white with frosted glass effect)
|
||||||
|
|
||||||
|
### Sidebar (LEFT SIDE)
|
||||||
|
- Background: WHITE (#FFFFFF)
|
||||||
|
- Width: 288px
|
||||||
|
- Right border: 1px solid #e2e8f0 (light gray line)
|
||||||
|
- Top: Company logo centered (height ~112px area)
|
||||||
|
- Menu items:
|
||||||
|
- **Active item**: bg #130F26 (dark navy), text white, rounded 12px, subtle shadow
|
||||||
|
- **Inactive item**: text #64748b (gray), hover: bg #f8fafc, text #0f172a
|
||||||
|
- Icon size: 20x20px, on the left of text
|
||||||
|
- Text: 14px semibold
|
||||||
|
- Section labels: 10px, bold, uppercase, gray (#94a3b8), with horizontal lines on both sides
|
||||||
|
- Sections: "Manajemen", "Absensi", "Data Master" (collapsible with chevron)
|
||||||
|
- Bottom: "Pengaturan Akun" link + red "Logout" button
|
||||||
|
|
||||||
|
### Top Header Bar
|
||||||
|
- Background: white with 70% opacity + backdrop blur (frosted glass)
|
||||||
|
- Sticky at top
|
||||||
|
- Bottom border: 1px solid rgba(226,232,240,0.6)
|
||||||
|
- Very subtle shadow
|
||||||
|
- Left: Page title (24px, bold, #1e293b)
|
||||||
|
- Right: Notification bell icon → User name + email text → Circular avatar
|
||||||
|
- Avatar: 44px circle, gradient #130F26→#2B2545, white letter inside, white ring around it
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### Cards
|
||||||
|
- Background: WHITE
|
||||||
|
- Border: 1px solid #f1f5f9 (barely visible)
|
||||||
|
- Border radius: 16px to 24px
|
||||||
|
- Shadow: very subtle (see shadows section)
|
||||||
|
- Padding: 24px inside
|
||||||
|
- On hover: slight lift (-1px up) with slightly stronger shadow
|
||||||
|
|
||||||
|
### Stat Cards (Dashboard)
|
||||||
|
- Background: WHITE
|
||||||
|
- Large number: 30-36px, extra bold, color #1e293b
|
||||||
|
- Label: 14px, semibold, uppercase, color #64748b
|
||||||
|
- Icon: in a pastel-colored rounded square (16px radius, 12px padding)
|
||||||
|
- Subtle decorative gradient orb in background at very low opacity (3%)
|
||||||
|
|
||||||
|
### Data Tables
|
||||||
|
- Container: WHITE background, 16px border radius, ring-1 #f1f5f9
|
||||||
|
- Header row: bg #f8fafc (almost white), text #64748b, 11px, bold, uppercase
|
||||||
|
- Body rows: WHITE background, divided by 1px #f1f5f9 lines
|
||||||
|
- Row hover: slight lift with shadow
|
||||||
|
- Cell padding: 16px vertical
|
||||||
|
|
||||||
|
### Input Fields
|
||||||
|
- Background: #f8fafc (very light gray), turns WHITE on focus
|
||||||
|
- Border: 1px solid #e2e8f0
|
||||||
|
- Border radius: 12px
|
||||||
|
- Height: 44px
|
||||||
|
- Focus state: 4px ring in rgba(19,15,38,0.1), border turns #130F26
|
||||||
|
- Label above: 14px, semibold, #334155
|
||||||
|
|
||||||
|
### Buttons
|
||||||
|
- **Primary**: gradient #130F26→#2A244A, text white, 12px radius, hover lifts up
|
||||||
|
- **Secondary**: WHITE bg, border #e2e8f0, text #334155
|
||||||
|
- **Danger**: gradient red-600→red-500, text white
|
||||||
|
- All buttons: 12px radius, padding 10px 24px
|
||||||
|
|
||||||
|
### Modals
|
||||||
|
- Overlay: #0f172a at 40% opacity + backdrop blur
|
||||||
|
- Modal box: WHITE bg, 16px radius, heavy shadow
|
||||||
|
- Header: frosted glass (white 80% + blur), title bold, close X button
|
||||||
|
- Content area: white, 24px padding
|
||||||
|
|
||||||
|
### Badges/Status Pills
|
||||||
|
- Small rounded-full pills
|
||||||
|
- Approved: light green bg #dcfce7, green text #16a34a
|
||||||
|
- Pending: light orange bg #fff7ed, orange text #f97316
|
||||||
|
- Rejected: light red bg #fef2f2, red text #dc2626
|
||||||
|
- Size: 10px bold text, padding 2px 8px
|
||||||
|
|
||||||
|
### Search Input
|
||||||
|
- Same as input field but with magnifying glass icon on the left
|
||||||
|
|
||||||
|
### Pagination
|
||||||
|
- Active page: bg #130F26, text white, rounded
|
||||||
|
- Inactive: WHITE bg, border, text #475569
|
||||||
|
|
||||||
|
### Notification Dropdown
|
||||||
|
- Bell icon (24px), with red count badge
|
||||||
|
- Dropdown: WHITE bg, 384px wide, 16px radius, heavy shadow
|
||||||
|
- Each item: icon + title + message + time
|
||||||
|
|
||||||
|
### SweetAlert Dialogs
|
||||||
|
- Rounded 20px, WHITE bg, centered
|
||||||
|
- Confirm button: #130F26 bg, full width
|
||||||
|
- Cancel button: #f1f5f9 bg, gray text
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Page Types
|
||||||
|
|
||||||
|
### 1. Login Page
|
||||||
|
- Split screen: Left half = full bleed image with primary color overlay, Right half = WHITE form area
|
||||||
|
- Form: centered, max 384px wide
|
||||||
|
- Title "Sign in": 30px, extra bold, #130F26
|
||||||
|
- Inputs with labels
|
||||||
|
- Full-width primary button
|
||||||
|
- Footer: copyright text
|
||||||
|
|
||||||
|
### 2. Dashboard
|
||||||
|
- Stat cards in 4-column grid (responsive)
|
||||||
|
- Charts below
|
||||||
|
- WHITE cards on light gray page background
|
||||||
|
|
||||||
|
### 3. Data Table Pages
|
||||||
|
- Page header: title + subtitle + action buttons
|
||||||
|
- Filter bar: dropdowns + search input
|
||||||
|
- WHITE table card
|
||||||
|
- Pagination
|
||||||
|
|
||||||
|
### 4. Form Pages (Create/Edit)
|
||||||
|
- Back button
|
||||||
|
- WHITE form cards with sections
|
||||||
|
- 2-column grid layout for form fields
|
||||||
|
- Save button
|
||||||
|
|
||||||
|
### 5. Modal CRUD Pages (Divisi, Jabatan, Shift, Role, etc.)
|
||||||
|
- Table page with "+ Tambah" button
|
||||||
|
- Centered modal overlay for create/edit
|
||||||
|
- Same table + modal pattern
|
||||||
|
|
||||||
|
### 6. Calendar Page (Jadwal)
|
||||||
|
- Month view calendar
|
||||||
|
- Color-coded shift events
|
||||||
|
- Legend bar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mobile App (Flutter)
|
||||||
|
|
||||||
|
- Light theme matching web
|
||||||
|
- Background: #F8FAFC
|
||||||
|
- Cards: WHITE
|
||||||
|
- Primary accent: #130F26
|
||||||
|
- Bottom navigation bar
|
||||||
|
- Card-based layouts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Animations
|
||||||
|
|
||||||
|
- Button hover: lift 0.5px up + glow shadow
|
||||||
|
- Card hover: lift 1px up + stronger shadow
|
||||||
|
- Sidebar icon hover: scale to 110%
|
||||||
|
- Modal: fade + slide up + scale
|
||||||
|
- Dropdown: fade + slide down
|
||||||
|
- Table row hover: lift with shadow
|
||||||
|
- All transitions: 200-300ms ease
|
||||||
|
|
@ -14,6 +14,7 @@ class NotifikasiApiController extends Controller
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$notifikasi = Notifikasi::forUser(Auth::id())
|
$notifikasi = Notifikasi::forUser(Auth::id())
|
||||||
|
->whereNotIn('tipe', ['training'])
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->paginate(20);
|
->paginate(20);
|
||||||
|
|
||||||
|
|
@ -22,7 +23,10 @@ public function index(Request $request)
|
||||||
|
|
||||||
public function unreadCount()
|
public function unreadCount()
|
||||||
{
|
{
|
||||||
$count = Notifikasi::forUser(Auth::id())->unread()->count();
|
$count = Notifikasi::forUser(Auth::id())
|
||||||
|
->whereNotIn('tipe', ['training'])
|
||||||
|
->unread()
|
||||||
|
->count();
|
||||||
return ApiResponse::success(['count' => $count]);
|
return ApiResponse::success(['count' => $count]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,7 +39,10 @@ public function markAsRead($id)
|
||||||
|
|
||||||
public function markAllAsRead()
|
public function markAllAsRead()
|
||||||
{
|
{
|
||||||
Notifikasi::forUser(Auth::id())->unread()->update(['is_read' => true]);
|
Notifikasi::forUser(Auth::id())
|
||||||
|
->whereNotIn('tipe', ['training'])
|
||||||
|
->unread()
|
||||||
|
->update(['is_read' => true]);
|
||||||
return ApiResponse::success(null, 'Semua notifikasi ditandai sudah dibaca.');
|
return ApiResponse::success(null, 'Semua notifikasi ditandai sudah dibaca.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ public function index()
|
||||||
'tanggal' => $item->tanggal?->format('Y-m-d'),
|
'tanggal' => $item->tanggal?->format('Y-m-d'),
|
||||||
'foto_url' => $item->foto ? asset('storage/' . $item->foto) : null,
|
'foto_url' => $item->foto ? asset('storage/' . $item->foto) : null,
|
||||||
'lampiran_url' => $item->lampiran ? asset('storage/' . $item->lampiran) : null,
|
'lampiran_url' => $item->lampiran ? asset('storage/' . $item->lampiran) : null,
|
||||||
'jabatan' => $item->pembuat?->jabatan?->nama_jabatan ?? 'Admin',
|
'jabatan' => $item->pembuat?->jabatan?->nama_jabatan ?? 'HRD',
|
||||||
'nama_pembuat' => $item->pembuat?->nama_lengkap ?? 'Admin',
|
'nama_pembuat' => $item->pembuat?->nama_lengkap ?? 'HRD',
|
||||||
'avatar_url' => $item->pembuat?->foto,
|
'avatar_url' => $item->pembuat?->foto,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
@ -48,8 +48,8 @@ public function show($id)
|
||||||
'tanggal' => $item->tanggal?->format('Y-m-d'),
|
'tanggal' => $item->tanggal?->format('Y-m-d'),
|
||||||
'foto_url' => $item->foto ? asset('storage/' . $item->foto) : null,
|
'foto_url' => $item->foto ? asset('storage/' . $item->foto) : null,
|
||||||
'lampiran_url' => $item->lampiran ? asset('storage/' . $item->lampiran) : null,
|
'lampiran_url' => $item->lampiran ? asset('storage/' . $item->lampiran) : null,
|
||||||
'jabatan' => $item->pembuat?->jabatan?->nama_jabatan ?? 'Admin',
|
'jabatan' => $item->pembuat?->jabatan?->nama_jabatan ?? 'HRD',
|
||||||
'nama_pembuat' => $item->pembuat?->nama_lengkap ?? 'Admin',
|
'nama_pembuat' => $item->pembuat?->nama_lengkap ?? 'HRD',
|
||||||
'avatar_url' => $item->pembuat?->foto,
|
'avatar_url' => $item->pembuat?->foto,
|
||||||
], 'Detail pengumuman berhasil dimuat');
|
], 'Detail pengumuman berhasil dimuat');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|
|
||||||
|
|
@ -235,8 +235,8 @@ public function history(Request $request)
|
||||||
$statusMasuk = 'Sakit';
|
$statusMasuk = 'Sakit';
|
||||||
} elseif ($item->id_status != null && $item->id_status != StatusPresensi::TEPAT_WAKTU && $item->id_status != StatusPresensi::TERLAMBAT) {
|
} elseif ($item->id_status != null && $item->id_status != StatusPresensi::TEPAT_WAKTU && $item->id_status != StatusPresensi::TERLAMBAT) {
|
||||||
|
|
||||||
$statusStatus = \DB::table('status_presensi')->where('id_status', $item->id_status)->value('nama_status');
|
$statusStatus = \App\Enums\StatusPresensi::label($item->id_status);
|
||||||
if ($statusStatus) {
|
if ($statusStatus !== 'Unknown') {
|
||||||
$statusMasuk = $statusStatus;
|
$statusMasuk = $statusStatus;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public function update(Request $request)
|
||||||
Storage::disk('public')->delete($user->foto);
|
Storage::disk('public')->delete($user->foto);
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = $request->file('foto')->store('uploads/profil', 'public');
|
$path = $request->file('foto')->store('foto-profil', 'public');
|
||||||
$user->foto = $path;
|
$user->foto = $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
use App\Jobs\ReextractAllFrames;
|
use App\Jobs\ReextractAllFrames;
|
||||||
use App\Jobs\RetrainAllModels;
|
use App\Jobs\RetrainAllModels;
|
||||||
use App\Jobs\MigrateExistingEmbeddings;
|
|
||||||
use App\Services\FaceRecognitionService;
|
use App\Services\FaceRecognitionService;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
@ -255,31 +255,4 @@ public function reextractAll()
|
||||||
return redirect()->back()->with('success', 'Job re-extract frame dari video berhasil ditambahkan ke antrian. Mohon tunggu beberapa saat untuk hasilnya.');
|
return redirect()->back()->with('success', 'Job re-extract frame dari video berhasil ditambahkan ke antrian. Mohon tunggu beberapa saat untuk hasilnya.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function migrateEmbeddings()
|
|
||||||
{
|
|
||||||
dispatch(new MigrateExistingEmbeddings());
|
|
||||||
return redirect()->back()->with('success', 'Proses migrasi embedding sedang berjalan di background. Anda akan menerima notifikasi setelah selesai.');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function uploadVideo(Request $request, $id)
|
|
||||||
{
|
|
||||||
$request->validate([
|
|
||||||
'video_wajah' => 'required|file|mimetypes:video/mp4,video/quicktime,video/x-msvideo,video/3gpp,video/x-matroska|max:51200',
|
|
||||||
], [
|
|
||||||
'video_wajah.required' => 'File video wajah wajib diupload.',
|
|
||||||
'video_wajah.mimetypes' => 'Format video tidak didukung. Gunakan MP4, MOV, atau AVI.',
|
|
||||||
'video_wajah.max' => 'Ukuran video terlalu besar (maksimal 50MB).',
|
|
||||||
]);
|
|
||||||
|
|
||||||
$user = User::findOrFail($id);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$result = $this->faceService->enrollFace($user->id, $request->file('video_wajah'));
|
|
||||||
|
|
||||||
return redirect()->back()->with('success', "Video wajah untuk {$user->nama_lengkap} berhasil diupload. Proses ekstraksi frame sedang berjalan.");
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error("Upload video wajah gagal untuk user {$id}: " . $e->getMessage());
|
|
||||||
return redirect()->back()->with('error', 'Gagal upload video: ' . $e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ public function index()
|
||||||
return $s;
|
return $s;
|
||||||
});
|
});
|
||||||
|
|
||||||
$pegawaiQuery = User::where('status_aktif', 1)->bukanHrd()->with(['kantor', 'jabatan']);
|
$pegawaiQuery = User::where('status_aktif', 1)->bukanSuperAdmin()->with(['kantor', 'jabatan']);
|
||||||
if (!$isGlobalAdmin) {
|
if (!$isGlobalAdmin) {
|
||||||
$pegawaiQuery->where('id_kantor', $user->id_kantor);
|
$pegawaiQuery->where('id_kantor', $user->id_kantor);
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +213,7 @@ public function create()
|
||||||
$kantor = $isGlobalAdmin ? Kantor::all() : Kantor::where('id_kantor', $user->id_kantor)->get();
|
$kantor = $isGlobalAdmin ? Kantor::all() : Kantor::where('id_kantor', $user->id_kantor)->get();
|
||||||
$divisi = Divisi::all();
|
$divisi = Divisi::all();
|
||||||
|
|
||||||
$pegawaiQuery = User::where('status_aktif', 1)->bukanHrd()->with(['kantor', 'jabatan']);
|
$pegawaiQuery = User::where('status_aktif', 1)->bukanSuperAdmin()->with(['kantor', 'jabatan']);
|
||||||
if (!$isGlobalAdmin) {
|
if (!$isGlobalAdmin) {
|
||||||
$pegawaiQuery->where('id_kantor', $user->id_kantor);
|
$pegawaiQuery->where('id_kantor', $user->id_kantor);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ private function buildRekap(int $bulan, int $tahun, $divisiId = null, $kantorId
|
||||||
|
|
||||||
$pegawaiQuery = User::with(['jabatan', 'divisi'])
|
$pegawaiQuery = User::with(['jabatan', 'divisi'])
|
||||||
->where('status_aktif', 1)
|
->where('status_aktif', 1)
|
||||||
->bukanHrd()
|
->bukanSuperAdmin()
|
||||||
->select('id', 'nama_lengkap', 'nik', 'id_jabatan', 'id_divisi')
|
->select('id', 'nama_lengkap', 'nik', 'id_jabatan', 'id_divisi')
|
||||||
->orderBy('nama_lengkap', 'asc');
|
->orderBy('nama_lengkap', 'asc');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ private function buildRekap(string $bulan, string $tahun, $divisiId = null, $kan
|
||||||
->whereMonth('tanggal_lembur', $bulan)
|
->whereMonth('tanggal_lembur', $bulan)
|
||||||
->where('id_status', StatusPengajuan::DISETUJUI);
|
->where('id_status', StatusPengajuan::DISETUJUI);
|
||||||
}])
|
}])
|
||||||
->bukanHrd();
|
->bukanSuperAdmin();
|
||||||
|
|
||||||
if ($kantorId) {
|
if ($kantorId) {
|
||||||
$query->where('id_kantor', $kantorId);
|
$query->where('id_kantor', $kantorId);
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ public function index(Request $request)
|
||||||
$usersQuery = User::with(['kantor', 'divisi', 'jabatan'])
|
$usersQuery = User::with(['kantor', 'divisi', 'jabatan'])
|
||||||
->select('id', 'nama_lengkap', 'nik', 'id_kantor', 'id_divisi', 'id_jabatan')
|
->select('id', 'nama_lengkap', 'nik', 'id_kantor', 'id_divisi', 'id_jabatan')
|
||||||
->where('status_aktif', 1)
|
->where('status_aktif', 1)
|
||||||
->bukanHrd()
|
->bukanSuperAdmin()
|
||||||
->orderBy('nama_lengkap', 'asc');
|
->orderBy('nama_lengkap', 'asc');
|
||||||
|
|
||||||
if (!$isGlobalAdmin) {
|
if (!$isGlobalAdmin) {
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ public function store(StorePengumumanRequest $request)
|
||||||
{
|
{
|
||||||
$data = $request->validated();
|
$data = $request->validated();
|
||||||
$data['dibuat_oleh'] = Auth::id();
|
$data['dibuat_oleh'] = Auth::id();
|
||||||
|
$data['tanggal'] = now()->toDateString();
|
||||||
|
|
||||||
if ($request->hasFile('foto')) {
|
if ($request->hasFile('foto')) {
|
||||||
$data['foto'] = $request->file('foto')->store('uploads/pengumuman/foto', 'public');
|
$data['foto'] = $request->file('foto')->store('uploads/pengumuman/foto', 'public');
|
||||||
|
|
@ -110,4 +111,31 @@ public function destroy(Pengumuman $pengumuman)
|
||||||
return redirect()->back()->with('error', 'Gagal menghapus pengumuman: ' . $e->getMessage());
|
return redirect()->back()->with('error', 'Gagal menghapus pengumuman: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function bulkDelete(\Illuminate\Http\Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$ids = $request->input('ids');
|
||||||
|
if (empty($ids) || !is_array($ids)) {
|
||||||
|
return redirect()->back()->with('error', 'Tidak ada pengumuman yang dipilih.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$pengumumanList = Pengumuman::whereIn('id_pengumuman', $ids)->get();
|
||||||
|
|
||||||
|
foreach ($pengumumanList as $pengumuman) {
|
||||||
|
if ($pengumuman->foto) {
|
||||||
|
Storage::disk('public')->delete($pengumuman->foto);
|
||||||
|
}
|
||||||
|
if ($pengumuman->lampiran) {
|
||||||
|
Storage::disk('public')->delete($pengumuman->lampiran);
|
||||||
|
}
|
||||||
|
$pengumuman->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('pengumuman.index')
|
||||||
|
->with('success', 'Pengumuman yang dipilih berhasil dihapus.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return redirect()->back()->with('error', 'Gagal menghapus pengumuman: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,6 +124,15 @@ public function index(Request $request)
|
||||||
} elseif ($item->id_status == StatusPresensi::TEPAT_WAKTU) {
|
} elseif ($item->id_status == StatusPresensi::TEPAT_WAKTU) {
|
||||||
$item->status_keterlambatan = 'Tepat Waktu';
|
$item->status_keterlambatan = 'Tepat Waktu';
|
||||||
$item->badge_color = 'emerald';
|
$item->badge_color = 'emerald';
|
||||||
|
} elseif ($item->id_status == StatusPresensi::IZIN) {
|
||||||
|
$item->status_keterlambatan = $item->alasan_telat ?? 'Izin / Cuti';
|
||||||
|
$item->badge_color = 'amber';
|
||||||
|
} elseif ($item->id_status == StatusPresensi::SAKIT) {
|
||||||
|
$item->status_keterlambatan = $item->alasan_telat ?? 'Sakit';
|
||||||
|
$item->badge_color = 'amber';
|
||||||
|
} elseif ($item->id_status == StatusPresensi::ALPHA) {
|
||||||
|
$item->status_keterlambatan = 'Alpha';
|
||||||
|
$item->badge_color = 'rose';
|
||||||
}
|
}
|
||||||
|
|
||||||
$item->validasi_label = $item->id_validasi == StatusValidasi::VALID ? 'Disetujui' : ($item->id_validasi == StatusValidasi::DITOLAK ? 'Ditolak' : 'Pending');
|
$item->validasi_label = $item->id_validasi == StatusValidasi::VALID ? 'Disetujui' : ($item->id_validasi == StatusValidasi::DITOLAK ? 'Ditolak' : 'Pending');
|
||||||
|
|
@ -247,7 +256,7 @@ public function create()
|
||||||
$isGlobalAdmin = $user->isGlobalAdmin();
|
$isGlobalAdmin = $user->isGlobalAdmin();
|
||||||
|
|
||||||
$pegawaiQuery = \App\Models\User::with('divisi')
|
$pegawaiQuery = \App\Models\User::with('divisi')
|
||||||
->bukanHrd()
|
->bukanSuperAdmin()
|
||||||
->orderBy('nama_lengkap', 'asc');
|
->orderBy('nama_lengkap', 'asc');
|
||||||
|
|
||||||
if (!$isGlobalAdmin) {
|
if (!$isGlobalAdmin) {
|
||||||
|
|
@ -256,7 +265,9 @@ public function create()
|
||||||
|
|
||||||
$pegawai = $pegawaiQuery->get();
|
$pegawai = $pegawaiQuery->get();
|
||||||
|
|
||||||
$statuses = \Illuminate\Support\Facades\DB::table('status_presensi')->get();
|
$statuses = collect(\App\Enums\StatusPresensi::LABELS)->map(function ($label, $id) {
|
||||||
|
return (object) ['id_status' => $id, 'nama_status' => $label];
|
||||||
|
})->values();
|
||||||
|
|
||||||
return view('presensi.create', compact('pegawai', 'statuses'));
|
return view('presensi.create', compact('pegawai', 'statuses'));
|
||||||
}
|
}
|
||||||
|
|
@ -307,7 +318,9 @@ public function edit($id)
|
||||||
abort(403, 'Anda tidak diizinkan mengedit presensi dari kantor lain.');
|
abort(403, 'Anda tidak diizinkan mengedit presensi dari kantor lain.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$statuses = \Illuminate\Support\Facades\DB::table('status_presensi')->get();
|
$statuses = collect(\App\Enums\StatusPresensi::LABELS)->map(function ($label, $id) {
|
||||||
|
return (object) ['id_status' => $id, 'nama_status' => $label];
|
||||||
|
})->values();
|
||||||
|
|
||||||
return view('presensi.edit', compact('presensi', 'statuses'));
|
return view('presensi.edit', compact('presensi', 'statuses'));
|
||||||
}
|
}
|
||||||
|
|
@ -322,7 +335,7 @@ public function updateManual(Request $request, $id)
|
||||||
}
|
}
|
||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'id_status' => 'required|exists:status_presensi,id_status',
|
'id_status' => ['required', \Illuminate\Validation\Rule::in(array_keys(\App\Enums\StatusPresensi::LABELS))],
|
||||||
'jam_masuk' => [
|
'jam_masuk' => [
|
||||||
\Illuminate\Validation\Rule::requiredIf(fn () => in_array($request->id_status, [StatusPresensi::TEPAT_WAKTU, StatusPresensi::TERLAMBAT])),
|
\Illuminate\Validation\Rule::requiredIf(fn () => in_array($request->id_status, [StatusPresensi::TEPAT_WAKTU, StatusPresensi::TERLAMBAT])),
|
||||||
'nullable',
|
'nullable',
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ public function index()
|
||||||
|
|
||||||
public function create()
|
public function create()
|
||||||
{
|
{
|
||||||
$pegawai = User::with('kantor')->bukanHrd()->get();
|
$pegawai = User::with('kantor')->bukanSuperAdmin()->get();
|
||||||
|
|
||||||
$kantor = \App\Models\Kantor::orderBy('nama_kantor', 'asc')->get();
|
$kantor = \App\Models\Kantor::orderBy('nama_kantor', 'asc')->get();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ public function rules(): array
|
||||||
return [
|
return [
|
||||||
'id_user' => 'required|exists:users,id',
|
'id_user' => 'required|exists:users,id',
|
||||||
'tanggal' => 'required|date',
|
'tanggal' => 'required|date',
|
||||||
'id_status' => 'required|exists:status_presensi,id_status',
|
'id_status' => ['required', Rule::in(array_keys(\App\Enums\StatusPresensi::LABELS))],
|
||||||
'jam_masuk' => [
|
'jam_masuk' => [
|
||||||
Rule::requiredIf(fn () => in_array($this->id_status, [1, 2])),
|
Rule::requiredIf(fn () => in_array($this->id_status, [1, 2])),
|
||||||
'nullable',
|
'nullable',
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ public function rules(): array
|
||||||
return [
|
return [
|
||||||
'judul' => 'required|string|max:80',
|
'judul' => 'required|string|max:80',
|
||||||
'isi' => 'required|string|max:500',
|
'isi' => 'required|string|max:500',
|
||||||
'tanggal' => 'required|date',
|
|
||||||
'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
|
'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
|
||||||
'lampiran' => 'nullable|file|mimes:pdf,doc,docx,xls,xlsx|max:5120',
|
'lampiran' => 'nullable|file|mimes:pdf,doc,docx,xls,xlsx|max:5120',
|
||||||
];
|
];
|
||||||
|
|
@ -28,7 +27,6 @@ public function messages(): array
|
||||||
'judul.required' => 'Judul pengumuman wajib diisi.',
|
'judul.required' => 'Judul pengumuman wajib diisi.',
|
||||||
'judul.max' => 'Judul maksimal 80 karakter.',
|
'judul.max' => 'Judul maksimal 80 karakter.',
|
||||||
'isi.required' => 'Isi pengumuman wajib diisi.',
|
'isi.required' => 'Isi pengumuman wajib diisi.',
|
||||||
'tanggal.required' => 'Tanggal tayang wajib diisi.',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,7 +16,6 @@ public function rules(): array
|
||||||
return [
|
return [
|
||||||
'judul' => 'required|string|max:80',
|
'judul' => 'required|string|max:80',
|
||||||
'isi' => 'required|string|max:500',
|
'isi' => 'required|string|max:500',
|
||||||
'tanggal' => 'required|date',
|
|
||||||
'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
|
'foto' => 'nullable|image|mimes:jpeg,png,jpg|max:2048',
|
||||||
'lampiran' => 'nullable|file|mimes:pdf,doc,docx,xls,xlsx|max:5120',
|
'lampiran' => 'nullable|file|mimes:pdf,doc,docx,xls,xlsx|max:5120',
|
||||||
];
|
];
|
||||||
|
|
@ -28,7 +27,6 @@ public function messages(): array
|
||||||
'judul.required' => 'Judul pengumuman wajib diisi.',
|
'judul.required' => 'Judul pengumuman wajib diisi.',
|
||||||
'judul.max' => 'Judul maksimal 80 karakter.',
|
'judul.max' => 'Judul maksimal 80 karakter.',
|
||||||
'isi.required' => 'Isi pengumuman wajib diisi.',
|
'isi.required' => 'Isi pengumuman wajib diisi.',
|
||||||
'tanggal.required' => 'Tanggal tayang wajib diisi.',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Jobs;
|
|
||||||
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
|
||||||
|
|
||||||
class MigrateExistingEmbeddings implements ShouldQueue, ShouldBeUnique
|
|
||||||
{
|
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
||||||
|
|
||||||
public $timeout = 3600;
|
|
||||||
public $uniqueFor = 300;
|
|
||||||
|
|
||||||
public function handle(): void
|
|
||||||
{
|
|
||||||
Log::info("Memulai migrasi embedding untuk data wajah yang sudah ada...");
|
|
||||||
|
|
||||||
$users = DB::table('data_wajah')
|
|
||||||
->whereNotNull('path_video')
|
|
||||||
->whereNull('face_embeddings')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
if ($users->isEmpty()) {
|
|
||||||
Log::info("Tidak ada data wajah yang perlu dimigrasi.");
|
|
||||||
app(\App\Services\NotifikasiService::class)->kirimKeRole(
|
|
||||||
'hrd',
|
|
||||||
'pengumuman',
|
|
||||||
'Migrasi Embedding Selesai',
|
|
||||||
'Semua data wajah sudah memiliki embedding. Tidak ada yang perlu dimigrasi.'
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info("Ditemukan {$users->count()} user yang perlu dimigrasi.");
|
|
||||||
|
|
||||||
$successCount = 0;
|
|
||||||
$failCount = 0;
|
|
||||||
|
|
||||||
foreach ($users as $data) {
|
|
||||||
$userId = $data->id_user;
|
|
||||||
$videoPath = Storage::disk('local')->path($data->path_video);
|
|
||||||
|
|
||||||
if (!file_exists($videoPath)) {
|
|
||||||
Log::warning("Migrasi: Video tidak ditemukan untuk user {$userId}: {$videoPath}");
|
|
||||||
$failCount++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$response = Http::timeout(180)
|
|
||||||
->withHeaders([
|
|
||||||
'X-API-Key' => config('services.flask.api_key'),
|
|
||||||
])
|
|
||||||
->attach(
|
|
||||||
'video',
|
|
||||||
fopen($videoPath, 'r'),
|
|
||||||
'enrollment.mp4'
|
|
||||||
)
|
|
||||||
->post(config('services.flask.url') . '/extract-and-embed', [
|
|
||||||
'user_id' => (string) $userId,
|
|
||||||
'target_frames' => 200,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (!$response->successful()) {
|
|
||||||
throw new \Exception("Flask API error: HTTP {$response->status()} - " . $response->body());
|
|
||||||
}
|
|
||||||
|
|
||||||
$output = $response->json();
|
|
||||||
|
|
||||||
if (!isset($output['status']) || $output['status'] !== 'success') {
|
|
||||||
throw new \Exception("Extract & embed error: " . ($output['message'] ?? 'Unknown'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$embedding = $output['embedding'] ?? null;
|
|
||||||
$jumlahFrame = $output['total_extracted'] ?? 0;
|
|
||||||
|
|
||||||
if ($embedding && is_array($embedding)) {
|
|
||||||
DB::table('data_wajah')->where('id_user', $userId)->update([
|
|
||||||
'face_embeddings' => json_encode($embedding),
|
|
||||||
'jumlah_embedding' => $jumlahFrame,
|
|
||||||
'jumlah_frame' => $jumlahFrame,
|
|
||||||
'embedding_generated_at' => now(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
Log::info("Migrasi berhasil untuk user {$userId}. Embedding dimensi: " . count($embedding));
|
|
||||||
$successCount++;
|
|
||||||
} else {
|
|
||||||
throw new \Exception("Embedding kosong dari Flask API.");
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
Log::error("Migrasi GAGAL untuk user {$userId}: " . $e->getMessage());
|
|
||||||
$failCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log::info("Migrasi embedding selesai. Berhasil: {$successCount}, Gagal: {$failCount}");
|
|
||||||
|
|
||||||
app(\App\Services\NotifikasiService::class)->kirimKeRole(
|
|
||||||
'hrd',
|
|
||||||
'pengumuman',
|
|
||||||
'Migrasi Embedding Selesai',
|
|
||||||
"Proses migrasi data wajah ke sistem baru selesai. Berhasil: {$successCount}, Gagal: {$failCount}."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -134,9 +134,11 @@ public function handle(): void
|
||||||
|
|
||||||
app(\App\Services\NotifikasiService::class)->kirimKeRole(
|
app(\App\Services\NotifikasiService::class)->kirimKeRole(
|
||||||
'hrd',
|
'hrd',
|
||||||
'pengumuman',
|
'training',
|
||||||
'Sync & Retrain Selesai',
|
'Sync & Retrain Selesai',
|
||||||
"Sync frame selesai (Extract: {$extractCount}, Skip: {$skipCount}, Gagal: {$failCount}). Model SVM sedang di-training ulang."
|
"Sync frame selesai (Extract: {$extractCount}, Skip: {$skipCount}, Gagal: {$failCount}). Model SVM sedang di-training ulang.",
|
||||||
|
[],
|
||||||
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,9 +72,11 @@ public function handle(): void
|
||||||
|
|
||||||
app(\App\Services\NotifikasiService::class)->kirimKeRole(
|
app(\App\Services\NotifikasiService::class)->kirimKeRole(
|
||||||
'hrd',
|
'hrd',
|
||||||
'pengumuman',
|
'training',
|
||||||
'Training Model Selesai',
|
'Training Model Selesai',
|
||||||
"Sistem telah berhasil memperbarui model AI Face Recognition untuk {$totalUsers} karyawan."
|
"Sistem telah berhasil memperbarui model AI Face Recognition untuk {$totalUsers} karyawan.",
|
||||||
|
[],
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|
@ -87,9 +89,11 @@ public function handle(): void
|
||||||
|
|
||||||
app(\App\Services\NotifikasiService::class)->kirimKeRole(
|
app(\App\Services\NotifikasiService::class)->kirimKeRole(
|
||||||
'hrd',
|
'hrd',
|
||||||
'pengumuman',
|
'training',
|
||||||
'Training Model Gagal',
|
'Training Model Gagal',
|
||||||
'Terjadi kesalahan saat melatih ulang model AI. Silakan cek log server.'
|
'Terjadi kesalahan saat melatih ulang model AI. Silakan cek log server.',
|
||||||
|
[],
|
||||||
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,13 @@
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
* @property int $id_user
|
* @property int $id_user
|
||||||
* @property string|null $path_model_yml
|
* @property string|null $path_video
|
||||||
|
* @property int|null $jumlah_frame
|
||||||
* @property int $is_verified 0:Pending, 1:Approved, 2:Rejected
|
* @property int $is_verified 0:Pending, 1:Approved, 2:Rejected
|
||||||
* @property string|null $last_updated
|
* @property string|null $last_updated
|
||||||
* @property string|null $encoding_wajah
|
|
||||||
* @property string $tanggal_latih
|
|
||||||
* @property \Illuminate\Support\Carbon|null $created_at
|
* @property \Illuminate\Support\Carbon|null $created_at
|
||||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||||
* @property-read \App\Models\User $user
|
* @property-read \App\Models\User $user
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah newModelQuery()
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah newQuery()
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah query()
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereCreatedAt($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereEncodingWajah($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereId($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereIdUser($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereIsVerified($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereLastUpdated($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah wherePathModelYml($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereTanggalLatih($value)
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|DataWajah whereUpdatedAt($value)
|
|
||||||
* @mixin \Eloquent
|
* @mixin \Eloquent
|
||||||
*/
|
*/
|
||||||
class DataWajah extends Model
|
class DataWajah extends Model
|
||||||
|
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @property-read \App\Models\User|null $user
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Poin newModelQuery()
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Poin newQuery()
|
|
||||||
* @method static \Illuminate\Database\Eloquent\Builder<static>|Poin query()
|
|
||||||
* @mixin \Eloquent
|
|
||||||
*/
|
|
||||||
class Poin extends Model
|
|
||||||
{
|
|
||||||
protected $table = 'poin';
|
|
||||||
protected $primaryKey = 'id_poin';
|
|
||||||
|
|
||||||
protected $fillable = [
|
|
||||||
'id_user',
|
|
||||||
'jumlah_poin',
|
|
||||||
'sumber',
|
|
||||||
'tgl_kadaluarsa',
|
|
||||||
];
|
|
||||||
|
|
||||||
public function user()
|
|
||||||
{
|
|
||||||
return $this->belongsTo(User::class, 'id_user', 'id');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -157,10 +157,10 @@ public function notifikasi(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||||
return $this->hasMany(Notifikasi::class, 'id_user', 'id');
|
return $this->hasMany(Notifikasi::class, 'id_user', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopeBukanHrd($query)
|
public function scopeBukanSuperAdmin($query)
|
||||||
{
|
{
|
||||||
return $query->whereDoesntHave('roles', function ($q) {
|
return $query->whereDoesntHave('roles', function ($q) {
|
||||||
$q->where('nama_role', 'hrd');
|
$q->where('nama_role', 'super_admin');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,10 @@ public function enrollFace($userId, UploadedFile $video)
|
||||||
|
|
||||||
$existing = DB::table('data_wajah')->where('id_user', $userId)->first();
|
$existing = DB::table('data_wajah')->where('id_user', $userId)->first();
|
||||||
if ($existing && $existing->is_verified == StatusVerifikasiWajah::APPROVED) {
|
if ($existing && $existing->is_verified == StatusVerifikasiWajah::APPROVED) {
|
||||||
if ($existing->face_embeddings !== null) {
|
if (($existing->jumlah_frame ?? 0) > 0) {
|
||||||
throw new \Exception('Wajah Anda sudah terverifikasi. Hubungi HRD untuk reset.', 400);
|
throw new \Exception('Wajah Anda sudah terverifikasi. Hubungi HRD untuk reset.', 400);
|
||||||
}
|
}
|
||||||
Log::warning("User {$userId} is_verified=APPROVED tapi embedding kosong, izinkan re-enrollment.");
|
Log::warning("User {$userId} is_verified=APPROVED tapi data kosong, izinkan re-enrollment.");
|
||||||
}
|
}
|
||||||
|
|
||||||
$videoStoragePath = "face_videos/{$userId}";
|
$videoStoragePath = "face_videos/{$userId}";
|
||||||
|
|
@ -51,14 +51,8 @@ public function enrollFace($userId, UploadedFile $video)
|
||||||
DB::table('data_wajah')->updateOrInsert(
|
DB::table('data_wajah')->updateOrInsert(
|
||||||
['id_user' => $userId],
|
['id_user' => $userId],
|
||||||
[
|
[
|
||||||
'path_model_yml' => null,
|
|
||||||
'path_model_pkl' => null,
|
|
||||||
'path_scaler_pkl' => null,
|
|
||||||
'path_video' => "{$videoStoragePath}/enrollment.mp4",
|
'path_video' => "{$videoStoragePath}/enrollment.mp4",
|
||||||
'jumlah_frame' => null,
|
'jumlah_frame' => null,
|
||||||
'face_embeddings' => null,
|
|
||||||
'jumlah_embedding' => null,
|
|
||||||
'embedding_generated_at' => null,
|
|
||||||
'is_verified' => StatusVerifikasiWajah::PENDING,
|
'is_verified' => StatusVerifikasiWajah::PENDING,
|
||||||
'last_updated' => now(),
|
'last_updated' => now(),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class NotifikasiService
|
||||||
/**
|
/**
|
||||||
* Kirim notifikasi ke satu user.
|
* Kirim notifikasi ke satu user.
|
||||||
*/
|
*/
|
||||||
public function kirim(int $idUser, string $tipe, string $judul, string $pesan, array $data = []): void
|
public function kirim(int $idUser, string $tipe, string $judul, string $pesan, array $data = [], bool $sendFcm = true): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
Notifikasi::create([
|
Notifikasi::create([
|
||||||
|
|
@ -28,8 +28,10 @@ public function kirim(int $idUser, string $tipe, string $judul, string $pesan, a
|
||||||
$unreadCount = Notifikasi::where('id_user', $idUser)->where('is_read', false)->count();
|
$unreadCount = Notifikasi::where('id_user', $idUser)->where('is_read', false)->count();
|
||||||
broadcast(new NotifikasiCreated($idUser, $judul, $pesan, $tipe, $unreadCount));
|
broadcast(new NotifikasiCreated($idUser, $judul, $pesan, $tipe, $unreadCount));
|
||||||
|
|
||||||
$data['tipe'] = $tipe;
|
if ($sendFcm) {
|
||||||
\App\Jobs\SendFcmPushNotification::dispatch($idUser, $judul, $pesan, $data);
|
$data['tipe'] = $tipe;
|
||||||
|
\App\Jobs\SendFcmPushNotification::dispatch($idUser, $judul, $pesan, $data);
|
||||||
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error('NotifikasiService::kirim gagal: ' . $e->getMessage());
|
Log::error('NotifikasiService::kirim gagal: ' . $e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +48,7 @@ public function kirimBroadcast(string $tipe, string $judul, string $pesan, array
|
||||||
/**
|
/**
|
||||||
* Kirim ke semua user dengan role tertentu (contoh: HRD, Manajer).
|
* Kirim ke semua user dengan role tertentu (contoh: HRD, Manajer).
|
||||||
*/
|
*/
|
||||||
public function kirimKeRole(string $namaRole, string $tipe, string $judul, string $pesan, array $data = []): void
|
public function kirimKeRole(string $namaRole, string $tipe, string $judul, string $pesan, array $data = [], bool $sendFcm = true): void
|
||||||
{
|
{
|
||||||
$users = User::where('status_aktif', 1)
|
$users = User::where('status_aktif', 1)
|
||||||
->whereHas('roles', function ($q) use ($namaRole) {
|
->whereHas('roles', function ($q) use ($namaRole) {
|
||||||
|
|
@ -55,7 +57,7 @@ public function kirimKeRole(string $namaRole, string $tipe, string $judul, strin
|
||||||
->pluck('id');
|
->pluck('id');
|
||||||
|
|
||||||
foreach ($users as $idUser) {
|
foreach ($users as $idUser) {
|
||||||
$this->kirim($idUser, $tipe, $judul, $pesan, $data);
|
$this->kirim($idUser, $tipe, $judul, $pesan, $data, $sendFcm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,10 @@ public function getDynamicSchedule($userId, $date)
|
||||||
|
|
||||||
public function absenMasuk($user, $request)
|
public function absenMasuk($user, $request)
|
||||||
{
|
{
|
||||||
|
if (!$user->is_face_registered || !$user->dataWajah || $user->dataWajah->is_verified != StatusVerifikasiWajah::APPROVED) {
|
||||||
|
throw new \Exception('Presensi ditolak. Wajah Anda belum terdaftar atau belum disetujui/diverifikasi oleh HRD.', 403);
|
||||||
|
}
|
||||||
|
|
||||||
$hariIni = Carbon::today('Asia/Jakarta')->toDateString();
|
$hariIni = Carbon::today('Asia/Jakarta')->toDateString();
|
||||||
$jamSekarang = Carbon::now('Asia/Jakarta');
|
$jamSekarang = Carbon::now('Asia/Jakarta');
|
||||||
|
|
||||||
|
|
@ -180,6 +184,10 @@ public function absenMasuk($user, $request)
|
||||||
|
|
||||||
public function absenPulang($user, $request)
|
public function absenPulang($user, $request)
|
||||||
{
|
{
|
||||||
|
if (!$user->is_face_registered || !$user->dataWajah || $user->dataWajah->is_verified != StatusVerifikasiWajah::APPROVED) {
|
||||||
|
throw new \Exception('Presensi ditolak. Wajah Anda belum terdaftar atau belum disetujui/diverifikasi oleh HRD.', 403);
|
||||||
|
}
|
||||||
|
|
||||||
$jamSekarang = Carbon::now('Asia/Jakarta');
|
$jamSekarang = Carbon::now('Asia/Jakarta');
|
||||||
|
|
||||||
$presensi = Presensi::where('id_user', $user->id)
|
$presensi = Presensi::where('id_user', $user->id)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
|
$middleware->trustProxies(at: '*');
|
||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
'role' => \App\Http\Middleware\CheckRole::class,
|
'role' => \App\Http\Middleware\CheckRole::class,
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,10 @@
|
||||||
"Composer\\Config::disableProcessTimeout",
|
"Composer\\Config::disableProcessTimeout",
|
||||||
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74,#a3e635\" \"php artisan serve --host=0.0.0.0\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan reverb:start\" \"npm run dev\" --names=server,queue,reverb,vite --kill-others"
|
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74,#a3e635\" \"php artisan serve --host=0.0.0.0\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan reverb:start\" \"npm run dev\" --names=server,queue,reverb,vite --kill-others"
|
||||||
],
|
],
|
||||||
|
"serve-all": [
|
||||||
|
"Composer\\Config::disableProcessTimeout",
|
||||||
|
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185\" \"php artisan serve --host=0.0.0.0\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan reverb:start\" --names=server,queue,reverb --kill-others"
|
||||||
|
],
|
||||||
"test": [
|
"test": [
|
||||||
"@php artisan config:clear --ansi",
|
"@php artisan config:clear --ansi",
|
||||||
"@php artisan test"
|
"@php artisan test"
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@
|
||||||
|
|
||||||
'key' => env('APP_KEY'),
|
'key' => env('APP_KEY'),
|
||||||
|
|
||||||
|
'cron_secret_key' => env('CRON_SECRET_KEY', ''),
|
||||||
|
|
||||||
'previous_keys' => [
|
'previous_keys' => [
|
||||||
...array_filter(
|
...array_filter(
|
||||||
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
|
explode(',', (string) env('APP_PREVIOUS_KEYS', ''))
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -0,0 +1,802 @@
|
||||||
|
-- MySQL dump 10.19 Distrib 10.4.32-MySQL, for Win64 (AMD64)
|
||||||
|
--
|
||||||
|
-- Host: localhost Database: db_mpg
|
||||||
|
-- ------------------------------------------------------
|
||||||
|
-- Server version 10.4.32-MySQL
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!40101 SET NAMES utf8mb4 */;
|
||||||
|
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||||
|
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||||
|
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||||
|
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||||
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `approval_surat`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `approval_surat` (
|
||||||
|
`id_approval` varchar(20) NOT NULL,
|
||||||
|
`id_surat` varchar(20) NOT NULL,
|
||||||
|
`id_approver` bigint(20) unsigned NOT NULL,
|
||||||
|
`id_ttd_approver` varchar(20) DEFAULT NULL,
|
||||||
|
`tahap` tinyint(4) NOT NULL COMMENT '1=Manajer, 2=HRD',
|
||||||
|
`status` enum('disetujui','ditolak') NOT NULL DEFAULT 'disetujui',
|
||||||
|
`catatan` text DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_approval`),
|
||||||
|
UNIQUE KEY `approval_surat_id_surat_tahap_unique` (`id_surat`,`tahap`),
|
||||||
|
KEY `approval_surat_id_approver_foreign` (`id_approver`),
|
||||||
|
KEY `approval_surat_id_ttd_approver_foreign` (`id_ttd_approver`),
|
||||||
|
CONSTRAINT `approval_surat_id_approver_foreign` FOREIGN KEY (`id_approver`) REFERENCES `users` (`id`),
|
||||||
|
CONSTRAINT `approval_surat_id_surat_foreign` FOREIGN KEY (`id_surat`) REFERENCES `surat_izin` (`id_surat`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `approval_surat_id_ttd_approver_foreign` FOREIGN KEY (`id_ttd_approver`) REFERENCES `tanda_tangan` (`id_tanda_tangan`) ON DELETE SET NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `cache`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `cache` (
|
||||||
|
`key` varchar(255) NOT NULL,
|
||||||
|
`value` mediumtext NOT NULL,
|
||||||
|
`expiration` int(11) NOT NULL,
|
||||||
|
PRIMARY KEY (`key`),
|
||||||
|
KEY `cache_expiration_index` (`expiration`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `cache_locks`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `cache_locks` (
|
||||||
|
`key` varchar(255) NOT NULL,
|
||||||
|
`owner` varchar(255) NOT NULL,
|
||||||
|
`expiration` int(11) NOT NULL,
|
||||||
|
PRIMARY KEY (`key`),
|
||||||
|
KEY `cache_locks_expiration_index` (`expiration`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `data_wajah`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `data_wajah` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_user` bigint(20) unsigned NOT NULL,
|
||||||
|
`path_video` varchar(255) DEFAULT NULL,
|
||||||
|
`jumlah_frame` int(11) DEFAULT NULL,
|
||||||
|
`is_verified` tinyint(4) NOT NULL DEFAULT 0 COMMENT '0:Pending, 1:Approved, 2:Rejected',
|
||||||
|
`last_updated` timestamp NULL DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `data_wajah_id_user_foreign` (`id_user`),
|
||||||
|
CONSTRAINT `data_wajah_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `detail_penggunaan_poin`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `detail_penggunaan_poin` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_penggunaan` varchar(20) NOT NULL,
|
||||||
|
`id_poin_sumber` bigint(20) unsigned NOT NULL,
|
||||||
|
`jumlah_diambil` int(11) NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `detail_penggunaan_poin_id_poin_sumber_foreign` (`id_poin_sumber`),
|
||||||
|
KEY `detail_penggunaan_poin_id_penggunaan_foreign` (`id_penggunaan`),
|
||||||
|
CONSTRAINT `detail_penggunaan_poin_id_penggunaan_foreign` FOREIGN KEY (`id_penggunaan`) REFERENCES `penggunaan_poin` (`id_penggunaan`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `detail_penggunaan_poin_id_poin_sumber_foreign` FOREIGN KEY (`id_poin_sumber`) REFERENCES `poin_lembur` (`id_poin`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `detail_role_permissions`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `detail_role_permissions` (
|
||||||
|
`id_role` bigint(20) unsigned NOT NULL,
|
||||||
|
`id_permission` bigint(20) unsigned NOT NULL,
|
||||||
|
PRIMARY KEY (`id_role`,`id_permission`),
|
||||||
|
KEY `detail_role_permissions_id_permission_foreign` (`id_permission`),
|
||||||
|
CONSTRAINT `detail_role_permissions_id_permission_foreign` FOREIGN KEY (`id_permission`) REFERENCES `permissions` (`id_permission`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `detail_role_permissions_id_role_foreign` FOREIGN KEY (`id_role`) REFERENCES `roles` (`id_role`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `detail_user_roles`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `detail_user_roles` (
|
||||||
|
`id_user` bigint(20) unsigned NOT NULL,
|
||||||
|
`id_role` bigint(20) unsigned NOT NULL,
|
||||||
|
PRIMARY KEY (`id_user`,`id_role`),
|
||||||
|
KEY `detail_user_roles_id_role_foreign` (`id_role`),
|
||||||
|
CONSTRAINT `detail_user_roles_id_role_foreign` FOREIGN KEY (`id_role`) REFERENCES `roles` (`id_role`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `detail_user_roles_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `device_tokens`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `device_tokens` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_user` bigint(20) unsigned NOT NULL,
|
||||||
|
`fcm_token` varchar(255) NOT NULL,
|
||||||
|
`device_type` varchar(20) NOT NULL DEFAULT 'android',
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `device_tokens_fcm_token_unique` (`fcm_token`),
|
||||||
|
KEY `device_tokens_id_user_index` (`id_user`),
|
||||||
|
CONSTRAINT `device_tokens_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `divisi`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `divisi` (
|
||||||
|
`id_divisi` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_divisi` varchar(50) NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_divisi`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `failed_jobs`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `failed_jobs` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`uuid` varchar(255) NOT NULL,
|
||||||
|
`connection` text NOT NULL,
|
||||||
|
`queue` text NOT NULL,
|
||||||
|
`payload` longtext NOT NULL,
|
||||||
|
`exception` longtext NOT NULL,
|
||||||
|
`failed_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `failed_jobs_uuid_unique` (`uuid`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `hari_libur`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `hari_libur` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`tanggal` date NOT NULL,
|
||||||
|
`keterangan` varchar(255) NOT NULL,
|
||||||
|
`id_kantor` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `hari_libur_id_kantor_foreign` (`id_kantor`),
|
||||||
|
CONSTRAINT `hari_libur_id_kantor_foreign` FOREIGN KEY (`id_kantor`) REFERENCES `kantor` (`id_kantor`) ON DELETE SET NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `jabatan`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `jabatan` (
|
||||||
|
`id_jabatan` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_jabatan` varchar(50) NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_jabatan`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `jadwal_kerja`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `jadwal_kerja` (
|
||||||
|
`id_jadwal` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_user` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`tanggal` date DEFAULT NULL,
|
||||||
|
`id_shift` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_jadwal`),
|
||||||
|
KEY `jadwal_kerja_id_shift_foreign` (`id_shift`),
|
||||||
|
KEY `idx_jadwal_tanggal` (`tanggal`),
|
||||||
|
KEY `jadwal_kerja_id_user_tanggal_index` (`id_user`,`tanggal`),
|
||||||
|
CONSTRAINT `jadwal_kerja_id_shift_foreign` FOREIGN KEY (`id_shift`) REFERENCES `shift_kerja` (`id_shift`),
|
||||||
|
CONSTRAINT `jadwal_kerja_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `jenis_izin`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `jenis_izin` (
|
||||||
|
`id_jenis_izin` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_izin` varchar(30) NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_jenis_izin`),
|
||||||
|
UNIQUE KEY `jenis_izin_nama_izin_unique` (`nama_izin`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `jenis_kompensasi`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `jenis_kompensasi` (
|
||||||
|
`id_kompensasi` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_kompensasi` varchar(30) NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_kompensasi`),
|
||||||
|
UNIQUE KEY `jenis_kompensasi_nama_kompensasi_unique` (`nama_kompensasi`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `jenis_pengurangan`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `jenis_pengurangan` (
|
||||||
|
`id_pengurangan` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_pengurangan` varchar(30) NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_pengurangan`),
|
||||||
|
UNIQUE KEY `jenis_pengurangan_nama_pengurangan_unique` (`nama_pengurangan`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `jobs`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `jobs` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`queue` varchar(255) NOT NULL,
|
||||||
|
`payload` longtext NOT NULL,
|
||||||
|
`attempts` tinyint(3) unsigned NOT NULL,
|
||||||
|
`reserved_at` int(10) unsigned DEFAULT NULL,
|
||||||
|
`available_at` int(10) unsigned NOT NULL,
|
||||||
|
`created_at` int(10) unsigned NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `jobs_queue_reserved_at_available_at_index` (`queue`,`reserved_at`,`available_at`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `kantor`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `kantor` (
|
||||||
|
`id_kantor` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_kantor` varchar(50) NOT NULL,
|
||||||
|
`tipe` varchar(20) NOT NULL DEFAULT 'Cabang',
|
||||||
|
`alamat` varchar(150) DEFAULT NULL,
|
||||||
|
`latitude` decimal(10,8) DEFAULT NULL,
|
||||||
|
`longitude` decimal(11,8) DEFAULT NULL,
|
||||||
|
`radius` int(11) NOT NULL DEFAULT 100,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_kantor`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `lembur`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `lembur` (
|
||||||
|
`id_lembur` varchar(20) NOT NULL,
|
||||||
|
`id_user` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`tanggal_lembur` date NOT NULL,
|
||||||
|
`jam_mulai` time NOT NULL,
|
||||||
|
`jam_selesai` time NOT NULL,
|
||||||
|
`durasi_menit` int(11) DEFAULT NULL,
|
||||||
|
`keterangan` varchar(255) DEFAULT NULL,
|
||||||
|
`jumlah_poin` int(11) DEFAULT NULL,
|
||||||
|
`id_kompensasi` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`id_status` bigint(20) unsigned NOT NULL DEFAULT 1,
|
||||||
|
`alasan_penolakan` varchar(150) DEFAULT NULL,
|
||||||
|
`tanggal_diajukan` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_lembur`),
|
||||||
|
KEY `lembur_id_kompensasi_foreign` (`id_kompensasi`),
|
||||||
|
KEY `lembur_id_status_index` (`id_status`),
|
||||||
|
KEY `lembur_id_user_index` (`id_user`),
|
||||||
|
CONSTRAINT `lembur_id_kompensasi_foreign` FOREIGN KEY (`id_kompensasi`) REFERENCES `jenis_kompensasi` (`id_kompensasi`),
|
||||||
|
CONSTRAINT `lembur_id_status_foreign` FOREIGN KEY (`id_status`) REFERENCES `status_pengajuan` (`id_status`),
|
||||||
|
CONSTRAINT `lembur_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `log_verifikasi_wajah`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `log_verifikasi_wajah` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_user` bigint(20) unsigned NOT NULL,
|
||||||
|
`skor_kepercayaan` decimal(10,4) DEFAULT NULL,
|
||||||
|
`skor_svm` decimal(10,4) DEFAULT NULL,
|
||||||
|
`jarak_normalisasi` decimal(8,4) DEFAULT NULL,
|
||||||
|
`status_verifikasi` varchar(30) DEFAULT NULL,
|
||||||
|
`apakah_cocok` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
|
`skor_blur` decimal(6,1) DEFAULT NULL,
|
||||||
|
`tipe` varchar(10) NOT NULL DEFAULT 'presensi',
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `log_verifikasi_wajah_id_user_created_at_index` (`id_user`,`created_at`),
|
||||||
|
CONSTRAINT `log_verifikasi_wajah_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `migrations`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `migrations` (
|
||||||
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`migration` varchar(255) NOT NULL,
|
||||||
|
`batch` int(11) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `notifikasi`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `notifikasi` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_user` bigint(20) unsigned NOT NULL,
|
||||||
|
`judul` varchar(255) NOT NULL,
|
||||||
|
`pesan` text NOT NULL,
|
||||||
|
`tipe` varchar(50) NOT NULL,
|
||||||
|
`data` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,
|
||||||
|
`is_read` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `notifikasi_id_user_is_read_index` (`id_user`,`is_read`),
|
||||||
|
KEY `notifikasi_id_user_created_at_index` (`id_user`,`created_at`),
|
||||||
|
CONSTRAINT `notifikasi_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `pengajuan_izin`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `pengajuan_izin` (
|
||||||
|
`id_izin` varchar(20) NOT NULL,
|
||||||
|
`id_user` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`tanggal_mulai` date NOT NULL,
|
||||||
|
`tanggal_selesai` date NOT NULL,
|
||||||
|
`id_jenis_izin` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`alasan` varchar(150) DEFAULT NULL,
|
||||||
|
`bukti_file` varchar(150) DEFAULT NULL,
|
||||||
|
`id_status` bigint(20) unsigned NOT NULL DEFAULT 1,
|
||||||
|
`alasan_penolakan` varchar(150) DEFAULT NULL,
|
||||||
|
`tanggal_diajukan` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_izin`),
|
||||||
|
KEY `pengajuan_izin_id_jenis_izin_foreign` (`id_jenis_izin`),
|
||||||
|
KEY `pengajuan_izin_id_status_index` (`id_status`),
|
||||||
|
KEY `pengajuan_izin_id_user_index` (`id_user`),
|
||||||
|
CONSTRAINT `pengajuan_izin_id_jenis_izin_foreign` FOREIGN KEY (`id_jenis_izin`) REFERENCES `jenis_izin` (`id_jenis_izin`),
|
||||||
|
CONSTRAINT `pengajuan_izin_id_status_foreign` FOREIGN KEY (`id_status`) REFERENCES `status_pengajuan` (`id_status`),
|
||||||
|
CONSTRAINT `pengajuan_izin_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `penggunaan_poin`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `penggunaan_poin` (
|
||||||
|
`id_penggunaan` varchar(20) NOT NULL,
|
||||||
|
`id_user` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`tanggal_penggunaan` date NOT NULL,
|
||||||
|
`jumlah_poin` int(11) DEFAULT NULL,
|
||||||
|
`jam_masuk_custom` time DEFAULT NULL,
|
||||||
|
`jam_pulang_custom` time DEFAULT NULL,
|
||||||
|
`id_pengurangan` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`id_status` bigint(20) unsigned NOT NULL DEFAULT 1,
|
||||||
|
`alasan_penolakan` varchar(150) DEFAULT NULL,
|
||||||
|
`tanggal_diajukan` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_penggunaan`),
|
||||||
|
KEY `penggunaan_poin_id_pengurangan_foreign` (`id_pengurangan`),
|
||||||
|
KEY `penggunaan_poin_id_user_tanggal_penggunaan_index` (`id_user`,`tanggal_penggunaan`),
|
||||||
|
KEY `penggunaan_poin_id_status_index` (`id_status`),
|
||||||
|
CONSTRAINT `penggunaan_poin_id_pengurangan_foreign` FOREIGN KEY (`id_pengurangan`) REFERENCES `jenis_pengurangan` (`id_pengurangan`),
|
||||||
|
CONSTRAINT `penggunaan_poin_id_status_foreign` FOREIGN KEY (`id_status`) REFERENCES `status_pengajuan` (`id_status`),
|
||||||
|
CONSTRAINT `penggunaan_poin_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `pengumuman`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `pengumuman` (
|
||||||
|
`id_pengumuman` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`judul` varchar(80) DEFAULT NULL,
|
||||||
|
`isi` varchar(500) DEFAULT NULL,
|
||||||
|
`foto` varchar(255) DEFAULT NULL,
|
||||||
|
`lampiran` varchar(255) DEFAULT NULL,
|
||||||
|
`tanggal` date DEFAULT NULL,
|
||||||
|
`dibuat_oleh` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_pengumuman`),
|
||||||
|
KEY `pengumuman_dibuat_oleh_foreign` (`dibuat_oleh`),
|
||||||
|
CONSTRAINT `pengumuman_dibuat_oleh_foreign` FOREIGN KEY (`dibuat_oleh`) REFERENCES `users` (`id`) ON DELETE SET NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `permissions`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `permissions` (
|
||||||
|
`id_permission` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_permission` varchar(255) NOT NULL,
|
||||||
|
`slug` varchar(255) NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_permission`),
|
||||||
|
UNIQUE KEY `permissions_nama_permission_unique` (`nama_permission`),
|
||||||
|
UNIQUE KEY `permissions_slug_unique` (`slug`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `personal_access_tokens`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `personal_access_tokens` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`tokenable_type` varchar(255) NOT NULL,
|
||||||
|
`tokenable_id` bigint(20) unsigned NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`token` varchar(64) NOT NULL,
|
||||||
|
`abilities` text DEFAULT NULL,
|
||||||
|
`last_used_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`expires_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `personal_access_tokens_token_unique` (`token`),
|
||||||
|
KEY `personal_access_tokens_tokenable_type_tokenable_id_index` (`tokenable_type`,`tokenable_id`),
|
||||||
|
KEY `personal_access_tokens_expires_at_index` (`expires_at`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `poin_lembur`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `poin_lembur` (
|
||||||
|
`id_poin` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_user` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`jumlah_poin` int(11) DEFAULT NULL,
|
||||||
|
`sisa_poin` int(11) DEFAULT NULL,
|
||||||
|
`id_lembur` varchar(20) DEFAULT NULL,
|
||||||
|
`keterangan` varchar(150) DEFAULT NULL,
|
||||||
|
`tanggal` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`expired_at` date DEFAULT NULL,
|
||||||
|
`is_fully_used` tinyint(1) DEFAULT 0,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_poin`),
|
||||||
|
KEY `poin_lembur_id_user_index` (`id_user`),
|
||||||
|
KEY `poin_lembur_tanggal_index` (`tanggal`),
|
||||||
|
KEY `poin_lembur_id_lembur_foreign` (`id_lembur`),
|
||||||
|
CONSTRAINT `poin_lembur_id_lembur_foreign` FOREIGN KEY (`id_lembur`) REFERENCES `lembur` (`id_lembur`) ON DELETE SET NULL,
|
||||||
|
CONSTRAINT `poin_lembur_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `presensi`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `presensi` (
|
||||||
|
`id_presensi` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_user` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`tanggal` date NOT NULL,
|
||||||
|
`jam_masuk` time DEFAULT NULL,
|
||||||
|
`jam_pulang` time DEFAULT NULL,
|
||||||
|
`lat_masuk` decimal(10,8) DEFAULT NULL,
|
||||||
|
`lon_masuk` decimal(11,8) DEFAULT NULL,
|
||||||
|
`lat_pulang` decimal(10,8) DEFAULT NULL,
|
||||||
|
`lon_pulang` decimal(11,8) DEFAULT NULL,
|
||||||
|
`foto_wajah_masuk` varchar(255) DEFAULT NULL,
|
||||||
|
`foto_wajah_pulang` varchar(255) DEFAULT NULL,
|
||||||
|
`id_status` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`alasan_telat` varchar(150) DEFAULT NULL,
|
||||||
|
`keterangan_pulang` varchar(150) DEFAULT NULL,
|
||||||
|
`waktu_terlambat` time DEFAULT NULL,
|
||||||
|
`waktu_masuk_awal` time DEFAULT NULL,
|
||||||
|
`waktu_pulang_awal` time DEFAULT NULL,
|
||||||
|
`waktu_pulang_akhir` time DEFAULT NULL,
|
||||||
|
`verifikasi_wajah` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
|
`id_validasi` bigint(20) unsigned NOT NULL DEFAULT 1,
|
||||||
|
`keterangan_luar_radius` text DEFAULT NULL,
|
||||||
|
`alasan_penolakan` varchar(150) DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`deleted_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_presensi`),
|
||||||
|
KEY `idx_presensi_tanggal` (`tanggal`),
|
||||||
|
KEY `presensi_tanggal_index` (`tanggal`),
|
||||||
|
KEY `presensi_id_user_index` (`id_user`),
|
||||||
|
KEY `presensi_id_status_index` (`id_status`),
|
||||||
|
KEY `presensi_id_validasi_index` (`id_validasi`),
|
||||||
|
KEY `presensi_id_user_tanggal_index` (`id_user`,`tanggal`),
|
||||||
|
CONSTRAINT `presensi_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `riwayat_tukar_shift`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `riwayat_tukar_shift` (
|
||||||
|
`id_riwayat` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`id_user_1` bigint(20) unsigned NOT NULL,
|
||||||
|
`id_jadwal_1` bigint(20) unsigned NOT NULL,
|
||||||
|
`id_user_2` bigint(20) unsigned NOT NULL,
|
||||||
|
`id_jadwal_2` bigint(20) unsigned NOT NULL,
|
||||||
|
`keterangan` text DEFAULT NULL,
|
||||||
|
`created_by` bigint(20) unsigned NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_riwayat`),
|
||||||
|
KEY `riwayat_tukar_shift_id_user_1_foreign` (`id_user_1`),
|
||||||
|
KEY `riwayat_tukar_shift_id_jadwal_1_foreign` (`id_jadwal_1`),
|
||||||
|
KEY `riwayat_tukar_shift_id_user_2_foreign` (`id_user_2`),
|
||||||
|
KEY `riwayat_tukar_shift_id_jadwal_2_foreign` (`id_jadwal_2`),
|
||||||
|
KEY `riwayat_tukar_shift_created_by_foreign` (`created_by`),
|
||||||
|
CONSTRAINT `riwayat_tukar_shift_created_by_foreign` FOREIGN KEY (`created_by`) REFERENCES `users` (`id`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `riwayat_tukar_shift_id_jadwal_1_foreign` FOREIGN KEY (`id_jadwal_1`) REFERENCES `jadwal_kerja` (`id_jadwal`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `riwayat_tukar_shift_id_jadwal_2_foreign` FOREIGN KEY (`id_jadwal_2`) REFERENCES `jadwal_kerja` (`id_jadwal`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `riwayat_tukar_shift_id_user_1_foreign` FOREIGN KEY (`id_user_1`) REFERENCES `users` (`id`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `riwayat_tukar_shift_id_user_2_foreign` FOREIGN KEY (`id_user_2`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `roles`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `roles` (
|
||||||
|
`id_role` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_role` varchar(30) NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_role`),
|
||||||
|
UNIQUE KEY `roles_nama_role_unique` (`nama_role`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `shift_kerja`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `shift_kerja` (
|
||||||
|
`id_shift` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_shift` varchar(50) DEFAULT NULL,
|
||||||
|
`jam_mulai` time DEFAULT NULL,
|
||||||
|
`jam_selesai` time DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_shift`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `status_pengajuan`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `status_pengajuan` (
|
||||||
|
`id_status` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nama_status` varchar(30) NOT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_status`),
|
||||||
|
UNIQUE KEY `status_pengajuan_nama_status_unique` (`nama_status`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `surat_izin`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `surat_izin` (
|
||||||
|
`id_surat` varchar(20) NOT NULL,
|
||||||
|
`id_izin` varchar(20) NOT NULL,
|
||||||
|
`id_user` bigint(20) unsigned NOT NULL,
|
||||||
|
`nomor_surat` varchar(50) NOT NULL,
|
||||||
|
`isi_surat` text NOT NULL,
|
||||||
|
`id_ttd_pengaju` varchar(20) DEFAULT NULL,
|
||||||
|
`status_surat` enum('menunggu_manajer','menunggu_hrd','disetujui','ditolak') NOT NULL DEFAULT 'menunggu_manajer',
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_surat`),
|
||||||
|
KEY `surat_izin_id_izin_foreign` (`id_izin`),
|
||||||
|
KEY `surat_izin_id_ttd_pengaju_foreign` (`id_ttd_pengaju`),
|
||||||
|
KEY `surat_izin_id_user_status_surat_index` (`id_user`,`status_surat`),
|
||||||
|
CONSTRAINT `surat_izin_id_izin_foreign` FOREIGN KEY (`id_izin`) REFERENCES `pengajuan_izin` (`id_izin`) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT `surat_izin_id_ttd_pengaju_foreign` FOREIGN KEY (`id_ttd_pengaju`) REFERENCES `tanda_tangan` (`id_tanda_tangan`) ON DELETE SET NULL,
|
||||||
|
CONSTRAINT `surat_izin_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `tanda_tangan`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `tanda_tangan` (
|
||||||
|
`id_tanda_tangan` varchar(20) NOT NULL,
|
||||||
|
`id_user` bigint(20) unsigned NOT NULL,
|
||||||
|
`file_ttd` varchar(255) NOT NULL,
|
||||||
|
`is_active` tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id_tanda_tangan`),
|
||||||
|
KEY `tanda_tangan_id_user_is_active_index` (`id_user`,`is_active`),
|
||||||
|
CONSTRAINT `tanda_tangan_id_user_foreign` FOREIGN KEY (`id_user`) REFERENCES `users` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `users`
|
||||||
|
--
|
||||||
|
|
||||||
|
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||||
|
/*!40101 SET character_set_client = utf8 */;
|
||||||
|
CREATE TABLE `users` (
|
||||||
|
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`nik` varchar(255) DEFAULT NULL,
|
||||||
|
`nama_lengkap` varchar(255) NOT NULL,
|
||||||
|
`email` varchar(255) NOT NULL,
|
||||||
|
`no_telp` varchar(255) DEFAULT NULL,
|
||||||
|
`alamat` text DEFAULT NULL,
|
||||||
|
`email_verified_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`password` varchar(255) NOT NULL,
|
||||||
|
`sisa_cuti` int(11) NOT NULL DEFAULT 12,
|
||||||
|
`foto` varchar(255) DEFAULT NULL,
|
||||||
|
`remember_token` varchar(100) DEFAULT NULL,
|
||||||
|
`created_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`updated_at` timestamp NULL DEFAULT NULL,
|
||||||
|
`id_kantor` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`id_divisi` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`id_jabatan` bigint(20) unsigned DEFAULT NULL,
|
||||||
|
`status_aktif` tinyint(1) NOT NULL DEFAULT 1,
|
||||||
|
`tgl_bergabung` date DEFAULT NULL,
|
||||||
|
`is_face_registered` tinyint(1) NOT NULL DEFAULT 0,
|
||||||
|
`deleted_at` timestamp NULL DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `users_email_unique` (`email`),
|
||||||
|
UNIQUE KEY `users_nik_unique` (`nik`),
|
||||||
|
KEY `users_id_jabatan_index` (`id_jabatan`),
|
||||||
|
KEY `users_id_divisi_index` (`id_divisi`),
|
||||||
|
KEY `users_id_kantor_index` (`id_kantor`),
|
||||||
|
KEY `users_status_aktif_index` (`status_aktif`),
|
||||||
|
CONSTRAINT `users_id_divisi_foreign` FOREIGN KEY (`id_divisi`) REFERENCES `divisi` (`id_divisi`) ON DELETE SET NULL,
|
||||||
|
CONSTRAINT `users_id_jabatan_foreign` FOREIGN KEY (`id_jabatan`) REFERENCES `jabatan` (`id_jabatan`) ON DELETE SET NULL,
|
||||||
|
CONSTRAINT `users_id_kantor_foreign` FOREIGN KEY (`id_kantor`) REFERENCES `kantor` (`id_kantor`) ON DELETE SET NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||||
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||||
|
|
||||||
|
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||||
|
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||||
|
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||||
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
|
||||||
|
-- Dump completed on 2026-05-17 23:31:48
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
-- =====================================================
|
||||||
|
-- Cleanup kolom deprecated di tabel data_wajah
|
||||||
|
-- Jalankan di MySQL Workbench / phpMyAdmin / CLI
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
USE `db_mpg`;
|
||||||
|
|
||||||
|
ALTER TABLE `data_wajah`
|
||||||
|
DROP COLUMN IF EXISTS `path_model_yml`,
|
||||||
|
DROP COLUMN IF EXISTS `path_model_pkl`,
|
||||||
|
DROP COLUMN IF EXISTS `path_scaler_pkl`,
|
||||||
|
DROP COLUMN IF EXISTS `face_embeddings`,
|
||||||
|
DROP COLUMN IF EXISTS `jumlah_embedding`,
|
||||||
|
DROP COLUMN IF EXISTS `embedding_generated_at`,
|
||||||
|
DROP COLUMN IF EXISTS `encoding_wajah`,
|
||||||
|
DROP COLUMN IF EXISTS `tanggal_latih`;
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('presensi', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['id_status']);
|
||||||
|
$table->dropForeign(['id_validasi']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::dropIfExists('status_presensi');
|
||||||
|
Schema::dropIfExists('status_validasi_presensi');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
// Recreate tables and constraints if needed, but omitted since they are replaced by Enum
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('data_wajah', function (Blueprint $table) {
|
||||||
|
$columnsToDrop = [
|
||||||
|
'path_model_yml',
|
||||||
|
'encoding_wajah',
|
||||||
|
'tanggal_latih',
|
||||||
|
'path_model_pkl',
|
||||||
|
'path_scaler_pkl',
|
||||||
|
'face_embeddings',
|
||||||
|
'jumlah_embedding',
|
||||||
|
'embedding_generated_at',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($columnsToDrop as $column) {
|
||||||
|
if (Schema::hasColumn('data_wajah', $column)) {
|
||||||
|
$table->dropColumn($column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('data_wajah', function (Blueprint $table) {
|
||||||
|
if (!Schema::hasColumn('data_wajah', 'path_model_yml')) {
|
||||||
|
$table->string('path_model_yml')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('data_wajah', 'encoding_wajah')) {
|
||||||
|
$table->text('encoding_wajah')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('data_wajah', 'tanggal_latih')) {
|
||||||
|
$table->timestamp('tanggal_latih')->useCurrent();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('data_wajah', 'path_model_pkl')) {
|
||||||
|
$table->string('path_model_pkl')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('data_wajah', 'path_scaler_pkl')) {
|
||||||
|
$table->string('path_scaler_pkl')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('data_wajah', 'face_embeddings')) {
|
||||||
|
$table->json('face_embeddings')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('data_wajah', 'jumlah_embedding')) {
|
||||||
|
$table->integer('jumlah_embedding')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('data_wajah', 'embedding_generated_at')) {
|
||||||
|
$table->timestamp('embedding_generated_at')->nullable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 163 KiB |
|
|
@ -1,3 +1,5 @@
|
||||||
|
const APP_BASE_URL = document.querySelector('meta[name="app-url"]')?.content || '';
|
||||||
|
|
||||||
function initSweetAlerts() {
|
function initSweetAlerts() {
|
||||||
const swalData = document.getElementById('swal-data');
|
const swalData = document.getElementById('swal-data');
|
||||||
const flashError = document.querySelector('.flash-data-error');
|
const flashError = document.querySelector('.flash-data-error');
|
||||||
|
|
@ -91,7 +93,7 @@ document.addEventListener('DOMContentLoaded', initSweetAlerts);
|
||||||
const badge = document.getElementById('notif-badge');
|
const badge = document.getElementById('notif-badge');
|
||||||
if (!badge) return; // User tidak punya akses notifikasi, hentikan tanpa fetch
|
if (!badge) return; // User tidak punya akses notifikasi, hentikan tanpa fetch
|
||||||
|
|
||||||
fetch('/notifikasi/unread-count', {
|
fetch(APP_BASE_URL + '/notifikasi/unread-count', {
|
||||||
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }
|
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }
|
||||||
})
|
})
|
||||||
.then(r => {
|
.then(r => {
|
||||||
|
|
@ -129,7 +131,7 @@ function notifDropdown() {
|
||||||
|
|
||||||
loadRecent() {
|
loadRecent() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
fetch('/notifikasi/recent', {
|
fetch(APP_BASE_URL + '/notifikasi/recent', {
|
||||||
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }
|
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' }
|
||||||
})
|
})
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
|
|
@ -143,7 +145,7 @@ function notifDropdown() {
|
||||||
|
|
||||||
readItem(item) {
|
readItem(item) {
|
||||||
if (!item.is_read) {
|
if (!item.is_read) {
|
||||||
fetch('/notifikasi/' + item.id + '/read', {
|
fetch(APP_BASE_URL + '/notifikasi/' + item.id + '/read', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||||
|
|
@ -159,7 +161,7 @@ function notifDropdown() {
|
||||||
},
|
},
|
||||||
|
|
||||||
markAllRead() {
|
markAllRead() {
|
||||||
fetch('/notifikasi/read-all', {
|
fetch(APP_BASE_URL + '/notifikasi/read-all', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,16 @@ import Echo from 'laravel-echo';
|
||||||
import Pusher from 'pusher-js';
|
import Pusher from 'pusher-js';
|
||||||
window.Pusher = Pusher;
|
window.Pusher = Pusher;
|
||||||
|
|
||||||
window.Echo = new Echo({
|
const reverbKey = import.meta.env.VITE_REVERB_APP_KEY;
|
||||||
broadcaster: 'reverb',
|
|
||||||
key: import.meta.env.VITE_REVERB_APP_KEY,
|
if (reverbKey) {
|
||||||
wsHost: import.meta.env.VITE_REVERB_HOST,
|
window.Echo = new Echo({
|
||||||
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
|
broadcaster: 'reverb',
|
||||||
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
|
key: reverbKey,
|
||||||
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
|
wsHost: import.meta.env.VITE_REVERB_HOST,
|
||||||
enabledTransports: ['ws', 'wss'],
|
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
|
||||||
});
|
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
|
||||||
|
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
|
||||||
|
enabledTransports: ['ws', 'wss'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Login Administrator - HRIS MP</title>
|
<title>Login Portal HRD & Administrator - HRIS MP</title>
|
||||||
|
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ class="absolute inset-0 h-full w-full object-cover"
|
||||||
<div class="mb-8 text-center flex flex-col items-center">
|
<div class="mb-8 text-center flex flex-col items-center">
|
||||||
<h2 class="text-3xl font-extrabold text-[#130F26] tracking-tight">Sign in</h2>
|
<h2 class="text-3xl font-extrabold text-[#130F26] tracking-tight">Sign in</h2>
|
||||||
<p class="mt-2 text-sm text-slate-500">
|
<p class="mt-2 text-sm text-slate-500">
|
||||||
Masuk menggunakan akun administrator Anda.
|
Masuk menggunakan akun HRD atau Administrator Anda.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -271,23 +271,9 @@ class="inline-flex items-center px-3 py-1.5 border border-amber-200 text-xs font
|
||||||
</svg>
|
</svg>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
<button onclick="openUploadModal({{ $user->id }}, '{{ addslashes($user->nama_lengkap) }}')"
|
|
||||||
class="inline-flex items-center px-3 py-1.5 border border-blue-200 text-xs font-medium rounded-lg text-blue-700 bg-blue-50 hover:bg-blue-100 transition-colors shadow-sm"
|
|
||||||
title="Upload Ulang Video Wajah">
|
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
|
|
||||||
</svg>
|
|
||||||
Re-Upload
|
|
||||||
</button>
|
|
||||||
@else
|
@else
|
||||||
<button onclick="openUploadModal({{ $user->id }}, '{{ addslashes($user->nama_lengkap) }}')"
|
<span class="text-xs text-slate-400 italic">Registrasi via Aplikasi</span>
|
||||||
class="inline-flex items-center px-3 py-1.5 border border-slate-200 text-xs font-medium rounded-lg text-slate-700 bg-white hover:bg-slate-50 transition-colors shadow-sm"
|
|
||||||
title="Upload Video Wajah">
|
|
||||||
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
|
|
||||||
</svg>
|
|
||||||
Upload Video
|
|
||||||
</button>
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -319,80 +305,7 @@ class="inline-flex items-center px-3 py-1.5 border border-slate-200 text-xs font
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="uploadVideoOverlay" class="fixed inset-0 z-50 hidden items-center justify-center bg-gray-500/50 backdrop-blur-sm" onclick="closeUploadModal()">
|
|
||||||
<div class="relative max-w-md w-full mx-4" onclick="event.stopPropagation()">
|
|
||||||
<div class="bg-white rounded-2xl shadow-2xl overflow-hidden">
|
|
||||||
<div class="flex items-center justify-between px-6 py-4 border-b border-slate-100">
|
|
||||||
<div>
|
|
||||||
<h3 class="font-bold text-slate-800">Upload Video Wajah</h3>
|
|
||||||
<p class="text-sm text-slate-500 mt-0.5" id="uploadUserName">Nama Karyawan</p>
|
|
||||||
</div>
|
|
||||||
<button onclick="closeUploadModal()" class="p-1.5 hover:bg-slate-100 rounded-lg transition">
|
|
||||||
<svg class="w-5 h-5 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<form id="uploadVideoForm" method="POST" enctype="multipart/form-data" class="p-6">
|
|
||||||
@csrf
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="block text-sm font-semibold text-slate-700 mb-2">File Video</label>
|
|
||||||
<div id="dropZone" class="border-2 border-dashed border-slate-300 rounded-xl p-6 text-center hover:border-primary transition-colors cursor-pointer">
|
|
||||||
<svg class="w-10 h-10 text-slate-400 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"></path>
|
|
||||||
</svg>
|
|
||||||
<p class="text-sm text-slate-600 font-medium">Klik atau drag & drop video di sini</p>
|
|
||||||
<p class="text-xs text-slate-400 mt-1">Format: MP4, MOV, AVI (maks. 50MB)</p>
|
|
||||||
<input type="file" name="video_wajah" id="videoFileInput" accept="video/mp4,video/quicktime,video/x-msvideo,.mp4,.mov,.avi" class="hidden" required>
|
|
||||||
</div>
|
|
||||||
<div id="fileInfo" class="hidden mt-3 p-3 bg-slate-50 rounded-lg border border-slate-200">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<svg class="w-5 h-5 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"></path>
|
|
||||||
</svg>
|
|
||||||
<div>
|
|
||||||
<p class="text-sm font-medium text-slate-700" id="fileName"></p>
|
|
||||||
<p class="text-xs text-slate-400" id="fileSize"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button type="button" onclick="clearFile()" class="p-1 hover:bg-slate-200 rounded transition">
|
|
||||||
<svg class="w-4 h-4 text-slate-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bg-amber-50 border border-amber-200 rounded-lg p-3 mb-4">
|
|
||||||
<div class="flex items-start gap-2">
|
|
||||||
<svg class="w-4 h-4 text-amber-600 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
|
|
||||||
</svg>
|
|
||||||
<p class="text-xs text-amber-700">Video akan diproses untuk ekstraksi frame wajah. Jika user sudah punya data wajah sebelumnya, data lama akan ditimpa.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-end gap-3">
|
|
||||||
<button type="button" onclick="closeUploadModal()"
|
|
||||||
class="px-4 py-2 text-sm font-semibold text-slate-600 bg-slate-100 hover:bg-slate-200 rounded-lg transition">
|
|
||||||
Batal
|
|
||||||
</button>
|
|
||||||
<button type="submit" id="uploadSubmitBtn"
|
|
||||||
class="px-4 py-2 text-sm font-semibold text-white bg-slate-800 hover:bg-slate-700 rounded-lg transition shadow-sm disabled:opacity-50 disabled:cursor-not-allowed" disabled>
|
|
||||||
<span id="uploadBtnText">Upload & Proses</span>
|
|
||||||
<span id="uploadBtnLoading" class="hidden inline-flex items-center">
|
|
||||||
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
||||||
</svg>
|
|
||||||
Mengupload...
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('script')
|
@section('script')
|
||||||
|
|
@ -495,73 +408,6 @@ function stopPolling() {
|
||||||
btn.classList.remove('opacity-50', 'cursor-not-allowed');
|
btn.classList.remove('opacity-50', 'cursor-not-allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
function openUploadModal(userId, userName) {
|
|
||||||
const form = document.getElementById('uploadVideoForm');
|
|
||||||
form.action = `/face-approval/${userId}/upload-video`;
|
|
||||||
document.getElementById('uploadUserName').textContent = userName;
|
|
||||||
clearFile();
|
|
||||||
const overlay = document.getElementById('uploadVideoOverlay');
|
|
||||||
overlay.classList.remove('hidden');
|
|
||||||
overlay.classList.add('flex');
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeUploadModal() {
|
|
||||||
const overlay = document.getElementById('uploadVideoOverlay');
|
|
||||||
overlay.classList.add('hidden');
|
|
||||||
overlay.classList.remove('flex');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop zone & file input
|
|
||||||
const dropZone = document.getElementById('dropZone');
|
|
||||||
const fileInput = document.getElementById('videoFileInput');
|
|
||||||
|
|
||||||
dropZone.addEventListener('click', () => fileInput.click());
|
|
||||||
|
|
||||||
dropZone.addEventListener('dragover', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
dropZone.classList.add('border-primary', 'bg-primary/5');
|
|
||||||
});
|
|
||||||
|
|
||||||
dropZone.addEventListener('dragleave', () => {
|
|
||||||
dropZone.classList.remove('border-primary', 'bg-primary/5');
|
|
||||||
});
|
|
||||||
|
|
||||||
dropZone.addEventListener('drop', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
dropZone.classList.remove('border-primary', 'bg-primary/5');
|
|
||||||
if (e.dataTransfer.files.length > 0) {
|
|
||||||
fileInput.files = e.dataTransfer.files;
|
|
||||||
showFileInfo(e.dataTransfer.files[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
fileInput.addEventListener('change', (e) => {
|
|
||||||
if (e.target.files.length > 0) {
|
|
||||||
showFileInfo(e.target.files[0]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function showFileInfo(file) {
|
|
||||||
const sizeMB = (file.size / 1024 / 1024).toFixed(2);
|
|
||||||
document.getElementById('fileName').textContent = file.name;
|
|
||||||
document.getElementById('fileSize').textContent = sizeMB + ' MB';
|
|
||||||
document.getElementById('fileInfo').classList.remove('hidden');
|
|
||||||
dropZone.classList.add('hidden');
|
|
||||||
document.getElementById('uploadSubmitBtn').disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearFile() {
|
|
||||||
fileInput.value = '';
|
|
||||||
document.getElementById('fileInfo').classList.add('hidden');
|
|
||||||
dropZone.classList.remove('hidden');
|
|
||||||
document.getElementById('uploadSubmitBtn').disabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('uploadVideoForm').addEventListener('submit', function() {
|
|
||||||
const btn = document.getElementById('uploadSubmitBtn');
|
|
||||||
btn.disabled = true;
|
|
||||||
document.getElementById('uploadBtnText').classList.add('hidden');
|
|
||||||
document.getElementById('uploadBtnLoading').classList.remove('hidden');
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
||||||
|
|
@ -33,18 +33,24 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<div class="flex flex-col md:flex-row justify-between md:items-center gap-4 mb-4">
|
<div class="flex flex-col lg:flex-row lg:items-center justify-between gap-3 mb-4">
|
||||||
<label class="block text-sm font-semibold text-slate-700">Pilih Pegawai</label>
|
<label class="block text-sm font-semibold text-slate-700 shrink-0">Pilih Pegawai</label>
|
||||||
|
|
||||||
<div class="flex gap-2">
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
<x-filter-select id="filter-kantor" class="py-1.5 text-xs">
|
<x-search-input
|
||||||
|
id="search-pegawai"
|
||||||
|
placeholder="Cari nama karyawan..."
|
||||||
|
class="!w-48 lg:!w-48"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<x-filter-select id="filter-kantor" class="py-1.5 text-xs w-36">
|
||||||
<option value="">Semua Kantor</option>
|
<option value="">Semua Kantor</option>
|
||||||
@foreach($kantor as $k)
|
@foreach($kantor as $k)
|
||||||
<option value="{{ $k->id_kantor }}">{{ $k->nama_kantor }}</option>
|
<option value="{{ $k->id_kantor }}">{{ $k->nama_kantor }}</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</x-filter-select>
|
</x-filter-select>
|
||||||
|
|
||||||
<x-filter-select id="filter-divisi" class="py-1.5 text-xs">
|
<x-filter-select id="filter-divisi" class="py-1.5 text-xs w-36">
|
||||||
<option value="">Semua Divisi</option>
|
<option value="">Semua Divisi</option>
|
||||||
@foreach($divisi as $d)
|
@foreach($divisi as $d)
|
||||||
<option value="{{ $d->id_divisi }}">{{ $d->nama_divisi }}</option>
|
<option value="{{ $d->id_divisi }}">{{ $d->nama_divisi }}</option>
|
||||||
|
|
@ -223,6 +229,7 @@ class="px-6 py-2 text-sm bg-primary text-white rounded-xl hover:bg-primary/90 fo
|
||||||
const selectedCountEl = document.getElementById('selectedCount');
|
const selectedCountEl = document.getElementById('selectedCount');
|
||||||
const filterKantor = document.getElementById('filter-kantor');
|
const filterKantor = document.getElementById('filter-kantor');
|
||||||
const filterDivisi = document.getElementById('filter-divisi');
|
const filterDivisi = document.getElementById('filter-divisi');
|
||||||
|
const searchPegawai = document.getElementById('search-pegawai');
|
||||||
|
|
||||||
if (!selectAllCheckbox) return;
|
if (!selectAllCheckbox) return;
|
||||||
|
|
||||||
|
|
@ -234,27 +241,47 @@ function updateCount() {
|
||||||
selectedCountEl.textContent = count;
|
selectedCountEl.textContent = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateSelectAllState() {
|
||||||
|
const visibleCheckboxes = document.querySelectorAll('.pegawai-item:not(.hidden) .user-checkbox');
|
||||||
|
if (visibleCheckboxes.length === 0) {
|
||||||
|
selectAllCheckbox.checked = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const allChecked = Array.from(visibleCheckboxes).every(cb => cb.checked);
|
||||||
|
selectAllCheckbox.checked = allChecked;
|
||||||
|
}
|
||||||
|
|
||||||
function filterPegawai() {
|
function filterPegawai() {
|
||||||
const kantorId = filterKantor.value;
|
const kantorId = filterKantor.value;
|
||||||
const divisiId = filterDivisi.value;
|
const divisiId = filterDivisi.value;
|
||||||
|
const searchQuery = searchPegawai ? searchPegawai.value.toLowerCase().trim() : '';
|
||||||
let visibleCount = 0;
|
let visibleCount = 0;
|
||||||
|
|
||||||
pegawaiItems.forEach(item => {
|
pegawaiItems.forEach(item => {
|
||||||
const itemKantor = item.getAttribute('data-kantor');
|
const itemKantor = item.getAttribute('data-kantor');
|
||||||
const itemDivisi = item.getAttribute('data-divisi');
|
const itemDivisi = item.getAttribute('data-divisi');
|
||||||
|
|
||||||
|
const nameEl = item.querySelector('.font-medium');
|
||||||
|
const nameText = nameEl ? nameEl.textContent.toLowerCase() : '';
|
||||||
|
|
||||||
let show = true;
|
let show = true;
|
||||||
if (kantorId && itemKantor !== kantorId) show = false;
|
if (kantorId && itemKantor !== kantorId) show = false;
|
||||||
if (divisiId && itemDivisi !== divisiId) show = false;
|
if (divisiId && itemDivisi !== divisiId) show = false;
|
||||||
|
if (searchQuery && !nameText.includes(searchQuery)) show = false;
|
||||||
|
|
||||||
if (show) {
|
if (show) {
|
||||||
item.classList.remove('hidden');
|
item.classList.remove('hidden');
|
||||||
visibleCount++;
|
visibleCount++;
|
||||||
} else {
|
} else {
|
||||||
item.classList.add('hidden');
|
item.classList.add('hidden');
|
||||||
|
const cb = item.querySelector('.user-checkbox');
|
||||||
|
if (cb) cb.checked = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateSelectAllState();
|
||||||
|
updateCount();
|
||||||
|
|
||||||
if (visibleCount === 0) {
|
if (visibleCount === 0) {
|
||||||
pegawaiContainer.classList.add('hidden');
|
pegawaiContainer.classList.add('hidden');
|
||||||
emptyState.classList.remove('hidden');
|
emptyState.classList.remove('hidden');
|
||||||
|
|
@ -272,10 +299,12 @@ function filterPegawai() {
|
||||||
|
|
||||||
if(filterKantor) filterKantor.addEventListener('change', filterPegawai);
|
if(filterKantor) filterKantor.addEventListener('change', filterPegawai);
|
||||||
if(filterDivisi) filterDivisi.addEventListener('change', filterPegawai);
|
if(filterDivisi) filterDivisi.addEventListener('change', filterPegawai);
|
||||||
|
if(searchPegawai) searchPegawai.addEventListener('input', filterPegawai);
|
||||||
|
|
||||||
if(pegawaiContainer) {
|
if(pegawaiContainer) {
|
||||||
pegawaiContainer.addEventListener('change', function (e) {
|
pegawaiContainer.addEventListener('change', function (e) {
|
||||||
if (e.target.classList.contains('user-checkbox')) {
|
if (e.target.classList.contains('user-checkbox')) {
|
||||||
|
updateSelectAllState();
|
||||||
updateCount();
|
updateCount();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<meta name="app-url" content="{{ config('app.url') }}">
|
||||||
|
|
||||||
|
<link rel="icon" type="image/png" href="{{ asset('favicon.png') }}">
|
||||||
|
|
||||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
<title>{{ config('app.name', 'Laravel') }}</title>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -232,7 +232,7 @@ class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
{{-- Lembur & Poin – HRD only --}}
|
{{-- Pengajuan Lembur – HRD only --}}
|
||||||
@if($isHrd)
|
@if($isHrd)
|
||||||
<a href="{{ route('lembur.index') }}"
|
<a href="{{ route('lembur.index') }}"
|
||||||
class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group {{ request()->routeIs('lembur.*') ? 'bg-[#130F26] text-white shadow-lg shadow-[#130F26]/30' : 'text-slate-500 hover:bg-slate-50 hover:text-slate-900' }}">
|
class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group {{ request()->routeIs('lembur.*') ? 'bg-[#130F26] text-white shadow-lg shadow-[#130F26]/30' : 'text-slate-500 hover:bg-slate-50 hover:text-slate-900' }}">
|
||||||
|
|
@ -242,7 +242,7 @@ class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group
|
||||||
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z">
|
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z">
|
||||||
</path>
|
</path>
|
||||||
</svg>
|
</svg>
|
||||||
<span class="flex-1 font-semibold text-sm">Lembur & Poin</span>
|
<span class="flex-1 font-semibold text-sm">Pengajuan Lembur</span>
|
||||||
@php
|
@php
|
||||||
$lemburQuery = \App\Models\Lembur::where('id_status', \App\Enums\StatusPengajuan::PENDING);
|
$lemburQuery = \App\Models\Lembur::where('id_status', \App\Enums\StatusPengajuan::PENDING);
|
||||||
if (!$isGlobalAdmin) { $lemburQuery->whereHas('user', fn($q) => $q->where('id_kantor', $userKantor)); }
|
if (!$isGlobalAdmin) { $lemburQuery->whereHas('user', fn($q) => $q->where('id_kantor', $userKantor)); }
|
||||||
|
|
@ -290,7 +290,7 @@ class="flex items-center px-4 py-3 rounded-xl transition-all duration-200 group
|
||||||
|
|
||||||
{{-- ════════════════════════════════ SEKSI: DATA MASTER (HRD only) ════════════════════════════════ --}}
|
{{-- ════════════════════════════════ SEKSI: DATA MASTER (HRD only) ════════════════════════════════ --}}
|
||||||
@if($isHrd)
|
@if($isHrd)
|
||||||
<div x-data="{ open: {{ request()->routeIs('divisi.*', 'jabatan.*', 'kantor.*', 'role.*', 'shift.*', 'hari-libur.*', 'cuti.*') ? 'true' : 'false' }} }"
|
<div x-data="{ open: {{ request()->routeIs('divisi.*', 'jabatan.*', 'kantor.*', 'shift.*', 'hari-libur.*', 'cuti.*') ? 'true' : 'false' }} }"
|
||||||
class="space-y-1">
|
class="space-y-1">
|
||||||
|
|
||||||
<button @click="open = !open"
|
<button @click="open = !open"
|
||||||
|
|
@ -331,10 +331,7 @@ class="block py-2.5 px-4 text-sm rounded-lg transition-all {{ request()->routeIs
|
||||||
Data Jabatan
|
Data Jabatan
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('role.index') }}"
|
|
||||||
class="block py-2.5 px-4 text-sm rounded-lg transition-all {{ request()->routeIs('role.*') ? 'bg-slate-100 text-[#130F26] font-bold border-l-4 border-[#130F26]' : 'text-slate-500 hover:text-slate-900 hover:bg-slate-50' }}">
|
|
||||||
Data Role
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="{{ route('shift.index') }}"
|
<a href="{{ route('shift.index') }}"
|
||||||
class="block py-2.5 px-4 text-sm rounded-lg transition-all {{ request()->routeIs('shift.*') ? 'bg-slate-100 text-[#130F26] font-bold border-l-4 border-[#130F26]' : 'text-slate-500 hover:text-slate-900 hover:bg-slate-50' }}">
|
class="block py-2.5 px-4 text-sm rounded-lg transition-all {{ request()->routeIs('shift.*') ? 'bg-slate-100 text-[#130F26] font-bold border-l-4 border-[#130F26]' : 'text-slate-500 hover:text-slate-900 hover:bg-slate-50' }}">
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,12 @@
|
||||||
<div class="space-y-5">
|
<div class="space-y-5">
|
||||||
<x-input label="Judul Pengumuman" name="judul" placeholder="Contoh: Libur Hari Raya" required />
|
<x-input label="Judul Pengumuman" name="judul" placeholder="Contoh: Libur Hari Raya" required />
|
||||||
|
|
||||||
<x-date-input label="Tanggal Tayang" name="tanggal" required />
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-slate-700 mb-1">Tanggal Tayang</label>
|
||||||
|
<div class="px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-lg text-sm text-slate-700">
|
||||||
|
{{ now()->translatedFormat('l, d F Y') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<x-textarea label="Isi Pengumuman" name="isi" placeholder="Tuliskan detail pengumuman di sini..."
|
<x-textarea label="Isi Pengumuman" name="isi" placeholder="Tuliskan detail pengumuman di sini..."
|
||||||
rows="5" required />
|
rows="5" required />
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,12 @@
|
||||||
<x-input label="Judul Pengumuman" name="judul" value="{{ old('judul', $pengumuman->judul) }}"
|
<x-input label="Judul Pengumuman" name="judul" value="{{ old('judul', $pengumuman->judul) }}"
|
||||||
placeholder="Contoh: Libur Hari Raya" required />
|
placeholder="Contoh: Libur Hari Raya" required />
|
||||||
|
|
||||||
<x-date-input label="Tanggal Tayang" name="tanggal"
|
<div>
|
||||||
value="{{ old('tanggal', $pengumuman->tanggal->format('Y-m-d')) }}" required />
|
<label class="block text-sm font-medium text-slate-700 mb-1">Tanggal Tayang</label>
|
||||||
|
<div class="px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-lg text-sm text-slate-700">
|
||||||
|
{{ $pengumuman->tanggal->translatedFormat('l, d F Y') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<x-textarea label="Isi Pengumuman" name="isi" value="{{ old('isi', $pengumuman->isi) }}"
|
<x-textarea label="Isi Pengumuman" name="isi" value="{{ old('isi', $pengumuman->isi) }}"
|
||||||
placeholder="Tuliskan detail pengumuman di sini..." rows="5" required />
|
placeholder="Tuliskan detail pengumuman di sini..." rows="5" required />
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,12 @@
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<x-page-header title="Daftar Pengumuman" subtitle="Kelola informasi untuk karyawan">
|
<x-page-header title="Daftar Pengumuman" subtitle="Kelola informasi untuk karyawan">
|
||||||
|
<x-button type="button" variant="danger" id="bulk-delete-btn" onclick="confirmBulkDelete()" class="hidden items-center gap-2">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||||
|
</svg>
|
||||||
|
Hapus Terpilih (<span id="selected-count">0</span>)
|
||||||
|
</x-button>
|
||||||
<x-button href="{{ route('pengumuman.create') }}" class="flex items-center gap-2">
|
<x-button href="{{ route('pengumuman.create') }}" class="flex items-center gap-2">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
||||||
|
|
@ -14,65 +20,165 @@
|
||||||
</x-page-header>
|
</x-page-header>
|
||||||
|
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<form id="bulk-delete-form" action="{{ route('pengumuman.bulkDelete') }}" method="POST">
|
||||||
<x-table>
|
@csrf
|
||||||
<x-slot:header>
|
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
<th class="px-6 py-5 text-xs font-bold text-slate-500 uppercase tracking-wider text-left w-14">No</th>
|
<x-table>
|
||||||
<th class="px-6 py-5 text-xs font-bold text-slate-500 uppercase tracking-wider text-left">Judul</th>
|
<x-slot:header>
|
||||||
<th class="px-6 py-5 text-xs font-bold text-slate-500 uppercase tracking-wider text-left">Tanggal</th>
|
<th class="px-6 py-5 text-xs font-bold text-slate-500 uppercase tracking-wider text-left w-10">
|
||||||
<th class="px-6 py-5 text-xs font-bold text-slate-500 uppercase tracking-wider text-left">Dibuat Oleh</th>
|
<input type="checkbox" id="select-all" class="w-4 h-4 rounded border-slate-300 text-[#130F26] focus:ring-[#130F26]">
|
||||||
<th class="px-6 py-5 text-xs font-bold text-slate-500 uppercase tracking-wider text-right">Aksi</th>
|
</th>
|
||||||
</x-slot:header>
|
<th class="px-6 py-5 text-xs font-bold text-slate-500 uppercase tracking-wider text-left w-14">No</th>
|
||||||
@forelse ($pengumuman as $item)
|
<th class="px-6 py-5 text-xs font-bold text-slate-500 uppercase tracking-wider text-left">Judul</th>
|
||||||
<tr class="hover:bg-slate-50 transition-colors">
|
<th class="px-6 py-5 text-xs font-bold text-slate-500 uppercase tracking-wider text-left">Tanggal</th>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">
|
<th class="px-6 py-5 text-xs font-bold text-slate-500 uppercase tracking-wider text-left">Dibuat Oleh</th>
|
||||||
{{ $loop->iteration + ($pengumuman->currentPage() - 1) * $pengumuman->perPage() }}
|
<th class="px-6 py-5 text-xs font-bold text-slate-500 uppercase tracking-wider text-right">Aksi</th>
|
||||||
</td>
|
</x-slot:header>
|
||||||
<td class="px-6 py-4">
|
@forelse ($pengumuman as $item)
|
||||||
<div class="text-sm font-medium text-slate-900">{{ $item->judul }}</div>
|
<tr class="hover:bg-slate-50 transition-colors">
|
||||||
<div class="text-sm text-slate-500 truncate max-w-xs">{{ Str::limit($item->isi, 50) }}</div>
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">
|
||||||
@if($item->foto || $item->lampiran)
|
<input type="checkbox" name="ids[]" value="{{ $item->id_pengumuman }}" class="pengumuman-checkbox w-4 h-4 rounded border-slate-300 text-[#130F26] focus:ring-[#130F26]">
|
||||||
<div class="flex gap-2 mt-1">
|
</td>
|
||||||
@if($item->foto)
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">
|
||||||
<span class="inline-flex items-center gap-1 text-xs text-blue-600">
|
{{ $loop->iteration + ($pengumuman->currentPage() - 1) * $pengumuman->perPage() }}
|
||||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
</td>
|
||||||
Foto
|
<td class="px-6 py-4">
|
||||||
</span>
|
<div class="text-sm font-medium text-slate-900">{{ $item->judul }}</div>
|
||||||
@endif
|
<div class="text-sm text-slate-500 truncate max-w-xs">{{ Str::limit($item->isi, 50) }}</div>
|
||||||
@if($item->lampiran)
|
@if($item->foto || $item->lampiran)
|
||||||
<span class="inline-flex items-center gap-1 text-xs text-green-600">
|
<div class="flex gap-2 mt-1">
|
||||||
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"></path></svg>
|
@if($item->foto)
|
||||||
Lampiran
|
<span class="inline-flex items-center gap-1 text-xs text-blue-600">
|
||||||
</span>
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
||||||
@endif
|
Foto
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
@if($item->lampiran)
|
||||||
|
<span class="inline-flex items-center gap-1 text-xs text-green-600">
|
||||||
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"></path></svg>
|
||||||
|
Lampiran
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">
|
||||||
|
{{ $item->tanggal ? $item->tanggal->translatedFormat('d M Y') : '-' }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">
|
||||||
|
{{ $item->pembuat->nama_lengkap ?? 'System' }}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<x-button-edit href="{{ route('pengumuman.edit', $item->id_pengumuman) }}" />
|
||||||
|
<x-delete-button :id="$item->id_pengumuman" />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
</td>
|
||||||
</td>
|
</tr>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">
|
@empty
|
||||||
{{ $item->tanggal ? $item->tanggal->translatedFormat('d M Y') : '-' }}
|
<x-empty-state colspan="6" message="Belum ada pengumuman" hint="Silakan buat pengumuman baru untuk karyawan." />
|
||||||
</td>
|
@endforelse
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500">
|
</x-table>
|
||||||
{{ $item->pembuat->nama_lengkap ?? 'System' }}
|
</div>
|
||||||
</td>
|
</form>
|
||||||
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
||||||
<div class="flex justify-end gap-2">
|
|
||||||
<x-button-edit href="{{ route('pengumuman.edit', $item->id_pengumuman) }}" />
|
|
||||||
|
|
||||||
<form id="delete-form-{{ $item->id_pengumuman }}"
|
|
||||||
action="{{ route('pengumuman.destroy', $item->id_pengumuman) }}" method="POST" class="hidden">
|
|
||||||
@csrf
|
|
||||||
@method('DELETE')
|
|
||||||
</form>
|
|
||||||
<x-delete-button :id="$item->id_pengumuman" />
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@empty
|
|
||||||
<x-empty-state colspan="5" message="Belum ada pengumuman" hint="Silakan buat pengumuman baru untuk karyawan." />
|
|
||||||
@endforelse
|
|
||||||
</x-table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<x-pagination :paginator="$pengumuman" />
|
<x-pagination :paginator="$pengumuman" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- Form terpisah untuk hapus per baris guna menghindari nested form --}}
|
||||||
|
@foreach ($pengumuman as $item)
|
||||||
|
<form id="delete-form-{{ $item->id_pengumuman }}"
|
||||||
|
action="{{ route('pengumuman.destroy', $item->id_pengumuman) }}" method="POST" class="hidden">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
</form>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('script')
|
||||||
|
<script>
|
||||||
|
document.addEventListener('turbo:load', function() {
|
||||||
|
const selectAllCheckbox = document.getElementById('select-all');
|
||||||
|
const checkboxes = document.querySelectorAll('.pengumuman-checkbox');
|
||||||
|
const bulkDeleteBtn = document.getElementById('bulk-delete-btn');
|
||||||
|
const selectedCount = document.getElementById('selected-count');
|
||||||
|
|
||||||
|
if (!selectAllCheckbox || !bulkDeleteBtn) return;
|
||||||
|
|
||||||
|
function updateBulkDeleteButton() {
|
||||||
|
const checkedCount = document.querySelectorAll('.pengumuman-checkbox:checked').length;
|
||||||
|
if (checkedCount > 0) {
|
||||||
|
selectedCount.textContent = checkedCount;
|
||||||
|
bulkDeleteBtn.classList.remove('hidden');
|
||||||
|
bulkDeleteBtn.classList.add('flex');
|
||||||
|
} else {
|
||||||
|
bulkDeleteBtn.classList.remove('flex');
|
||||||
|
bulkDeleteBtn.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectAllCheckbox.addEventListener('change', function() {
|
||||||
|
checkboxes.forEach(cb => {
|
||||||
|
cb.checked = selectAllCheckbox.checked;
|
||||||
|
});
|
||||||
|
updateBulkDeleteButton();
|
||||||
|
});
|
||||||
|
|
||||||
|
checkboxes.forEach(cb => {
|
||||||
|
cb.addEventListener('change', function() {
|
||||||
|
const allChecked = document.querySelectorAll('.pengumuman-checkbox:checked').length === checkboxes.length;
|
||||||
|
selectAllCheckbox.checked = allChecked;
|
||||||
|
updateBulkDeleteButton();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function confirmBulkDelete() {
|
||||||
|
const checkedBoxes = document.querySelectorAll('.pengumuman-checkbox:checked');
|
||||||
|
if (checkedBoxes.length === 0) return;
|
||||||
|
|
||||||
|
Swal.fire({
|
||||||
|
html: `
|
||||||
|
<div class="flex flex-col items-center py-2">
|
||||||
|
<div class="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mb-4 ring-8 ring-red-50">
|
||||||
|
<svg class="w-8 h-8 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-xl font-extrabold text-slate-800 tracking-tight">Hapus Pengumuman?</h2>
|
||||||
|
<p class="text-sm text-slate-500 text-center px-4 mt-2 leading-relaxed">
|
||||||
|
Sebanyak <b>${checkedBoxes.length}</b> pengumuman yang dipilih akan dihapus secara permanen dari sistem.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Ya, Hapus Semua',
|
||||||
|
cancelButtonText: 'Batalkan',
|
||||||
|
buttonsStyling: false,
|
||||||
|
customClass: {
|
||||||
|
confirmButton: 'px-6 py-2.5 bg-red-600 text-white rounded-xl font-bold hover:bg-red-700 transition shadow-lg shadow-red-200 mr-2',
|
||||||
|
cancelButton: 'px-6 py-2.5 bg-slate-100 text-slate-600 rounded-xl font-bold hover:bg-slate-200 transition'
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
reverseButtons: true
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
Swal.fire({
|
||||||
|
html: `
|
||||||
|
<div class="flex flex-col items-center py-6">
|
||||||
|
<div class="animate-spin rounded-full h-10 w-10 border-4 border-slate-200 border-t-red-600 mb-4"></div>
|
||||||
|
<p class="text-sm font-bold text-slate-700">Menghapus Pengumuman...</p>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
width: '250px',
|
||||||
|
showConfirmButton: false,
|
||||||
|
allowOutsideClick: false
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('bulk-delete-form').submit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@endsection
|
@endsection
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('title', 'Input Presensi Manual')
|
@section('title', 'Input Presensi Manual')
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<a href="{{ route('presensi.index') }}" class="p-2 bg-white border border-slate-200 rounded-xl hover:bg-slate-50 transition-colors text-slate-500">
|
<a href="{{ route('presensi.index') }}" class="p-2 bg-white border border-slate-200 rounded-xl hover:bg-slate-50 transition-colors text-slate-500">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||||
</a>
|
</a>
|
||||||
<x-page-header title="Input Presensi Manual" subtitle="Catat kehadiran pegawai secara manual (Admin)" />
|
<x-page-header title="Input Presensi Manual" subtitle="Catat kehadiran pegawai secara manual (HRD/Admin)" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(session('error'))
|
@if(session('error'))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('title', 'Koreksi Presensi')
|
@section('title', 'Koreksi Presensi')
|
||||||
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<a href="{{ route('presensi.index', ['tanggal' => $presensi->tanggal]) }}" class="p-2 bg-white border border-slate-200 rounded-xl hover:bg-slate-50 transition-colors text-slate-500">
|
<a href="{{ route('presensi.index', ['tanggal' => $presensi->tanggal]) }}" class="p-2 bg-white border border-slate-200 rounded-xl hover:bg-slate-50 transition-colors text-slate-500">
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path></svg>
|
||||||
</a>
|
</a>
|
||||||
<x-page-header title="Koreksi Presensi" subtitle="Ubah data kehadiran pegawai secara manual (Admin)" />
|
<x-page-header title="Koreksi Presensi" subtitle="Ubah data kehadiran pegawai secara manual (HRD/Admin)" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(session('error'))
|
@if(session('error'))
|
||||||
|
|
@ -67,7 +67,7 @@
|
||||||
|
|
||||||
<div class="space-y-1 md:col-span-2 mt-4">
|
<div class="space-y-1 md:col-span-2 mt-4">
|
||||||
<label class="block text-sm font-semibold text-slate-700">Catatan Koreksi (Wajib)</label>
|
<label class="block text-sm font-semibold text-slate-700">Catatan Koreksi (Wajib)</label>
|
||||||
<textarea name="alasan_telat" rows="2" required class="w-full rounded-xl border-slate-300 shadow-sm focus:border-primary focus:ring focus:ring-primary/20 transition-all text-sm" placeholder="Contoh: Koreksi jam masuk karena admin salah input opsi cuti, dll.">{{ old('alasan_telat', $presensi->alasan_telat) }}</textarea>
|
<textarea name="alasan_telat" rows="2" required class="w-full rounded-xl border-slate-300 shadow-sm focus:border-primary focus:ring focus:ring-primary/20 transition-all text-sm" placeholder="Contoh: Koreksi jam masuk karena HRD salah input opsi cuti, dll.">{{ old('alasan_telat', $presensi->alasan_telat) }}</textarea>
|
||||||
<p class="text-xs text-slate-500 mt-1">Status Validasi akan otomatis diset menjadi <strong>Disetujui</strong> setelah dikoreksi.</p>
|
<p class="text-xs text-slate-500 mt-1">Status Validasi akan otomatis diset menjadi <strong>Disetujui</strong> setelah dikoreksi.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,6 +144,10 @@ class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-bold
|
||||||
{{ substr($p->jam_jadwal_masuk, 0, 5) }}*
|
{{ substr($p->jam_jadwal_masuk, 0, 5) }}*
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@elseif(!$p->jam_masuk)
|
||||||
|
<span class="font-mono font-bold text-slate-400">
|
||||||
|
--:--
|
||||||
|
</span>
|
||||||
@else
|
@else
|
||||||
<span class="font-mono font-bold {{ $p->jam_masuk > $p->jam_jadwal_masuk ? 'text-red-600' : 'text-emerald-600' }}">
|
<span class="font-mono font-bold {{ $p->jam_masuk > $p->jam_jadwal_masuk ? 'text-red-600' : 'text-emerald-600' }}">
|
||||||
{{ \Carbon\Carbon::parse($p->jam_masuk)->format('H:i') }}
|
{{ \Carbon\Carbon::parse($p->jam_masuk)->format('H:i') }}
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,7 @@ class="w-full pl-10 pr-4 py-2 border border-slate-300 rounded-lg focus:outline-n
|
||||||
</svg>
|
</svg>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<button x-data @click="$dispatch('open-modal', 'create-role')"
|
|
||||||
class="flex items-center gap-2 px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition text-sm font-medium shadow-lg shadow-primary/30">
|
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
|
||||||
</svg>
|
|
||||||
Tambah
|
|
||||||
</button>
|
|
||||||
</x-page-header>
|
</x-page-header>
|
||||||
|
|
||||||
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
<div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
|
|
@ -73,39 +67,13 @@ class="flex items-center gap-2 px-4 py-2 bg-primary text-white rounded-lg hover:
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<x-empty-state colspan="4" message="Belum ada data role" hint="Silakan tambahkan role baru." />
|
<x-empty-state colspan="4" message="Belum ada data role" hint="Role akan tersedia setelah seeder dijalankan." />
|
||||||
@endforelse
|
@endforelse
|
||||||
</x-table>
|
</x-table>
|
||||||
<x-pagination :paginator="$roles" />
|
<x-pagination :paginator="$roles" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-modal name="create-role" title="Tambah Role Baru">
|
|
||||||
<form action="{{ route('role.store') }}" method="POST">
|
|
||||||
@csrf
|
|
||||||
<div class="mb-4">
|
|
||||||
<x-input label="Nama Role" name="nama_role" placeholder="Contoh: Administrator" required />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-4">
|
|
||||||
<label class="block text-sm font-semibold text-slate-700 mb-2">Hak Akses (Permissions)</label>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 p-4 bg-slate-50 rounded-xl border border-slate-200 max-h-60 overflow-y-auto custom-scrollbar">
|
|
||||||
@foreach($allPermissions as $permission)
|
|
||||||
<div class="p-2 hover:bg-white rounded-lg transition-colors">
|
|
||||||
<x-checkbox name="id_permissions[]" value="{{ $permission->id_permission }}" label="{{ $permission->nama_permission }}" />
|
|
||||||
</div>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex justify-end gap-3 pt-4 border-t border-slate-100 mt-2">
|
|
||||||
<button type="button" x-data @click="$dispatch('close-modal', 'create-role')"
|
|
||||||
class="px-4 py-2 text-sm border rounded-xl hover:bg-slate-50">Batal</button>
|
|
||||||
<button type="submit"
|
|
||||||
class="px-4 py-2 text-sm bg-primary text-white rounded-xl hover:bg-primary/90">Simpan</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</x-modal>
|
|
||||||
|
|
||||||
<x-modal name="edit-role" title="Edit Role">
|
<x-modal name="edit-role" title="Edit Role">
|
||||||
<form id="editForm" method="POST">
|
<form id="editForm" method="POST">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
use App\Http\Controllers\DashboardController;
|
use App\Http\Controllers\DashboardController;
|
||||||
use App\Http\Controllers\PegawaiController;
|
use App\Http\Controllers\PegawaiController;
|
||||||
use App\Http\Controllers\DivisiController;
|
use App\Http\Controllers\DivisiController;
|
||||||
|
|
@ -84,6 +85,7 @@
|
||||||
Route::post('/presensi/{id}/approve', [PresensiController::class, 'approve'])->name('presensi.approve');
|
Route::post('/presensi/{id}/approve', [PresensiController::class, 'approve'])->name('presensi.approve');
|
||||||
Route::post('/presensi/{id}/reject', [PresensiController::class, 'reject'])->name('presensi.reject');
|
Route::post('/presensi/{id}/reject', [PresensiController::class, 'reject'])->name('presensi.reject');
|
||||||
|
|
||||||
|
Route::post('/pengumuman/bulk-delete', [PengumumanController::class, 'bulkDelete'])->name('pengumuman.bulkDelete');
|
||||||
Route::resource('pengumuman', PengumumanController::class);
|
Route::resource('pengumuman', PengumumanController::class);
|
||||||
|
|
||||||
Route::resource('lembur', LemburController::class)->only(['index', 'update', 'create', 'store']);
|
Route::resource('lembur', LemburController::class)->only(['index', 'update', 'create', 'store']);
|
||||||
|
|
@ -119,13 +121,11 @@
|
||||||
Route::get('/face-approval', [FaceApprovalController::class, 'index'])->name('face.index');
|
Route::get('/face-approval', [FaceApprovalController::class, 'index'])->name('face.index');
|
||||||
Route::post('/face-approval/reextract-all', [FaceApprovalController::class, 'reextractAll'])->name('face.reextract_all');
|
Route::post('/face-approval/reextract-all', [FaceApprovalController::class, 'reextractAll'])->name('face.reextract_all');
|
||||||
Route::get('/face-approval/training-status', [FaceApprovalController::class, 'trainingStatus'])->name('face.training_status');
|
Route::get('/face-approval/training-status', [FaceApprovalController::class, 'trainingStatus'])->name('face.training_status');
|
||||||
Route::post('/face-approval/migrate-embeddings', [FaceApprovalController::class, 'migrateEmbeddings'])->name('face.migrate_embeddings');
|
|
||||||
Route::get('/face-approval/photo/{userId}/{pose}', [FaceApprovalController::class, 'showPhoto'])->name('face.photo');
|
Route::get('/face-approval/photo/{userId}/{pose}', [FaceApprovalController::class, 'showPhoto'])->name('face.photo');
|
||||||
Route::get('/face-approval/frame/{userId}/{frameIndex}', [FaceApprovalController::class, 'showFrame'])->name('face.frame');
|
Route::get('/face-approval/frame/{userId}/{frameIndex}', [FaceApprovalController::class, 'showFrame'])->name('face.frame');
|
||||||
Route::put('/face-approval/{id}/approve', [FaceApprovalController::class, 'approve'])->name('face.approve');
|
Route::put('/face-approval/{id}/approve', [FaceApprovalController::class, 'approve'])->name('face.approve');
|
||||||
Route::delete('/face-approval/{id}/reject', [FaceApprovalController::class, 'reject'])->name('face.reject');
|
Route::delete('/face-approval/{id}/reject', [FaceApprovalController::class, 'reject'])->name('face.reject');
|
||||||
Route::delete('/face-approval/{id}/reset', [FaceApprovalController::class, 'reset'])->name('face.reset');
|
Route::delete('/face-approval/{id}/reset', [FaceApprovalController::class, 'reset'])->name('face.reset');
|
||||||
Route::post('/face-approval/{id}/upload-video', [FaceApprovalController::class, 'uploadVideo'])->name('face.upload_video');
|
|
||||||
|
|
||||||
Route::resource('role', RoleController::class)->except(['create', 'edit']);
|
Route::resource('role', RoleController::class)->except(['create', 'edit']);
|
||||||
});
|
});
|
||||||
|
|
@ -160,3 +160,24 @@
|
||||||
|
|
||||||
return response()->download($zipFilePath)->deleteFileAfterSend(true);
|
return response()->download($zipFilePath)->deleteFileAfterSend(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Endpoint untuk external cron service (cron-job.org)
|
||||||
|
Route::get('/cron/queue', function () {
|
||||||
|
$key = request('key');
|
||||||
|
|
||||||
|
if (!$key || $key !== config('app.cron_secret_key')) {
|
||||||
|
return response()->json(['error' => 'Unauthorized'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$exitCode = Artisan::call('queue:work', [
|
||||||
|
'--once' => true,
|
||||||
|
'--timeout' => 60,
|
||||||
|
'--tries' => 3,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'ok',
|
||||||
|
'exit_code' => $exitCode,
|
||||||
|
'time' => now()->toDateTimeString(),
|
||||||
|
]);
|
||||||
|
})->name('cron.queue');
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 484 KiB |
Loading…
Reference in New Issue