From 3738adf85aa3beace1149e2eeb7c223a080dac41 Mon Sep 17 00:00:00 2001 From: Rafiafrzl Date: Tue, 28 Oct 2025 12:54:24 +0700 Subject: [PATCH] integration jadwal shift --- src/api/jadwal-shift.jsx | 10 +- .../component/DetailJadwalShift.jsx | 357 ++++--- .../jadwalShift/component/ListJadwalShift.jsx | 872 ++++++++++++------ 3 files changed, 829 insertions(+), 410 deletions(-) diff --git a/src/api/jadwal-shift.jsx b/src/api/jadwal-shift.jsx index ce221b3..9373fca 100644 --- a/src/api/jadwal-shift.jsx +++ b/src/api/jadwal-shift.jsx @@ -3,7 +3,7 @@ import { SendRequest } from '../components/Global/ApiRequest'; const getAllJadwalShift = async (queryParams) => { const response = await SendRequest({ method: 'get', - prefix: `jadwal-shift?${queryParams.toString()}`, + prefix: `user-schedule?${queryParams.toString()}`, }); return response.data; @@ -12,7 +12,7 @@ const getAllJadwalShift = async (queryParams) => { const getJadwalShiftById = async (id) => { const response = await SendRequest({ method: 'get', - prefix: `jadwal-shift/${id}`, + prefix: `user-schedule/${id}`, }); return response.data; @@ -21,7 +21,7 @@ const getJadwalShiftById = async (id) => { const createJadwalShift = async (queryParams) => { const response = await SendRequest({ method: 'post', - prefix: `jadwal-shift`, + prefix: `user-schedule`, params: queryParams, }); @@ -31,7 +31,7 @@ const createJadwalShift = async (queryParams) => { const updateJadwalShift = async (id, queryParams) => { const response = await SendRequest({ method: 'put', - prefix: `jadwal-shift/${id}`, + prefix: `user-schedule/${id}`, params: queryParams, }); @@ -41,7 +41,7 @@ const updateJadwalShift = async (id, queryParams) => { const deleteJadwalShift = async (id) => { const response = await SendRequest({ method: 'delete', - prefix: `jadwal-shift/${id}`, + prefix: `user-schedule/${id}`, }); return response.data; }; diff --git a/src/pages/jadwalShift/component/DetailJadwalShift.jsx b/src/pages/jadwalShift/component/DetailJadwalShift.jsx index 83d705f..300377b 100644 --- a/src/pages/jadwalShift/component/DetailJadwalShift.jsx +++ b/src/pages/jadwalShift/component/DetailJadwalShift.jsx @@ -1,173 +1,278 @@ import React, { useEffect, useState } from 'react'; -import { - Modal, - Typography, - Button, - ConfigProvider, - Form, - Select, - Spin, - Input -} from 'antd'; -import { NotifOk, NotifAlert } from '../../../components/Global/ToastNotif'; -import { updateJadwalShift, createJadwalShift } from '../../../api/jadwal-shift.jsx'; +import { Modal, Select, Typography, Button, ConfigProvider } from 'antd'; +import { NotifOk } from '../../../components/Global/ToastNotif'; +import { createJadwalShift, updateJadwalShift } from '../../../api/jadwal-shift'; +import { getAllUser } from '../../../api/user'; +import { getAllShift } from '../../../api/master-shift'; +import { validateRun } from '../../../Utils/validate'; const { Text } = Typography; const { Option } = Select; const DetailJadwalShift = (props) => { - const [form] = Form.useForm(); const [confirmLoading, setConfirmLoading] = useState(false); const [employees, setEmployees] = useState([]); - const [loadingEmployees, setLoadingEmployees] = useState(false); + const [shifts, setShifts] = useState([]); + const [loadingData, setLoadingData] = useState(false); - const isReadOnly = props.actionMode === 'preview'; + const defaultData = { + id: '', + user_id: null, + shift_id: null, + schedule_id: '', + user_phone: null, + }; + + const [formData, setFormData] = useState(defaultData); + + const handleSelectChange = (name, value) => { + const updates = { [name]: value }; + + if (name === 'user_id') { + const selectedEmployee = employees.find((emp) => emp.user_id === value); + updates.user_phone = selectedEmployee?.user_phone || '-'; + } + + setFormData({ + ...formData, + ...updates, + }); + }; const handleCancel = () => { + props.setSelectedData(null); props.setActionMode('list'); }; - const fetchEmployees = async () => { - setLoadingEmployees(true); + const fetchData = async () => { + setLoadingData(true); try { - // Data dummy untuk dropdown karyawan - const dummyEmployees = [ - { employee_id: '101', nama_employee: 'Andi Pratama' }, - { employee_id: '102', nama_employee: 'Budi Santoso' }, - { employee_id: '103', nama_employee: 'Citra Lestari' }, - { employee_id: '104', nama_employee: 'Dewi Anggraini' }, - { employee_id: '105', nama_employee: 'Eko Wahyudi' }, - { employee_id: '106', nama_employee: 'Fitriani' }, - ]; - setEmployees(dummyEmployees); + const params = new URLSearchParams({ + page: 1, + limit: 100, + }); + + const [usersResponse, shiftsResponse] = await Promise.all([ + getAllUser(params), + getAllShift(params), + ]); + + const userData = usersResponse?.data || usersResponse || []; + const shiftData = shiftsResponse?.data || shiftsResponse || []; + + setEmployees(Array.isArray(userData) ? userData : []); + setShifts(Array.isArray(shiftData) ? shiftData : []); } catch (error) { - NotifAlert({ icon: 'error', title: 'Gagal', message: 'Gagal memuat daftar karyawan.' }); + NotifOk({ + icon: 'error', + title: 'Gagal', + message: 'Gagal memuat data karyawan atau shift.', + }); } finally { - setLoadingEmployees(false); + setLoadingData(false); } }; const handleSave = async () => { + setConfirmLoading(true); + + // Daftar aturan validasi + const validationRules = [ + { field: 'user_id', label: 'Nama Karyawan', required: true }, + { field: 'shift_id', label: 'Shift', required: true }, + ]; + + if ( + validateRun(formData, validationRules, (errorMessages) => { + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: errorMessages, + }); + setConfirmLoading(false); + }) + ) + return; + try { - const values = await form.validateFields(); - let payload; - let responseMessage; + const payload = { + user_id: formData.user_id, + shift_id: formData.shift_id, + }; - setConfirmLoading(true); - if (props.actionMode === 'edit') { - payload = { ...props.selectedData, ...values }; - // await updateJadwalShift(payload.schedule_id, payload); - console.log("Updating schedule with payload:", payload); - responseMessage = 'Jadwal berhasil diperbarui.'; - } else { // 'add' mode - payload = { - employee_id: values.employee_id, - shift_name: props.selectedData.shift_name, - schedule_date: new Date().toISOString().split('T')[0], // Example date - }; - // await createJadwalShift(payload); - console.log("Creating schedule with payload:", payload); - responseMessage = 'User berhasil ditambahkan ke jadwal.'; + // Add schedule_id only if editing and it exists + if (props.actionMode === 'edit' && formData.schedule_id) { + payload.schedule_id = formData.schedule_id; } - await new Promise(resolve => setTimeout(resolve, 500)); // Simulasi API call - NotifOk({ icon: 'success', title: 'Berhasil', message: responseMessage }); - props.setActionMode('list'); // Menutup modal dan memicu refresh di parent + const response = + props.actionMode === 'edit' + ? await updateJadwalShift(formData.id, payload) + : await createJadwalShift(payload); + + if (response && (response.statusCode === 200 || response.statusCode === 201)) { + const action = props.actionMode === 'edit' ? 'diubah' : 'ditambahkan'; + + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: `Jadwal berhasil ${action}.`, + }); + + props.setActionMode('list'); + } else { + NotifOk({ + icon: 'error', + title: 'Gagal', + message: response?.message || 'Terjadi kesalahan saat menyimpan data.', + }); + } } catch (error) { - const message = error.response?.data?.message || 'Gagal memperbarui jadwal.'; - NotifAlert({ icon: 'error', title: 'Gagal', message }); + NotifOk({ + icon: 'error', + title: 'Error', + message: error.message || 'Terjadi kesalahan pada server.', + }); } finally { setConfirmLoading(false); } }; useEffect(() => { - // Hanya jalankan jika modal untuk 'edit' atau 'preview' terbuka if (props.showModal) { - fetchEmployees(); - if (props.actionMode === 'edit' || props.actionMode === 'preview') { - form.setFieldsValue({ - employee_id: props.selectedData.employee_id, - shift_name: props.selectedData.shift_name, - }); - } else if (props.actionMode === 'add') { - form.setFieldsValue({ - shift_name: props.selectedData.shift_name, - employee_id: null, // Reset employee selection - }); - } + fetchData(); } - }, [props.actionMode, props.showModal, props.selectedData, form]); + + if (props.selectedData) { + setFormData({ + id: props.selectedData.id || '', + user_id: props.selectedData.user_id || null, + shift_id: props.selectedData.shift_id || null, + schedule_id: props.selectedData.schedule_id || '', + user_phone: props.selectedData.whatsapp || props.selectedData.user_phone || null, + }); + } else { + setFormData(defaultData); + } + }, [props.showModal, props.selectedData, props.actionMode]); return ( - - {!isReadOnly && ( - - )} + + + + + {!props.readOnly && ( + + )} + , ]} > - -
- {props.actionMode === 'add' ? ( - <> - - - - - - - - ) : ( - <> - - - - - - - - )} -
-
+ {formData && ( +
+
+ Nama Karyawan + * + +
+ +
+ No. Telepon +
+ {formData.user_phone || 'Pilih karyawan terlebih dahulu'} +
+
+ +
+ Shift + * + +
+
+ )}
); }; diff --git a/src/pages/jadwalShift/component/ListJadwalShift.jsx b/src/pages/jadwalShift/component/ListJadwalShift.jsx index 57c11f3..e0249c8 100644 --- a/src/pages/jadwalShift/component/ListJadwalShift.jsx +++ b/src/pages/jadwalShift/component/ListJadwalShift.jsx @@ -1,5 +1,17 @@ import React, { memo, useState, useEffect } from 'react'; -import { Space, ConfigProvider, Button, Row, Col, Card, Input, Typography, Spin, Divider, Checkbox, Select } from 'antd'; +import { + Space, + ConfigProvider, + Button, + Row, + Col, + Card, + Input, + Typography, + Divider, + Checkbox, + Select, +} from 'antd'; import { PlusOutlined, SearchOutlined, @@ -9,32 +21,25 @@ import { } from '@ant-design/icons'; import { NotifAlert, NotifOk, NotifConfirmDialog } from '../../../components/Global/ToastNotif'; import { useNavigate } from 'react-router-dom'; -import { getAllJadwalShift, deleteJadwalShift, updateJadwalShift } from '../../../api/jadwal-shift.jsx'; +import { getAllJadwalShift, deleteJadwalShift, updateJadwalShift } from '../../../api/jadwal-shift'; +import { getAllShift } from '../../../api/master-shift'; const { Title, Text } = Typography; const ListJadwalShift = memo(function ListJadwalShift(props) { + const [trigerFilter, setTrigerFilter] = useState(false); + const defaultFilter = { criteria: '' }; + const [formDataFilter, setFormDataFilter] = useState(defaultFilter); const [groupedSchedules, setGroupedSchedules] = useState({}); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [searchValue, setSearchValue] = useState(''); - const [isEditMode, setIsEditMode] = useState(false); const [selectedSchedules, setSelectedSchedules] = useState([]); - const [editingShift, setEditingShift] = useState(null); // State for shift-specific edit mode - const [pendingChanges, setPendingChanges] = useState({}); // State for bulk shift edits - const [employeeOptions, setEmployeeOptions] = useState([]); // State for employee dropdown + const [editingShift, setEditingShift] = useState(null); + const [pendingChanges, setPendingChanges] = useState({}); + const [employeeOptions, setEmployeeOptions] = useState([]); + const [shiftOptions, setShiftOptions] = useState([]); const navigate = useNavigate(); - // Function to format timestamp without moment.js - const formatTimestamp = (timestamp) => { - const date = new Date(timestamp); - const optionsDate = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; - const optionsTime = { hour: '2-digit', minute: '2-digit', hour12: false }; - - const formattedDate = date.toLocaleDateString('id-ID', optionsDate); - const formattedTime = date.toLocaleTimeString('id-ID', optionsTime); - return `${formattedDate} pukul ${formattedTime}`; - }; - const formatRelativeTimestamp = (timestamp) => { const now = new Date(); const date = new Date(timestamp); @@ -50,97 +55,157 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { dayString = date.toLocaleDateString('id-ID', { day: 'numeric', month: 'long' }); } - const timeString = date.toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit', hour12: false }).replace('.', ':'); + const timeString = date + .toLocaleTimeString('id-ID', { hour: '2-digit', minute: '2-digit', hour12: false }) + .replace('.', ':'); return `${dayString}, ${timeString}`; }; const fetchData = async () => { setLoading(true); try { - // const params = new URLSearchParams({ ... }); - // const response = await getAllJadwalShift(params); - - // ================== START: DUMMY DATA FOR VISUAL PREVIEW ================== - // This section creates dummy schedules and users to ensure all shifts are populated. - // The actual API call is commented out for now. - - const mockSchedules = [ - { schedule_id: 1, employee_id: '101', shift_name: 'PAGI', nama_employee: 'Andi Pratama', whatsapp: '081234567890', updated_by: 'Admin Super', updated_at: '2024-05-21T10:00:00' }, - { schedule_id: 2, employee_id: '102', shift_name: 'PAGI', nama_employee: 'Budi Santoso', whatsapp: '081234567891', updated_by: 'Admin Super', updated_at: '2024-05-21T10:00:00' }, - { schedule_id: 3, employee_id: '103', shift_name: 'SIANG', nama_employee: 'Citra Lestari', whatsapp: '081234567892', updated_by: 'John Doe', updated_at: '2024-05-21T09:45:00' }, - { schedule_id: 4, employee_id: '104', shift_name: 'SIANG', nama_employee: 'Dewi Anggraini', whatsapp: '081234567893', updated_by: 'John Doe', updated_at: '2024-05-21T09:45:00' }, - { schedule_id: 5, employee_id: '105', shift_name: 'MALAM', nama_employee: 'Eko Wahyudi', whatsapp: '081234567894', updated_by: 'Jane Smith', updated_at: '2024-05-20T22:15:00' }, - { schedule_id: 6, employee_id: '106', shift_name: 'MALAM', nama_employee: 'Fitriani', whatsapp: '081234567895', updated_by: 'Jane Smith', updated_at: '2024-05-20T22:15:00' }, - ]; - - // Dummy employee data for dropdowns - const dummyEmployees = [ - { employee_id: '101', nama_employee: 'Andi Pratama' }, - { employee_id: '102', nama_employee: 'Budi Santoso' }, - { employee_id: '103', nama_employee: 'Citra Lestari' }, - { employee_id: '104', nama_employee: 'Dewi Anggraini' }, - { employee_id: '105', nama_employee: 'Eko Wahyudi' }, - { employee_id: '106', nama_employee: 'Fitriani' }, - ]; - setEmployeeOptions(dummyEmployees); - // =================== END: DUMMY DATA FOR VISUAL PREVIEW =================== - - - const grouped = mockSchedules.reduce((acc, schedule) => { - const shiftName = schedule.shift_name.toUpperCase().trim(); - if (!acc[shiftName]) { - acc[shiftName] = { users: [], lastUpdate: { user: 'N/A', timestamp: '1970-01-01T00:00:00Z' } }; - } - acc[shiftName].users.push(schedule); - - // Find the latest update timestamp for the shift - const currentUpdate = new Date(schedule.updated_at || schedule.created_at); - const lastUpdate = new Date(acc[shiftName].lastUpdate.timestamp); - if (currentUpdate > lastUpdate) { - acc[shiftName].lastUpdate = { - user: schedule.updated_by || 'N/A', - timestamp: currentUpdate.toISOString() - }; - } - return acc; - }, {}); - - const finalGrouped = { - 'PAGI': grouped['PAGI'] || { users: [], lastUpdate: { user: 'N/A', timestamp: new Date().toISOString() } }, - 'SIANG': grouped['SIANG'] || { users: [], lastUpdate: { user: 'N/A', timestamp: new Date().toISOString() } }, - 'MALAM': grouped['MALAM'] || { users: [], lastUpdate: { user: 'N/A', timestamp: new Date().toISOString() } }, + const paging = { + page: 1, + limit: 1000, }; - setGroupedSchedules(finalGrouped); + const params = new URLSearchParams({ ...paging, ...formDataFilter }); + // Fetch both schedules and shifts data + const [schedulesResponse, shiftsResponse] = await Promise.all([ + getAllJadwalShift(params), + getAllShift(params), + ]); + + // Handle nested data structure from backend + const rawData = schedulesResponse?.data || schedulesResponse || []; + const shifts = shiftsResponse?.data || shiftsResponse || []; + + setShiftOptions(shifts); + + // Parse backend response structure: [{ shift: { shift_id, shift_name, users: [...] } }] + const grouped = {}; + const allUsers = []; + + rawData.forEach((item) => { + if (item.shift && item.shift.shift_name) { + const shift = item.shift; + const shiftName = shift.shift_name.toUpperCase().trim(); + + // Initialize shift group + if (!grouped[shiftName]) { + grouped[shiftName] = { + shift_id: shift.shift_id, + users: [], + lastUpdate: { user: 'N/A', timestamp: '1970-01-01T00:00:00Z' }, + }; + } + + // Process users in this shift + if (shift.users && Array.isArray(shift.users)) { + shift.users.forEach((user) => { + const normalizedUser = { + id: user.user_schedule_id, + user_schedule_id: user.user_schedule_id, + user_id: user.user_id, + shift_id: shift.shift_id, + shift_name: shift.shift_name, + nama_employee: user.user_fullname || user.user_name || 'Unknown', + whatsapp: user.user_phone || '-', + user_fullname: user.user_fullname, + user_name: user.user_name, + user_phone: user.user_phone, + updated_at: user.updated_at, + created_at: user.created_at, + updated_by: user.updated_by, + }; + + grouped[shiftName].users.push(normalizedUser); + allUsers.push(normalizedUser); + + // Update last update timestamp + const currentUpdate = new Date( + user.updated_at || user.created_at || new Date() + ); + const lastUpdate = new Date(grouped[shiftName].lastUpdate.timestamp); + if (currentUpdate > lastUpdate) { + grouped[shiftName].lastUpdate = { + user: user.updated_by || 'N/A', + timestamp: currentUpdate.toISOString(), + }; + } + }); + } + } + }); + + setEmployeeOptions(allUsers); + + // Add empty shifts that don't have users yet + shifts.forEach((shift) => { + const shiftName = shift.shift_name.toUpperCase().trim(); + if (!grouped[shiftName]) { + grouped[shiftName] = { + shift_id: shift.shift_id, + users: [], + lastUpdate: { user: 'N/A', timestamp: new Date().toISOString() }, + }; + } + }); + + setGroupedSchedules(grouped); } catch (error) { - console.error('Error processing dummy data:', error); - NotifAlert({ // Changed to NotifAlert for consistency - icon: 'error', // Changed to error icon - title: 'Gagal Memuat Data', // Changed title - message: 'Terjadi kesalahan saat memuat data jadwal shift.', // Changed message + NotifAlert({ + icon: 'error', + title: 'Gagal Memuat Data', + message: 'Terjadi kesalahan saat memuat data jadwal shift.', }); } finally { - // Add a small delay to simulate network loading - setTimeout(() => setLoading(false), 500); + setLoading(false); } }; useEffect(() => { const token = localStorage.getItem('token'); if (token) { - fetchData(); + if (props.actionMode === 'list') { + setFormDataFilter(defaultFilter); + doFilter(); + } } else { navigate('/signin'); } - }, [searchValue, props.actionMode]); // Refetch when searchValue changes or after modal closes + }, [props.actionMode]); - const handleSearch = (value) => { - setSearchValue(value); + useEffect(() => { + if (props.actionMode === 'list') { + fetchData(); + } + }, [trigerFilter]); + + const doFilter = () => { + setTrigerFilter((prev) => !prev); + }; + + const handleSearch = () => { + setFormDataFilter({ criteria: searchValue }); + setTrigerFilter((prev) => !prev); }; const handleSearchClear = () => { setSearchValue(''); + setFormDataFilter({ criteria: '' }); + setTrigerFilter((prev) => !prev); + }; + + const showPreviewModal = (param) => { + props.setSelectedData(param); + props.setActionMode('preview'); + }; + + const showEditModal = (param = null) => { + props.setSelectedData(param); + props.setActionMode('edit'); }; const showAddModal = (param = null) => { @@ -148,28 +213,31 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { props.setActionMode('add'); }; - const handleAction = (mode, record) => { - props.setSelectedData(record); - props.setActionMode(mode); - }; - - const showDeleteDialog = (user) => { + const showDeleteDialog = (param) => { NotifConfirmDialog({ icon: 'question', title: 'Konfirmasi Hapus', - message: `Hapus jadwal untuk karyawan "${user.nama_employee}"?`, - onConfirm: () => handleDelete(user.schedule_id), + message: `Jadwal untuk karyawan "${param.nama_employee}" akan dihapus?`, + onConfirm: () => handleDelete(param.id), + onCancel: () => props.setSelectedData(null), }); }; - const handleDelete = async (schedule_id) => { - try { - await deleteJadwalShift(schedule_id); - NotifOk({ icon: 'success', title: 'Berhasil', message: 'Jadwal berhasil dihapus.' }); - fetchData(); // Refresh data - } catch (error) { - console.error("Failed to delete schedule:", error); - NotifAlert({ icon: "error", title: "Gagal", message: "Gagal menghapus jadwal." }); + const handleDelete = async (id) => { + const response = await deleteJadwalShift(id); + if (response.statusCode === 200) { + NotifAlert({ + icon: 'success', + title: 'Berhasil', + message: 'Jadwal berhasil dihapus.', + }); + doFilter(); + } else { + NotifAlert({ + icon: 'error', + title: 'Gagal', + message: response?.message || 'Gagal menghapus jadwal.', + }); } }; @@ -185,52 +253,79 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { setSelectedSchedules([]); }; - const handleSelectSchedule = (scheduleId, isChecked) => { + const handleSelectSchedule = (id, isChecked) => { if (isChecked) { - setSelectedSchedules(prev => [...prev, scheduleId]); + setSelectedSchedules((prev) => [...prev, id]); } else { - setSelectedSchedules(prev => prev.filter(id => id !== scheduleId)); + setSelectedSchedules((prev) => prev.filter((scheduleId) => scheduleId !== id)); } }; const handleBulkUpdateChange = (scheduleId, field, value) => { - setPendingChanges(prev => ({ + setPendingChanges((prev) => ({ ...prev, [scheduleId]: { ...prev[scheduleId], [field]: value, - } + }, })); }; const handleBulkSave = async () => { if (Object.keys(pendingChanges).length === 0) { - NotifAlert({ icon: 'info', title: 'Tidak Ada Perubahan', message: 'Tidak ada perubahan untuk disimpan.' }); + NotifAlert({ + icon: 'info', + title: 'Tidak Ada Perubahan', + message: 'Tidak ada perubahan untuk disimpan.', + }); cancelShiftEditMode(); return; } - const updatePromises = Object.keys(pendingChanges).map(scheduleId => { - const originalSchedule = groupedSchedules[editingShift].users.find(u => u.schedule_id.toString() === scheduleId); - const updatedData = { ...originalSchedule, ...pendingChanges[scheduleId] }; - // return updateJadwalShift(scheduleId, updatedData); // UNCOMMENT FOR REAL API - console.log(`Simulating update for schedule ${scheduleId}:`, updatedData); // DUMMY LOG - return Promise.resolve(); // DUMMY PROMISE + const updatePromises = Object.keys(pendingChanges).map((id) => { + const originalSchedule = groupedSchedules[editingShift].users.find( + (u) => u.id.toString() === id + ); + const changes = pendingChanges[id]; + + // Build payload according to backend schema + const payload = { + user_id: changes.user_id || originalSchedule.user_id, + shift_id: changes.shift_id || originalSchedule.shift_id, + }; + + if (originalSchedule.schedule_id) { + payload.schedule_id = originalSchedule.schedule_id; + } + + return updateJadwalShift(id, payload); }); try { await Promise.all(updatePromises); - NotifOk({ icon: 'success', title: 'Berhasil', message: 'Semua perubahan berhasil disimpan.' }); - fetchData(); + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: 'Semua perubahan berhasil disimpan.', + }); + doFilter(); cancelShiftEditMode(); } catch (error) { - NotifAlert({ icon: 'error', title: 'Gagal', message: 'Gagal menyimpan beberapa perubahan.' }); + NotifAlert({ + icon: 'error', + title: 'Gagal', + message: 'Gagal menyimpan beberapa perubahan.', + }); } }; const handleBulkDelete = () => { if (selectedSchedules.length === 0) { - NotifAlert({ icon: 'warning', title: 'Perhatian', message: 'Pilih setidaknya satu jadwal untuk dihapus.' }); + NotifAlert({ + icon: 'warning', + title: 'Perhatian', + message: 'Pilih setidaknya satu jadwal untuk dihapus.', + }); return; } NotifConfirmDialog({ @@ -238,11 +333,13 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { title: `Konfirmasi Hapus`, message: `Anda yakin ingin menghapus ${selectedSchedules.length} jadwal yang dipilih?`, onConfirm: async () => { - await Promise.all(selectedSchedules.map(id => deleteJadwalShift(id))); - NotifOk({ icon: 'success', title: 'Berhasil', message: `${selectedSchedules.length} jadwal berhasil dihapus.` }); - fetchData(); - // Exit both edit modes - setIsEditMode(false); + await Promise.all(selectedSchedules.map((id) => deleteJadwalShift(id))); + NotifOk({ + icon: 'success', + title: 'Berhasil', + message: `${selectedSchedules.length} jadwal berhasil dihapus.`, + }); + doFilter(); setEditingShift(null); setSelectedSchedules([]); }, @@ -257,106 +354,75 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { - - {isEditMode ? ( - - - Mode Edit Halaman - - - - - - - - ) : ( - <> - - + + { + const value = e.target.value; + setSearchValue(value); + if (value === '') { + handleSearchClear(); + } + }} + onSearch={handleSearch} + allowClear + enterButton={ + - - - - { - const value = e.target.value; - setSearchValue(value); - if (value === '') { - handleSearchClear(); - } - }} - onSearch={handleSearch} - allowClear - enterButton={ - + @@ -377,23 +447,26 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { } onClick={() => handleShiftEditMode(shiftName)} - disabled={editingShift !== null || isEditMode} + disabled={editingShift !== null} > Edit @@ -410,81 +483,321 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { )} - {/* Horizontal scrollable container for employee cards */} -
+
{groupedSchedules[shiftName].users.length > 0 ? ( - groupedSchedules[shiftName].users.map(user => ( + groupedSchedules[shiftName].users.map((user) => ( - {isEditMode && editingShift === null && ( // Checkbox for global delete mode only - handleSelectSchedule(user.schedule_id, e.target.checked)} - onClick={(e) => e.stopPropagation()} // Prevent card click - /> - )} - {editingShift === shiftName || (isEditMode && editingShift === null) ? ( // Global or Shift-specific Edit Mode + {editingShift === shiftName ? ( // EDIT MODE VIEW -
- - - - - handleSelectSchedule(user.schedule_id, e.target.checked)} - /> +
+
+ + handleSelectSchedule( + user.id, + e.target.checked + ) + } + style={{ + transform: 'scale(1.4)', + }} + /> +
+
+
+ + KARYAWAN + + +
+
+ + NO. TELEPON + + + emp.user_id === + pendingChanges[ + user + .id + ]?.user_id + )?.user_phone || + user.whatsapp + : user.whatsapp + } + readOnly + style={{ + backgroundColor: + '#f5f5f5', + color: '#595959', + cursor: 'not-allowed', + }} + size="large" + /> +
+
+ + SHIFT + + +
+
) : ( // NORMAL VIEW -
-
- {user.nama_employee} - {user.whatsapp} +
+
+ + {user.nama_employee} + + + {user.whatsapp} +
-
- - Terakhir diperbarui
- {formatRelativeTimestamp(groupedSchedules[shiftName].lastUpdate.timestamp)}
- oleh {groupedSchedules[shiftName].lastUpdate.user} +
+ + + Terakhir diperbarui + {' '} +
+ {formatRelativeTimestamp( + user.updated_at || + user.created_at || + new Date() + )}{' '} +
+ oleh {user.updated_by || 'N/A'}
-
@@ -492,14 +805,15 @@ const ListJadwalShift = memo(function ListJadwalShift(props) { )) ) : ( - Tidak ada karyawan yang dijadwalkan untuk shift ini. + + Tidak ada karyawan yang dijadwalkan untuk shift ini. + )}
)) - )} -
- + )} +
);