diff --git a/public/assets/defaultSparepartImg.jpg b/public/assets/defaultSparepartImg.jpg new file mode 100644 index 0000000..4cf1f7c Binary files /dev/null and b/public/assets/defaultSparepartImg.jpg differ diff --git a/src/api/notification.jsx b/src/api/notification.jsx index 9aab0c5..fe38523 100644 --- a/src/api/notification.jsx +++ b/src/api/notification.jsx @@ -1,9 +1,21 @@ import { SendRequest } from '../components/Global/ApiRequest'; -export const getAllNotification = async () => { +const getAllNotification = async (queryParams) => { const response = await SendRequest({ method: 'get', - prefix: 'notification', + prefix: `notification?${queryParams.toString()}`, }); + return response.data; }; + +const getNotificationById = async (id) => { + const response = await SendRequest({ + method: 'get', + prefix: `notification/${id}`, + }); + + return response.data; +}; + +export { getAllNotification, getNotificationById }; diff --git a/src/components/Global/TableList.jsx b/src/components/Global/TableList.jsx index a44ed8c..19f6a36 100644 --- a/src/components/Global/TableList.jsx +++ b/src/components/Global/TableList.jsx @@ -20,6 +20,9 @@ const TableList = memo(function TableList({ fieldColor, firstLoad = true, columnDynamic = false, + cardComponent, // New prop for custom card component + onStockUpdate, // Prop to pass to card component + onGetData, // Callback to execute when data is received }) { const [gridLoading, setGridLoading] = useState(false); @@ -103,7 +106,14 @@ const TableList = memo(function TableList({ setColumnsDynamic([...defaultColumns, ...numericColumns]); } - setData(resData?.data ?? []); + const fetchedData = resData?.data ?? []; + + // Panggil callback jika disediakan + if (onGetData && typeof onGetData === 'function') { + onGetData(fetchedData); + } + + setData(fetchedData); const pagingData = resData?.paging; @@ -142,6 +152,9 @@ const TableList = memo(function TableList({ const isMobile = !screens.md; // kalau kurang dari md (768px) dianggap mobile + // Use the custom card component if provided, otherwise default to CardList + const CardViewComponent = cardComponent || CardList; + return (
{(isMobile && mobile) || viewMode === 'card' ? ( - ) : ( @@ -200,3 +214,4 @@ const TableList = memo(function TableList({ }); export default TableList; + diff --git a/src/pages/master/brandDevice/EditBrandDevice.jsx b/src/pages/master/brandDevice/EditBrandDevice.jsx index fe4e8a1..0581433 100644 --- a/src/pages/master/brandDevice/EditBrandDevice.jsx +++ b/src/pages/master/brandDevice/EditBrandDevice.jsx @@ -159,7 +159,7 @@ const EditBrandDevice = () => { sparepart: ec.sparepart || [], errorCodeIcon: ec.path_icon ? { - name: 'icon', + name: ec.path_icon.split('/').pop(), // Ambil nama file dari path uploadPath: ec.path_icon, url: (() => { const pathParts = ec.path_icon.split('/'); diff --git a/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx b/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx index 9781c75..79a0661 100644 --- a/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx +++ b/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx @@ -152,7 +152,7 @@ const ErrorCodeSimpleForm = ({
Error Code Icon + new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result); + reader.onerror = (error) => reject(error); + }); + const DetailSparepart = (props) => { const [confirmLoading, setConfirmLoading] = useState(false); + const [fileList, setFileList] = useState([]); + const [previewOpen, setPreviewOpen] = useState(false); + const [previewImage, setPreviewImage] = useState(''); + const [previewTitle, setPreviewTitle] = useState(''); const defaultData = { sparepart_id: '', @@ -18,7 +44,8 @@ const DetailSparepart = (props) => { sparepart_item_type: '', sparepart_unit: '', sparepart_merk: '', - sparepart_stok: '', + sparepart_stok: '0', + sparepart_foto: '', }; const [formData, setFormData] = useState(defaultData); @@ -26,60 +53,200 @@ const DetailSparepart = (props) => { const handleCancel = () => { props.setSelectedData(null); props.setActionMode('list'); + setFileList([]); }; + const handlePreviewCancel = () => setPreviewOpen(false); + + const handlePreview = async (file) => { + if (!file.url && !file.preview) { + file.preview = await getBase64(file.originFileObj); + } + setPreviewImage(file.url || file.preview); + setPreviewOpen(true); + setPreviewTitle(file.name || file.url.substring(file.url.lastIndexOf('/') + 1)); + }; + + const handleChange = ({ fileList: newFileList }) => setFileList(newFileList); + const handleSave = async () => { setConfirmLoading(true); - // Daftar aturan validasi const validationRules = [ { field: 'sparepart_name', label: 'Sparepart Name', required: true }, - { field: 'sparepart_model', label: 'Sparepart Model', required: true }, - { field: 'sparepart_unit', label: 'Sparepart Unit', required: true }, - { field: 'sparepart_merk', label: 'Sparepart Merk', required: true }, - { field: 'sparepart_stok', label: 'Sparepart Stok', required: true }, ]; if ( validateRun(formData, validationRules, (errorMessages) => { - NotifOk({ - icon: 'warning', - title: 'Peringatan', - message: errorMessages, - }); + NotifOk({ icon: 'warning', title: 'Peringatan', message: errorMessages }); setConfirmLoading(false); }) ) return; try { + let imageUrl = formData.sparepart_foto; + const newFile = fileList.length > 0 ? fileList[0] : null; + + if (newFile && newFile.originFileObj) { + console.log('Uploading file:', newFile.originFileObj); + const uploadResponse = await uploadFile(newFile.originFileObj, 'images'); + + // Log untuk debugging + console.log('Upload response:', uploadResponse); + + // Cek berbagai kemungkinan struktur respons dari API + let uploadedUrl = null; + + // Cek berbagai kemungkinan struktur respons dari API + // Cek langsung properti file_url atau url + if (uploadResponse && typeof uploadResponse === 'object') { + // Cek jika uploadResponse langsung memiliki file_url + if (uploadResponse.file_url) { + uploadedUrl = uploadResponse.file_url; + } + // Cek jika uploadResponse memiliki data yang berisi file_url + else if (uploadResponse.data && uploadResponse.data.file_url) { + uploadedUrl = uploadResponse.data.file_url; + } + // Cek jika uploadResponse memiliki data yang berisi url + else if (uploadResponse.data && uploadResponse.data.url) { + uploadedUrl = uploadResponse.data.url; + } + // Cek jika uploadResponse langsung memiliki url + else if (uploadResponse.url) { + uploadedUrl = uploadResponse.url; + } + // Cek jika uploadResponse.data adalah string URL + else if (uploadResponse.data && typeof uploadResponse.data === 'string') { + uploadedUrl = uploadResponse.data; + } + // Cek jika uploadResponse.data adalah objek yang berisi file URL dalam format berbeda + else if (uploadResponse.data && typeof uploadResponse.data === 'object') { + // Cek kemungkinan nama field lain + if (uploadResponse.data.file) { + uploadedUrl = uploadResponse.data.file; + } else if (uploadResponse.data.filename) { + // Jika hanya nama file dikembalikan, bangun URL + const baseUrl = import.meta.env.VITE_API_SERVER || ''; + uploadedUrl = `${baseUrl}/uploads/images/${uploadResponse.data.filename}`; + } else if (uploadResponse.data.path) { + uploadedUrl = uploadResponse.data.path; + } else if (uploadResponse.data.location) { + uploadedUrl = uploadResponse.data.location; + } + // Tambahkan kemungkinan lain berdasarkan struktur respons umum + else if (uploadResponse.data.filePath) { + uploadedUrl = uploadResponse.data.filePath; + } else if (uploadResponse.data.file_path) { + uploadedUrl = uploadResponse.data.file_path; + } else if (uploadResponse.data.publicUrl) { + uploadedUrl = uploadResponse.data.publicUrl; + } else if (uploadResponse.data.public_url) { + uploadedUrl = uploadResponse.data.public_url; + } + // Berdasarkan log yang ditampilkan, API mengembalikan path_document atau path_solution + else if (uploadResponse.data.path_document) { + uploadedUrl = uploadResponse.data.path_document; + } else if (uploadResponse.data.path_solution) { + uploadedUrl = uploadResponse.data.path_solution; + } else if (uploadResponse.data.file_upload_name) { + // Jika hanya nama file dikembalikan, bangun URL + const baseUrl = import.meta.env.VITE_API_SERVER || ''; + uploadedUrl = `${baseUrl}/uploads/images/${uploadResponse.data.file_upload_name}`; + } + } + } + // Jika respons adalah string, mungkin itu adalah URL + else if (uploadResponse && typeof uploadResponse === 'string') { + uploadedUrl = uploadResponse; + } + + if (uploadedUrl) { + console.log('Successfully extracted image URL:', uploadedUrl); + imageUrl = uploadedUrl; + } else { + console.error('Upload response structure:', uploadResponse); + console.error('Available properties:', Object.keys(uploadResponse || {})); + console.error('Response type:', typeof uploadResponse); + console.error( + 'Is response an object?', + uploadResponse && typeof uploadResponse === 'object' + ); + if (uploadResponse && typeof uploadResponse === 'object') { + console.error('Response keys:', Object.keys(uploadResponse)); + console.error( + 'Response data keys:', + uploadResponse.data + ? Object.keys(uploadResponse.data) + : 'No data property' + ); + } + + // Tampilkan notifikasi bahwa upload gagal tapi lanjutkan penyimpanan + NotifOk({ + icon: 'warning', + title: 'Peringatan', + message: 'Upload gambar gagal. Data akan disimpan tanpa gambar.', + }); + + // Gunakan URL gambar yang sebelumnya jika ada, atau kosongkan + imageUrl = formData.sparepart_foto || ''; + } + } else if (fileList.length === 0) { + // Jika tidak ada file di fileList (termasuk saat user menghapus file), gunakan gambar default + imageUrl = '/assets/defaultSparepartImg.jpg'; + } + + // Payload hanya berisi field yang tidak kosong untuk menghindari error validasi const payload = { - sparepart_name: formData.sparepart_name, - sparepart_description: formData.sparepart_description, - sparepart_model: formData.sparepart_model, - sparepart_item_type: formData.sparepart_item_type, - sparepart_unit: formData.sparepart_unit, - sparepart_merk: formData.sparepart_merk, - sparepart_stok: formData.sparepart_stok, + sparepart_name: formData.sparepart_name, // Wajib }; + // Tambahkan field-field secara kondisional hanya jika nilainya tidak kosong + if (formData.sparepart_description && formData.sparepart_description.trim() !== '') { + payload.sparepart_description = formData.sparepart_description; + } + if (formData.sparepart_model && formData.sparepart_model.trim() !== '') { + payload.sparepart_model = formData.sparepart_model; + } + if (formData.sparepart_item_type && formData.sparepart_item_type.trim() !== '') { + payload.sparepart_item_type = formData.sparepart_item_type; + } + if (formData.sparepart_unit && formData.sparepart_unit.trim() !== '') { + payload.sparepart_unit = formData.sparepart_unit; + } + if (formData.sparepart_merk && formData.sparepart_merk.trim() !== '') { + payload.sparepart_merk = formData.sparepart_merk; + } + if (formData.sparepart_stok && formData.sparepart_stok.trim() !== '') { + payload.sparepart_stok = formData.sparepart_stok.toString(); + } else { + payload.sparepart_stok = '0'; // Set default value jika tidak diisi + } + // Sertakan sparepart_foto hanya jika nilainya tidak kosong, agar tidak memicu validasi + if (imageUrl && imageUrl.trim() !== '') { + payload.sparepart_foto = imageUrl; + } + + console.log('Sending payload:', payload); + const response = formData.sparepart_id ? await updateSparepart(formData.sparepart_id, payload) : await createSparepart(payload); - // Check if response is successful - if (response && (response.statusCode === 200 || response.statusCode === 201)) { - const sparepartName = response.data?.sparepart_name || formData.sparepart_name; + console.log('API response:', response); + if (response && (response.statusCode === 200 || response.statusCode === 201)) { NotifOk({ icon: 'success', title: 'Berhasil', - message: `Data Sparepart "${sparepartName}" berhasil ${ + message: `Data Sparepart berhasil ${ formData.sparepart_id ? 'diubah' : 'ditambahkan' }.`, }); - props.setActionMode('list'); + setFileList([]); } else { NotifAlert({ icon: 'error', @@ -101,35 +268,47 @@ const DetailSparepart = (props) => { const handleInputChange = (e) => { const { name, value } = e.target; - setFormData({ - ...formData, - [name]: value, - }); + setFormData({ ...formData, [name]: value }); }; - const handleFieldChange = (name, value) => { - setFormData({ - ...formData, - [name]: value, - }); - }; - - const handleStatusToggle = (event) => { - const isChecked = event; - setFormData({ - ...formData, - is_active: isChecked ? true : false, - }); + const handleSelectChange = (name, value) => { + setFormData({ ...formData, [name]: value }); }; useEffect(() => { if (props.selectedData) { setFormData(props.selectedData); + if (props.selectedData.sparepart_foto) { + // Buat URL lengkap dengan token untuk file yang sudah ada + const fileName = props.selectedData.sparepart_foto.split('/').pop(); + const token = localStorage.getItem('token'); + const baseURL = import.meta.env.VITE_API_SERVER || ''; + const fullUrl = `${baseURL}/file-uploads/images/${encodeURIComponent(fileName)}${token ? `?token=${encodeURIComponent(token)}` : ''}`; + + setFileList([ + { + uid: '-1', + name: fileName, + status: 'done', + url: fullUrl, + }, + ]); + } else { + setFileList([]); + } } else { setFormData(defaultData); + setFileList([]); } }, [props.showModal, props.selectedData, props.actionMode]); + const uploadButton = ( +
+ +
Upload
+
+ ); + return ( { defaultBorderColor: '#23A55A', defaultHoverColor: '#23A55A', defaultHoverBorderColor: '#23A55A', - defaultHoverColor: '#23A55A', }, }, }} @@ -162,9 +340,7 @@ const DetailSparepart = (props) => { { > {formData && (
- - -
- Sparepart Name - * - -
- -
- Sparepart Description -