diff --git a/src/pages/master/brandDevice/hooks/errorCode.js b/src/pages/master/brandDevice/hooks/errorCode.js
deleted file mode 100644
index 7a82ee5..0000000
--- a/src/pages/master/brandDevice/hooks/errorCode.js
+++ /dev/null
@@ -1,263 +0,0 @@
-import { useState, useEffect } from 'react';
-import { NotifAlert, NotifOk } from '../../../../components/Global/ToastNotif';
-
-export const useErrorCodeLogic = (errorCodeForm, fileList) => {
- const [solutionFields, setSolutionFields] = useState([0]);
- const [solutionTypes, setSolutionTypes] = useState({ 0: 'text' });
- const [solutionStatuses, setSolutionStatuses] = useState({ 0: true });
- const [firstSolutionValid, setFirstSolutionValid] = useState(false);
- const [solutionsToDelete, setSolutionsToDelete] = useState(new Set());
-
- const checkPreviousSolutionValid = (currentSolutionIndex) => {
- for (let i = 0; i < currentSolutionIndex; i++) {
- const fieldId = solutionFields[i];
- const solutionType = solutionTypes[fieldId];
-
- const solutionName = errorCodeForm.getFieldValue(`solution_name_${fieldId}`);
- if (!solutionName || solutionName.trim() === '') {
- return false;
- }
-
- if (solutionType === 'text') {
- const textSolution = errorCodeForm.getFieldValue(`text_solution_${fieldId}`);
- if (!textSolution || textSolution.trim() === '') {
- return false;
- }
- } else if (solutionType === 'file') {
- const filesForSolution = fileList.filter(file => file.solutionId === fieldId);
- if (filesForSolution.length === 0) {
- return false;
- }
- }
- }
- return true;
- };
-
- const checkFirstSolutionValid = () => {
- if (solutionFields.length === 0) {
- setFirstSolutionValid(false);
- return false;
- }
- const isValid = checkPreviousSolutionValid(1);
- setFirstSolutionValid(isValid);
- return isValid;
- };
-
- const handleAddSolutionField = () => {
- const currentSolutionCount = solutionFields.length;
- const nextSolutionNumber = currentSolutionCount + 1;
-
- if (!checkPreviousSolutionValid(currentSolutionCount)) {
- let incompleteSolutionIndex = -1;
- for (let i = 0; i < currentSolutionCount; i++) {
- const fieldId = solutionFields[i];
- const solutionType = solutionTypes[fieldId];
- const solutionName = errorCodeForm.getFieldValue(`solution_name_${fieldId}`);
- let hasContent = false;
-
- if (solutionType === 'text') {
- const textSolution = errorCodeForm.getFieldValue(`text_solution_${fieldId}`);
- hasContent = textSolution && textSolution.trim();
- } else if (solutionType === 'file') {
- const filesForSolution = fileList.filter(file => file.solutionId === fieldId);
- hasContent = filesForSolution.length > 0;
- }
-
- if (!solutionName?.trim() || !hasContent) {
- incompleteSolutionIndex = i + 1;
- break;
- }
- }
-
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: `Harap lengkapi Solution ${incompleteSolutionIndex} terlebih dahulu sebelum menambah Solution ${nextSolutionNumber}!`
- });
- return;
- }
-
- const newId = `new-${Date.now()}`;
- setSolutionFields(prev => [...prev, newId]);
- setSolutionTypes(prev => ({ ...prev, [newId]: 'text' }));
- setSolutionStatuses(prev => ({ ...prev, [newId]: true }));
- errorCodeForm.setFieldValue(`solution_status_${newId}`, true);
- errorCodeForm.setFieldValue(`solution_type_${newId}`, 'text');
- };
-
- const handleRemoveSolutionField = (id) => {
- const isNewSolution = !id.toString().startsWith('existing-');
-
- if (isNewSolution) {
- if (solutionFields.length > 1) {
- setSolutionFields(solutionFields.filter(fieldId => fieldId !== id));
- setSolutionTypes(prev => {
- const newTypes = { ...prev };
- delete newTypes[id];
- return newTypes;
- });
- setSolutionStatuses(prev => {
- const newStatuses = { ...prev };
- delete newStatuses[id];
- return newStatuses;
- });
- setSolutionsToDelete(prev => {
- const newSet = new Set(prev);
- newSet.delete(id);
- return newSet;
- });
- } else {
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: 'Setiap error code harus memiliki minimal 1 solution!'
- });
- }
- } else {
- const solutionName = errorCodeForm.getFieldValue(`solution_name_${id}`);
- const solutionType = solutionTypes[id];
- let isEmpty = true;
-
- const existingSolution = window.currentSolutionData?.[id];
- const hasExistingData = existingSolution && (
- (existingSolution.solution_name && existingSolution.solution_name.trim()) ||
- (existingSolution.text_solution && existingSolution.text_solution.trim()) ||
- (existingSolution.path_solution && existingSolution.path_solution.trim())
- );
-
- if (solutionType === 'text') {
- const textSolution = errorCodeForm.getFieldValue(`text_solution_${id}`);
- isEmpty = !solutionName?.trim() && !textSolution?.trim() && !hasExistingData;
- } else if (solutionType === 'file') {
- const filesForSolution = fileList.filter(file => file.solutionId === id);
- isEmpty = !solutionName?.trim() && filesForSolution.length === 0 && !hasExistingData;
- }
-
- if (isEmpty) {
- if (solutionFields.length > 1) {
- setSolutionFields(solutionFields.filter(fieldId => fieldId !== id));
- setSolutionTypes(prev => {
- const newTypes = { ...prev };
- delete newTypes[id];
- return newTypes;
- });
- setSolutionStatuses(prev => {
- const newStatuses = { ...prev };
- delete newStatuses[id];
- return newStatuses;
- });
-
- if (window.currentSolutionData) {
- delete window.currentSolutionData[id];
- }
-
- setSolutionsToDelete(prev => {
- const newSet = new Set(prev);
- newSet.delete(id);
- return newSet;
- });
- } else {
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: 'Setiap error code harus memiliki minimal 1 solution!'
- });
- }
- } else {
- if (solutionFields.length > 1) {
- setSolutionsToDelete(prev => new Set(prev).add(id));
-
- const solutionElement = document.querySelector(`[data-solution-id="${id}"]`);
- if (solutionElement) {
- solutionElement.style.opacity = '0.5';
- solutionElement.style.border = '2px dashed #ff4d4f';
- }
-
- NotifOk({
- icon: 'success',
- title: 'Berhasil',
- message: 'Solution ditandai untuk dihapus. Klik "Update Error Code" untuk menyimpan perubahan.'
- });
- } else {
- NotifAlert({
- icon: 'warning',
- title: 'Perhatian',
- message: 'Setiap error code harus memiliki minimal 1 solution!'
- });
- }
- }
- }
- };
-
- const handleSolutionTypeChange = (fieldId, type) => {
- setSolutionTypes(prev => ({ ...prev, [fieldId]: type }));
- };
-
- const handleSolutionStatusChange = (fieldId, status) => {
- setSolutionStatuses(prev => ({
- ...prev,
- [fieldId]: status
- }));
- };
-
- const setSolutionsForExistingRecord = (solutions, errorCodeForm) => {
- const newSolutionFields = [];
- const newSolutionTypes = {};
- const newSolutionStatuses = {};
- const newSolutionData = {};
-
- solutions.forEach((solution, index) => {
- const fieldId = `existing-${index}`;
- newSolutionFields.push(fieldId);
- newSolutionTypes[fieldId] = solution.type_solution || 'text';
- newSolutionStatuses[fieldId] = solution.is_active !== false;
- newSolutionData[fieldId] = {
- ...solution
- };
-
- setTimeout(() => {
- errorCodeForm.setFieldsValue({
- [`solution_name_${fieldId}`]: solution.solution_name,
- [`text_solution_${fieldId}`]: solution.text_solution || '',
- [`solution_status_${fieldId}`]: solution.is_active !== false,
- [`solution_type_${fieldId}`]: solution.type_solution === 'image' || solution.type_solution === 'pdf' ? 'file' : solution.type_solution
- });
- }, 100);
- });
-
- setSolutionFields(newSolutionFields);
- setSolutionTypes(newSolutionTypes);
- setSolutionStatuses(newSolutionStatuses);
- window.currentSolutionData = newSolutionData;
- };
-
- const resetSolutionFields = () => {
- setSolutionFields([0]);
- setSolutionTypes({ 0: 'text' });
- setSolutionStatuses({ 0: true });
- setFirstSolutionValid(false);
- setSolutionsToDelete(new Set());
- };
-
- useEffect(() => {
- const timer = setTimeout(() => {
- checkFirstSolutionValid();
- }, 100);
- return () => clearTimeout(timer);
- }, [solutionFields, solutionTypes, fileList, errorCodeForm]);
-
- return {
- solutionFields,
- solutionTypes,
- solutionStatuses,
- firstSolutionValid,
- solutionsToDelete,
- handleAddSolutionField,
- handleRemoveSolutionField,
- handleSolutionTypeChange,
- handleSolutionStatusChange,
- resetSolutionFields,
- checkFirstSolutionValid,
- setSolutionsForExistingRecord
- };
-};
\ No newline at end of file
diff --git a/src/pages/master/brandDevice/hooks/solution.js b/src/pages/master/brandDevice/hooks/solution.js
deleted file mode 100644
index 4babbb9..0000000
--- a/src/pages/master/brandDevice/hooks/solution.js
+++ /dev/null
@@ -1,453 +0,0 @@
-import { useState, useEffect } from 'react';
-
-export const useSolutionLogic = (solutionForm) => {
- const [solutionFields, setSolutionFields] = useState([0]);
- const [solutionTypes, setSolutionTypes] = useState({ 0: 'text' });
- const [solutionStatuses, setSolutionStatuses] = useState({ 0: true });
- const [solutionsToDelete, setSolutionsToDelete] = useState([]);
-
- useEffect(() => {
- setTimeout(() => {
- if (solutionForm) {
- solutionForm.setFieldsValue({
- solution_items: {
- 0: {
- name: 'Solution 1',
- status: true,
- type: 'text',
- text: 'Solution description',
- file: null,
- fileUpload: null
- }
- }
- });
- }
- }, 100);
- }, [solutionForm]);
-
- const handleAddSolutionField = () => {
- const newKey = Date.now();
-
- setSolutionFields(prev => [...prev, newKey]);
- setSolutionTypes(prev => ({ ...prev, [newKey]: 'text' }));
- setSolutionStatuses(prev => ({ ...prev, [newKey]: true }));
-
- setTimeout(() => {
- const currentFormValues = solutionForm.getFieldsValue(true);
- const existingNames = [];
-
- Object.keys(currentFormValues).forEach(key => {
- if (key.startsWith('solution_items,') || key.startsWith('solution_items.')) {
- const solutionData = currentFormValues[key];
- if (solutionData && solutionData.name) {
- existingNames.push(solutionData.name);
- }
- }
- });
-
- if (currentFormValues.solution_items) {
- Object.values(currentFormValues.solution_items).forEach(solution => {
- if (solution && solution.name) {
- existingNames.push(solution.name);
- }
- });
- }
-
- let solutionNumber = solutionFields.length + 1;
- let defaultName = `Solution ${solutionNumber}`;
-
- while (existingNames.includes(defaultName)) {
- solutionNumber++;
- defaultName = `Solution ${solutionNumber}`;
- }
-
- solutionForm.setFieldValue(['solution_items', newKey, 'name'], defaultName);
- solutionForm.setFieldValue(['solution_items', newKey, 'type'], 'text');
- solutionForm.setFieldValue(['solution_items', newKey, 'text'], 'Solution description');
- solutionForm.setFieldValue(['solution_items', newKey, 'status'], true);
- solutionForm.setFieldValue(['solution_items', newKey, 'file'], null);
- solutionForm.setFieldValue(['solution_items', newKey, 'fileUpload'], null);
- }, 100);
- };
-
- const handleRemoveSolutionField = (key) => {
- if (solutionFields.length <= 1) {
- return;
- }
-
- setSolutionFields(prev => prev.filter(field => field !== key));
-
- const newTypes = { ...solutionTypes };
- const newStatuses = { ...solutionStatuses };
- delete newTypes[key];
- delete newStatuses[key];
-
- setSolutionTypes(newTypes);
- setSolutionStatuses(newStatuses);
-
- setTimeout(() => {
- try {
- solutionForm.setFieldValue(['solution_items', key], undefined);
- solutionForm.setFieldValue(['solution_items', key, 'name'], undefined);
- solutionForm.setFieldValue(['solution_items', key, 'type'], undefined);
- solutionForm.setFieldValue(['solution_items', key, 'text'], undefined);
- solutionForm.setFieldValue(['solution_items', key, 'status'], undefined);
- solutionForm.setFieldValue(['solution_items', key, 'file'], undefined);
- solutionForm.setFieldValue(['solution_items', key, 'fileUpload'], undefined);
- } catch (error) {
- }
- }, 50);
- };
-
- const handleSolutionTypeChange = (key, value) => {
- setSolutionTypes(prev => ({ ...prev, [key]: value }));
-
- setTimeout(() => {
- const fieldName = ['solution_items', key];
- const currentSolutionData = solutionForm.getFieldsValue([fieldName]) || {};
- const solutionData = currentSolutionData[`solution_items,${key}`] || currentSolutionData[`solution_items.${key}`] || {};
-
- if (value === 'text') {
- const updatedSolutionData = {
- ...solutionData,
- fileUpload: null,
- file: null,
- path_solution: null,
- fileName: null,
- text: solutionData.text || 'Solution description'
- };
-
- solutionForm.setFieldValue([...fieldName, 'fileUpload'], null);
- solutionForm.setFieldValue([...fieldName, 'file'], null);
- solutionForm.setFieldValue([...fieldName, 'path_solution'], null);
- solutionForm.setFieldValue([...fieldName, 'fileName'], null);
- solutionForm.setFieldValue([...fieldName, 'text'], updatedSolutionData.text);
- } else if (value === 'file') {
- const updatedSolutionData = {
- ...solutionData,
- text: '',
- fileUpload: null,
- file: null,
- path_solution: null,
- fileName: null
- };
- solutionForm.setFieldValue([...fieldName, 'text'], '');
- solutionForm.setFieldValue([...fieldName, 'fileUpload'], null);
- solutionForm.setFieldValue([...fieldName, 'file'], null);
- solutionForm.setFieldValue([...fieldName, 'path_solution'], null);
- solutionForm.setFieldValue([...fieldName, 'fileName'], null);
- }
- }, 0);
- };
-
- const handleSolutionStatusChange = (key, value) => {
- setSolutionStatuses(prev => ({ ...prev, [key]: value }));
- };
-
- const resetSolutionFields = () => {
- setSolutionFields([0]);
- setSolutionTypes({ 0: 'text' });
- setSolutionStatuses({ 0: true });
-
- if (!solutionForm || !solutionForm.resetFields) {
- return;
- }
-
- solutionForm.resetFields();
- setTimeout(() => {
- solutionForm.setFieldsValue({
- solution_items: {
- 0: {
- name: 'Solution 1',
- status: true,
- type: 'text',
- text: 'Solution description',
- file: null,
- fileUpload: null
- }
- }
- });
-
- solutionForm.setFieldValue(['solution_items', 0, 'name'], 'Solution 1');
- solutionForm.setFieldValue(['solution_items', 0, 'type'], 'text');
- solutionForm.setFieldValue(['solution_items', 0, 'text'], 'Solution description');
- solutionForm.setFieldValue(['solution_items', 0, 'status'], true);
- solutionForm.setFieldValue(['solution_items', 0, 'file'], null);
- solutionForm.setFieldValue(['solution_items', 0, 'fileUpload'], null);
-
- }, 100);
- };
-
- const checkFirstSolutionValid = () => {
- if (!solutionForm || !solutionForm.getFieldsValue) {
- return false;
- }
- const values = solutionForm.getFieldsValue();
-
- const firstField = solutionFields[0];
- if (!firstField) {
- return false;
- }
-
- const solutionKey = firstField.key || firstField;
- const commaPath = `solution_items,${solutionKey}`;
- const dotPath = `solution_items.${solutionKey}`;
- const firstSolution = values[commaPath] || values[dotPath];
-
- if (!firstSolution || !firstSolution.name || firstSolution.name.trim() === '') {
- return false;
- }
-
- if (solutionTypes[solutionKey] === 'text' && (!firstSolution.text || firstSolution.text.trim() === '')) {
- return false;
- }
-
- return true;
- };
-
- const getSolutionData = () => {
- try {
- const values = solutionForm.getFieldsValue(true);
- const result = [];
-
- solutionFields.forEach(key => {
- let solution = null;
-
- try {
- solution = solutionForm.getFieldValue(['solution_items', key]);
- } catch (error) {
- }
-
- if (!solution && values.solution_items && values.solution_items[key]) {
- solution = values.solution_items[key];
- }
-
- if (!solution) {
- const commaKey = `solution_items,${key}`;
- solution = values[commaKey];
- }
-
- if (!solution) {
- const dotKey = `solution_items.${key}`;
- solution = values[dotKey];
- }
-
- if (!solution) {
- const allKeys = Object.keys(values);
- const foundKey = allKeys.find(k =>
- k.includes(key.toString()) &&
- k.includes('solution_items')
- );
- if (foundKey) {
- solution = values[foundKey];
- }
- }
-
- if (!solution) {
- const rawValues = solutionForm.getFieldsValue();
-
- if (rawValues.solution_items && rawValues.solution_items[key]) {
- solution = rawValues.solution_items[key];
- }
- }
-
- if (!solution) {
- return;
- }
-
-
- const hasName = solution.name && solution.name.trim() !== '';
-
- if (!hasName) {
- return;
- }
-
- const solutionType = solutionTypes[key] || solution.type || 'text';
- let isValidType = true;
-
- if (solutionType === 'text') {
- isValidType = solution.text && solution.text.trim() !== '';
- if (!isValidType) {
- return;
- }
- } else if (solutionType === 'file') {
- const hasPathSolution = solution.path_solution && solution.path_solution.trim() !== '';
- const hasFileUpload = (solution.fileUpload && typeof solution.fileUpload === 'object' && Object.keys(solution.fileUpload).length > 0);
- const hasFile = (solution.file && typeof solution.file === 'object' && Object.keys(solution.file).length > 0);
-
- isValidType = hasPathSolution || hasFileUpload || hasFile;
- if (!isValidType) {
- return;
- }
- }
-
- let pathSolution = '';
- let fileObject = null;
- const typeSolution = solutionTypes[key] || solution.type || 'text';
-
- if (typeSolution === 'file') {
- if (solution.fileUpload && typeof solution.fileUpload === 'object' && Object.keys(solution.fileUpload).length > 0) {
- pathSolution = solution.fileUpload.path_solution || solution.fileUpload.uploadPath || '';
- fileObject = solution.fileUpload;
- } else if (solution.file && typeof solution.file === 'object' && Object.keys(solution.file).length > 0) {
- pathSolution = solution.file.path_solution || solution.file.uploadPath || '';
- fileObject = solution.file;
- } else if (solution.file && typeof solution.file === 'string' && solution.file.trim() !== '') {
- pathSolution = solution.file;
- } else if (solution.path_solution && solution.path_solution.trim() !== '') {
- pathSolution = solution.path_solution;
- } else {
- }
- }
-
- let finalTypeSolution = typeSolution;
- if (typeSolution === 'file') {
- if (fileObject && fileObject.type_solution) {
- finalTypeSolution = fileObject.type_solution;
- } else {
- finalTypeSolution = 'image';
- }
- }
-
- const finalSolution = {
- solution_name: solution.name,
- type_solution: finalTypeSolution,
- is_active: solution.status !== false && solution.status !== undefined ? solution.status : (solutionStatuses[key] !== false),
- };
-
- if (typeSolution === 'text') {
- finalSolution.text_solution = solution.text || '';
- finalSolution.path_solution = '';
- } else {
- finalSolution.text_solution = '';
- finalSolution.path_solution = pathSolution;
- }
-
- result.push(finalSolution);
- });
-
- return result;
- } catch (error) {
- return [];
- }
- };
-
- const setSolutionsForExistingRecord = (solutions, form) => {
- if (!solutions || solutions.length === 0) return;
-
- const newFields = solutions.map((solution, index) => solution.id || index);
-
- setSolutionFields(newFields);
-
- const solutionsValues = {};
- const newTypes = {};
- const newStatuses = {};
-
- solutions.forEach((solution, index) => {
- const key = solution.brand_code_solution_id || solution.id || index;
-
- let fileObject = null;
- if (solution.path_solution && solution.path_solution.trim() !== '') {
- const fileName = solution.file_upload_name || solution.path_solution.split('/').pop() || `file_${index}`;
-
- fileObject = {
- uploadPath: solution.path_solution,
- path_solution: solution.path_solution,
- name: fileName,
- type_solution: solution.type_solution || 'image',
- isExisting: true,
- size: 0,
- type: solution.type_solution === 'pdf' ? 'application/pdf' : 'image/jpeg',
- fileExtension: solution.type_solution === 'pdf' ? 'pdf' : (fileName.split('.').pop().toLowerCase() || 'jpg')
- };
- }
-
- const isFileType = solution.type_solution && solution.type_solution !== 'text' && fileObject;
-
- solutionsValues[key] = {
- name: solution.solution_name || '',
- type: isFileType ? 'file' : 'text',
- text: solution.text_solution || '',
- file: fileObject,
- fileUpload: fileObject,
- status: solution.is_active !== false,
- path_solution: solution.path_solution || ''
- };
- newTypes[key] = isFileType ? 'file' : 'text';
- newStatuses[key] = solution.is_active !== false;
- });
-
- const nestedFormValues = {
- solution_items: {}
- };
-
- Object.keys(solutionsValues).forEach(key => {
- const solution = solutionsValues[key];
- nestedFormValues.solution_items[key] = {
- name: solution.name,
- type: solution.type,
- text: solution.text,
- file: solution.file,
- fileUpload: solution.fileUpload,
- status: solution.status,
- path_solution: solution.path_solution
- };
- });
-
- form.setFieldsValue(nestedFormValues);
-
- const fallbackFormValues = {};
- Object.keys(solutionsValues).forEach(key => {
- const solution = solutionsValues[key];
- fallbackFormValues[`solution_items,${key}`] = {
- name: solution.name,
- type: solution.type,
- text: solution.text,
- file: solution.file,
- fileUpload: solution.fileUpload,
- status: solution.status,
- path_solution: solution.path_solution
- };
- });
-
- form.setFieldsValue(fallbackFormValues);
-
- Object.keys(solutionsValues).forEach(key => {
- const solution = solutionsValues[key];
- form.setFieldValue([`solution_items,${key}`, 'name'], solution.name);
- form.setFieldValue([`solution_items,${key}`, 'type'], solution.type);
- form.setFieldValue([`solution_items,${key}`, 'text'], solution.text);
- form.setFieldValue([`solution_items,${key}`, 'file'], solution.file);
- form.setFieldValue([`solution_items,${key}`, 'fileUpload'], solution.fileUpload);
- form.setFieldValue([`solution_items,${key}`, 'status'], solution.status);
- form.setFieldValue([`solution_items,${key}`, 'path_solution'], solution.path_solution);
-
- form.setFieldValue(['solution_items', key, 'name'], solution.name);
- form.setFieldValue(['solution_items', key, 'type'], solution.type);
- form.setFieldValue(['solution_items', key, 'text'], solution.text);
- form.setFieldValue(['solution_items', key, 'file'], solution.file);
- form.setFieldValue(['solution_items', key, 'fileUpload'], solution.fileUpload);
- form.setFieldValue(['solution_items', key, 'status'], solution.status);
- form.setFieldValue(['solution_items', key, 'path_solution'], solution.path_solution);
- });
-
- setSolutionTypes(newTypes);
- setSolutionStatuses(newStatuses);
- };
-
- return {
- solutionFields,
- solutionTypes,
- solutionStatuses,
- solutionsToDelete,
- firstSolutionValid: checkFirstSolutionValid(),
- handleAddSolutionField,
- handleRemoveSolutionField,
- handleSolutionTypeChange,
- handleSolutionStatusChange,
- resetSolutionFields,
- checkFirstSolutionValid,
- getSolutionData,
- setSolutionsForExistingRecord,
- };
-};
\ No newline at end of file
diff --git a/src/pages/notification/component/ListNotification.jsx b/src/pages/notification/component/ListNotification.jsx
index 3fc11c8..a959cd9 100644
--- a/src/pages/notification/component/ListNotification.jsx
+++ b/src/pages/notification/component/ListNotification.jsx
@@ -38,7 +38,7 @@ import {
SearchOutlined,
} from '@ant-design/icons';
import { useNavigate, Link as RouterLink } from 'react-router-dom';
-import { getAllNotification } from '../../../api/notification';
+import { getAllNotification, getNotificationLogByNotificationId } from '../../../api/notification';
const { Text, Paragraph, Link: AntdLink } = Typography;
@@ -47,17 +47,18 @@ const transformNotificationData = (apiData) => {
return apiData.map((item, index) => ({
id: `notification-${item.notification_error_id}-${index}`, // Unique key prefix with array index
type: item.is_read ? 'resolved' : item.is_delivered ? 'warning' : 'critical',
- title: item.error_code?.error_code_name || item.device_name || 'Unknown Error',
+ title: item.error_code_name || 'Unknown Error',
issue: item.error_code || item.error_code_name || 'Unknown Error',
description: `${item.error_code} - ${item.error_code_name || ''}`,
- timestamp:
- item.created_at ? new Date(item.created_at).toLocaleString('id-ID', {
- day: '2-digit',
- month: '2-digit',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- }) + ' WIB' : 'N/A',
+ timestamp: item.created_at
+ ? new Date(item.created_at).toLocaleString('id-ID', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ }) + ' WIB'
+ : 'N/A',
location: item.plant_sub_section_name || item.device_location || 'Location not specified',
details: item.message_error_issue || 'No details available',
link: `/verification-sparepart/${item.notification_error_id}`, // Dummy URL untuk verifikasi spare part
@@ -68,7 +69,10 @@ const transformNotificationData = (apiData) => {
errorCode: item.error_code,
solutionName: item.error_code?.solution?.[0]?.solution_name || 'N/A',
typeSolution: item.error_code?.solution?.[0]?.type_solution || 'N/A',
- pathSolution: item.error_code?.solution?.[0]?.path_document || item.error_code?.solution?.[0]?.path_solution || 'N/A',
+ pathSolution:
+ item.error_code?.solution?.[0]?.path_document ||
+ item.error_code?.solution?.[0]?.path_solution ||
+ 'N/A',
error_code: item.error_code,
}));
};
@@ -98,37 +102,6 @@ const userHistoryData = [
},
];
-// Dummy data untuk log history
-const logHistoryData = [
- {
- id: 1,
- timestamp: '04-11-2025 11:55 WIB',
- addedBy: {
- name: 'Budi Santoso',
- phone: '081122334455',
- },
- description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.',
- },
- {
- id: 2,
- timestamp: '04-11-2025 11:45 WIB',
- addedBy: {
- name: 'John Doe',
- phone: '081234567890',
- },
- description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.',
- },
- {
- id: 3,
- timestamp: '04-11-2025 11:40 WIB',
- addedBy: {
- name: 'Jane Smith',
- phone: '087654321098',
- },
- description: 'Suhu sudah coba diturunkan, namun masih belum mencapai treshold aman.',
- },
-];
-
const ListNotification = memo(function ListNotification(props) {
const [notifications, setNotifications] = useState([]);
const [activeTab, setActiveTab] = useState('all');
@@ -138,6 +111,8 @@ const ListNotification = memo(function ListNotification(props) {
const [modalContent, setModalContent] = useState(null); // 'user', 'log', 'details', or null
const [isAddingLog, setIsAddingLog] = useState(false);
const [selectedNotification, setSelectedNotification] = useState(null);
+ const [logHistoryData, setLogHistoryData] = useState([]);
+ const [logLoading, setLogLoading] = useState(false);
const [pagination, setPagination] = useState({
current_page: 1,
current_limit: 10,
@@ -281,6 +256,40 @@ const ListNotification = memo(function ListNotification(props) {
});
};
+ // Fetch log history from API
+ const fetchLogHistory = async (notificationId) => {
+ try {
+ setLogLoading(true);
+ const response = await getNotificationLogByNotificationId(notificationId);
+ if (response && response.data) {
+ // Transform API data to component format
+ const transformedLogs = response.data.map((log) => ({
+ id: log.notification_error_log_id,
+ timestamp: log.created_at
+ ? new Date(log.created_at).toLocaleString('id-ID', {
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ }) + ' WIB'
+ : 'N/A',
+ addedBy: {
+ name: log.contact_name || 'Unknown',
+ phone: log.contact_phone || 'N/A',
+ },
+ description: log.notification_error_log_description || '',
+ }));
+ setLogHistoryData(transformedLogs);
+ }
+ } catch (err) {
+ console.error('Error fetching log history:', err);
+ setLogHistoryData([]); // Set empty array on error
+ } finally {
+ setLogLoading(false);
+ }
+ };
+
const tabButtonStyle = (isActive) => ({
padding: '12px 16px',
border: 'none',
@@ -315,7 +324,6 @@ const ListNotification = memo(function ListNotification(props) {
borderColor: notification.isRead ? '#f0f0f0' : '#d6e4ff',
cursor: 'pointer',
}}
- onClick={() => handleMarkAsRead(notification.id)}
>
{
e.stopPropagation();
+
+ // Set the selected notification for the log history
+ const notificationId =
+ notification.id.split('-')[1];
+ setSelectedNotification(notification);
+
+ // Fetch log history for the selected notification
+ fetchLogHistory(notificationId);
+
setModalContent('log');
}}
/>
@@ -554,291 +571,314 @@ const ListNotification = memo(function ListNotification(props) {
const renderLogHistory = () => (
<>
-
- {/* Garis vertikal yang menyambung */}
-
+ {logLoading ? (
+
+
+
+ ) : logHistoryData.length === 0 ? (
+
+ Tidak ada log history
+
+ ) : (
+
+ {/* Garis vertikal yang menyambung */}
+
- {logHistoryData.map((log, index) => (
-
- {/* Kolom Kiri: Branch/Timeline */}
- (
+
-
-
-
- {/* Kolom Kanan: Card */}
-
-
-
-
-
-
-
-
- Added at {log.timestamp}
-
-
-
- Added by: {log.addedBy.name}
-
- {log.addedBy.phone}
-
-
-
-
-
-
- {log.description}
-
-
-
-
-
-
- ))}
-
- >
- );
-
- const renderDetailsNotification = () => {
- if (!selectedNotification) return null;
-
- const { IconComponent, color } = getIconAndColor(selectedNotification.type);
-
- return (
-
-
- {/* Kolom Kiri: Data Kompresor */}
-
-
-
-
-
-
+
+
+ {/* Kolom Kanan: Card */}
+
+
+
+
+
+
+
+
+ Added at {log.timestamp}
+
+
+
+ Added by: {log.addedBy.name}
+
+ {log.addedBy.phone}
+
+
+
+
+
+
-
-
-
-
-
{selectedNotification.title}
-
-
- {selectedNotification.issue}
-
-
+ {log.description}
+
-
-
Plant Subsection
-
{selectedNotification.subsection}
-
- Date & Time
-
-
{selectedNotification.timestamp}
-
-
-
-
-
-
- Value
-
-
- N/A
-
-
-
-
- Treshold
-
- N/A
-
-
-
-
-
-
-
- {/* Kolom Kanan: Informasi Teknis */}
-
-
-
-
-
PLC
-
{selectedNotification.plc}
-
-
-
Status
-
- {selectedNotification.status}
-
-
-
-
Tag
-
- {selectedNotification.tag}
-
-
-
-
-
-
-
-
-
-
-
-
-
- Handling Guideline
-
-
-
-
-
-
-
-
-
- Spare Part
-
-
-
-
-
- setModalContent('log')}
- >
-
-
-
- Log Activity
-
-
-
-
-
-
-
- PDF
-
- } >
+ ))}
+
+ )}
+ >
+ );
+
+ const renderDetailsNotification = () => {
+ if (!selectedNotification) return null;
+
+ const { IconComponent, color } = getIconAndColor(selectedNotification.type);
+
+ return (
+
+
+ {/* Kolom Kiri: Data Kompresor */}
+
+
+
+
+
+
+
+
+
+
+ {selectedNotification.title}
+
+
+ {selectedNotification.issue}
+
+
+
+
+
+
Plant Subsection
+
{selectedNotification.subsection}
+
+ Date & Time
+
+
{selectedNotification.timestamp}
+
+
+
+
+
+
+ Value
+
+
+ N/A
+
+
+
+
+ Treshold
+
+ N/A
+
+
+
+
+
+
+
+ {/* Kolom Kanan: Informasi Teknis */}
+
+
+
+
+
PLC
+
{selectedNotification.plc}
+
+
+
Status
+
+ {selectedNotification.status}
+
+
+
+
Tag
+
+ {selectedNotification.tag}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Handling Guideline
+
+
+
+
+
+
+
+
+
+ Spare Part
+
+
+
+
+
+ {
+ // Set the selected notification for the log history if not already set
+ if (selectedNotification) {
+ const notificationId =
+ selectedNotification.id.split('-')[1];
+ // Fetch log history for the selected notification
+ fetchLogHistory(notificationId);
+ }
+ setModalContent('log');
+ }}
+ >
+
+
+
+ Log Activity
+
+
+
+
+
+
+
+
+
+
+ PDF
+
+ }
+ >
- {logHistoryData.map((log) => (
-
+
+
+ ) : logHistoryData.length === 0 ? (
+
-
+ ) : (
+ logHistoryData.map((log) => (
+
- {log.addedBy.name}:{' '}
- {log.description}
-
-
- {log.timestamp}
-
-
- ))}
+
+ {log.addedBy.name}:{' '}
+ {log.description}
+
+
+ {log.timestamp}
+
+
+ ))
+ )}
diff --git a/src/pages/notificationDetail/IndexNotificationDetail.jsx b/src/pages/notificationDetail/IndexNotificationDetail.jsx
index 72a74cb..1a43f09 100644
--- a/src/pages/notificationDetail/IndexNotificationDetail.jsx
+++ b/src/pages/notificationDetail/IndexNotificationDetail.jsx
@@ -1,6 +1,20 @@
import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
-import { Layout, Card, Row, Col, Typography, Space, Button, Spin, Result, Input, message } from 'antd';
+import {
+ Layout,
+ Card,
+ Row,
+ Col,
+ Typography,
+ Space,
+ Button,
+ Spin,
+ Result,
+ Input,
+ message,
+ Avatar,
+ Tag,
+} from 'antd';
import {
ArrowLeftOutlined,
CloseCircleFilled,
@@ -15,10 +29,16 @@ import {
PlusOutlined,
UserOutlined,
LoadingOutlined,
+ PhoneOutlined,
+ CheckCircleOutlined,
+ SyncOutlined,
+ SendOutlined,
} from '@ant-design/icons';
-import { getNotificationDetail, createNotificationLog, getNotificationLogByNotificationId } from '../../api/notification';
-import UserHistoryModal from '../notification/component/UserHistoryModal';
-import LogHistoryCard from '../notification/component/LogHistoryCard';
+import {
+ getNotificationDetail,
+ createNotificationLog,
+ getNotificationLogByNotificationId,
+} from '../../api/notification';
const { Content } = Layout;
const { Text, Paragraph, Link } = Typography;
@@ -39,12 +59,12 @@ const transformNotificationData = (apiData) => {
description: apiData.message_error_issue || 'No details available',
timestamp: apiData.created_at
? new Date(apiData.created_at).toLocaleString('id-ID', {
- day: '2-digit',
- month: '2-digit',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- }) + ' WIB'
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ }) + ' WIB'
: 'N/A',
location: apiData.plant_sub_section_name || 'Location not specified',
details: apiData.message_error_issue || 'No details available',
@@ -62,9 +82,9 @@ const transformNotificationData = (apiData) => {
...activeSolution,
path_document: activeSolution.path_document
? activeSolution.path_document.replace(
- '/detail-notification/pdf/',
- '/notification-detail/pdf/'
- )
+ '/detail-notification/pdf/',
+ '/notification-detail/pdf/'
+ )
: activeSolution.path_document,
}, // Include the active solution data with fixed URL
error_code: errorCodeData,
@@ -77,6 +97,58 @@ const transformNotificationData = (apiData) => {
};
};
+// Dummy data baru untuk user history
+const getDummyUsers = (notification) => {
+ if (!notification) return [];
+ return [
+ {
+ id: '1',
+ name: 'John Doe',
+ phone: '081234567890',
+ status: 'delivered',
+ },
+ {
+ id: '2',
+ name: 'Jane Smith',
+ phone: '082345678901',
+ status: 'sent',
+ },
+ {
+ id: '3',
+ name: 'Bob Johnson',
+ phone: '083456789012',
+ status: 'failed',
+ },
+ {
+ id: '4',
+ name: 'Alice Brown',
+ phone: '084567890123',
+ status: 'delivered',
+ },
+ ];
+};
+
+const getStatusTag = (status) => {
+ switch (status) {
+ case 'delivered':
+ return (
+
} color="success">
+ Delivered
+
+ );
+ case 'sent':
+ return (
+
} color="processing">
+ Sent
+
+ );
+ case 'failed':
+ return
Failed;
+ default:
+ return
{status};
+ }
+};
+
const getIconAndColor = (type) => {
switch (type) {
case 'critical':
@@ -90,13 +162,13 @@ const getIconAndColor = (type) => {
}
};
-const NotificationDetailTab = () => {
- const { notificationId } = useParams(); // Mungkin perlu disesuaikan jika route berbeda
+const NotificationDetailTab = (props) => {
+ const params = useParams(); // Mungkin perlu disesuaikan jika route berbeda
+ const notificationId = props.id ?? params.notificationId;
const navigate = useNavigate();
const [notification, setNotification] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
- const [modalContent, setModalContent] = useState(null); // 'user', atau null
const [isAddingLog, setIsAddingLog] = useState(false);
// Log history states
@@ -116,12 +188,12 @@ const NotificationDetailTab = () => {
id: log.notification_error_log_id,
timestamp: log.created_at
? new Date(log.created_at).toLocaleString('id-ID', {
- day: '2-digit',
- month: '2-digit',
- year: 'numeric',
- hour: '2-digit',
- minute: '2-digit',
- }) + ' WIB'
+ day: '2-digit',
+ month: '2-digit',
+ year: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ }) + ' WIB'
: 'N/A',
addedBy: {
name: log.contact_name || 'Unknown',
@@ -251,26 +323,21 @@ const NotificationDetailTab = () => {
marginBottom: '24px',
}}
>
-
-
- }
- onClick={() => navigate('/notification')}
- style={{ paddingLeft: 0 }}
- >
- Back to notification list
-
-
-
- }
- onClick={() => setModalContent('user')}
- >
- User History
-
-
-
+ {!props.id && (
+
+
+ }
+ onClick={() => navigate('/notification')}
+ style={{ paddingLeft: 0 }}
+ >
+ Back to notification list
+
+
+
+ )}
+
{
- {/* Kolom Kanan: Log History */}
+ {/* Kolom Kanan: User History */}
-
+
+
+
+ {getDummyUsers(notification).map((user) => (
+
+
+
+
+ }
+ />
+
+
+
+
+
+ {getStatusTag(user.status)}
+ }
+ size="small"
+ onClick={(e) => {
+ e.stopPropagation();
+ console.log(
+ `Resend to ${user.name}`
+ );
+ }}
+ >
+ Resend
+
+
+
+
+
+ ))}
+
+
+
@@ -428,11 +561,8 @@ const NotificationDetailTab = () => {
-
-
+
+
{
style={{ width: '100%' }}
>
{notification.error_code?.solution &&
- notification.error_code.solution.length > 0 ? (
+ notification.error_code.solution.length > 0 ? (
<>
{notification.error_code.solution
.filter((sol) => sol.is_active) // Hanya tampilkan solusi yang aktif
@@ -532,7 +662,7 @@ const NotificationDetailTab = () => {
) : null}
{sol.type_solution === 'text' &&
- sol.text_solution ? (
+ sol.text_solution ? (
{
style={{ width: '100%' }}
>
{notification.spareparts &&
- notification.spareparts.length > 0 ? (
+ notification.spareparts.length > 0 ? (
notification.spareparts.map((sparepart, index) => (
{
color:
sparepart.sparepart_stok ===
'Available' ||
- sparepart.sparepart_stok ===
+ sparepart.sparepart_stok ===
'available'
? '#52c41a'
: '#ff4d4f',
@@ -722,7 +852,9 @@ const NotificationDetailTab = () => {
rows={2}
placeholder="Tuliskan update penanganan di sini..."
value={newLogDescription}
- onChange={(e) => setNewLogDescription(e.target.value)}
+ onChange={(e) =>
+ setNewLogDescription(e.target.value)
+ }
disabled={submitLoading}
/>
>
@@ -731,8 +863,18 @@ const NotificationDetailTab = () => {
type={isAddingLog ? 'primary' : 'dashed'}
size="small"
block
- icon={submitLoading ? : (!isAddingLog && )}
- onClick={isAddingLog ? handleSubmitLog : () => setIsAddingLog(true)}
+ icon={
+ submitLoading ? (
+
+ ) : (
+ !isAddingLog &&
+ )
+ }
+ onClick={
+ isAddingLog
+ ? handleSubmitLog
+ : () => setIsAddingLog(true)
+ }
loading={submitLoading}
disabled={submitLoading}
>
@@ -780,12 +922,6 @@ const NotificationDetailTab = () => {
-
- setModalContent(null)}
- notificationData={notification}
- />
);
};
diff --git a/src/pages/report/report/component/ListReport.jsx b/src/pages/report/report/component/ListReport.jsx
index 693a18e..f48f51e 100644
--- a/src/pages/report/report/component/ListReport.jsx
+++ b/src/pages/report/report/component/ListReport.jsx
@@ -9,6 +9,8 @@ import {
import { getAllPlantSection } from '../../../../api/master-plant-section';
import jsPDF from 'jspdf';
import autoTable from 'jspdf-autotable';
+import ExcelJS from 'exceljs';
+import { saveAs } from 'file-saver';
const { Text } = Typography;
@@ -54,9 +56,9 @@ const ListReport = memo(function ListReport(props) {
};
const fetchData = async (page = 1, pageSize = 10, showModal = false) => {
- if (!plantSubSection) {
- return;
- }
+ // if (!plantSubSection) {
+ // return;
+ // }
if (showModal) {
setIsLoadingModal(true);
@@ -195,8 +197,34 @@ const ListReport = memo(function ListReport(props) {
fetchData(pagination.current, pagination.pageSize, false);
};
- const handleSearch = () => {
- fetchData(1, pagination.pageSize, true);
+ const handleSearch = async () => {
+ setIsLoadingModal(true);
+
+ try {
+ const formattedDateStart = startDate.format('YYYY-MM-DD');
+ const formattedDateEnd = endDate.format('YYYY-MM-DD');
+
+ const params = new URLSearchParams({
+ plant_sub_section_id: plantSubSection,
+ from: formattedDateStart,
+ to: formattedDateEnd,
+ interval: periode,
+ page: 1,
+ limit: 1000,
+ });
+
+ const pivotResponse = await getAllHistoryValueReportPivot(params);
+
+ // Jika response sukses, proses data
+ if (pivotResponse && pivotResponse.data) {
+ await fetchData(1, pagination.pageSize, false);
+ }
+ } catch (error) {
+ console.error('Error fetching data:', error);
+ // Error akan ditangkap oleh api-request.js dan muncul Swal otomatis
+ } finally {
+ setIsLoadingModal(false);
+ }
};
const handleReset = () => {
@@ -247,6 +275,168 @@ const ListReport = memo(function ListReport(props) {
{ value: 120, label: '2 Hour', disabled: false },
];
+ const exportToExcel = async () => {
+ if (pivotData.length === 0) {
+ alert('No data to export');
+ return;
+ }
+
+ const tagMapping = {};
+ valueReportData.forEach(item => {
+ if (item.tag_name && item.tag_number) {
+ tagMapping[item.tag_name] = item.tag_number;
+ }
+ });
+
+ const selectedSection = plantSubSectionList.find(
+ item => item.plant_sub_section_id === plantSubSection
+ );
+ const sectionName = selectedSection ? selectedSection.plant_sub_section_name : 'Unknown';
+
+ // Buat struktur pivot yang sama seperti di tabel
+ const timeMap = new Map();
+ const tagSet = new Set();
+
+ pivotData.forEach((row) => {
+ const tagName = row.id;
+ tagSet.add(tagName);
+
+ const dataPoints = row.data || [];
+ dataPoints.forEach((item) => {
+ if (item && typeof item === 'object' && 'x' in item && 'y' in item) {
+ const datetime = item.x;
+ if (!timeMap.has(datetime)) {
+ timeMap.set(datetime, {});
+ }
+ timeMap.get(datetime)[tagName] = item.y;
+ }
+ });
+ });
+
+ const sortedTimes = Array.from(timeMap.keys()).sort();
+ const sortedTags = Array.from(tagSet).sort();
+
+ const pivotTableData = sortedTimes.map((datetime) => {
+ const rowData = {
+ datetime: datetime,
+ };
+
+ sortedTags.forEach((tagName) => {
+ rowData[tagName] = timeMap.get(datetime)[tagName];
+ });
+
+ return rowData;
+ });
+
+ console.log('Excel Pivot data:', pivotTableData.slice(0, 5));
+ console.log('Total rows for Excel:', pivotTableData.length);
+
+ const workbook = new ExcelJS.Workbook();
+ const ws = workbook.addWorksheet('Pivot Report');
+
+ // Buat header info (3 baris pertama)
+ ws.addRow(['PT. PUPUK INDONESIA UTILITAS']);
+ ws.addRow(['GRESIK GAS COGENERATION PLANT']);
+ ws.addRow([`${sectionName}`]);
+ ws.addRow([]); // Baris kosong sebagai pemisah
+
+ // Buat header kolom dengan tag number
+ const headerRow = [
+ 'Datetime',
+ ...sortedTags.map(tag => tagMapping[tag] || tag)
+ ];
+ ws.addRow(headerRow);
+
+ // Buat data rows - PERBAIKAN: Simpan sebagai number murni
+ pivotTableData.forEach((rowData) => {
+ const row = [dayjs(rowData.datetime).format('DD-MM-YYYY HH:mm')];
+ sortedTags.forEach((tagName) => {
+ const value = rowData[tagName];
+ // Simpan sebagai number, bukan string
+ if (value !== undefined && value !== null) {
+ row.push(Number(value));
+ } else {
+ row.push('-');
+ }
+ });
+ ws.addRow(row);
+ });
+
+ // Set column widths
+ ws.getColumn(1).width = 18; // Datetime column
+ for (let i = 2; i <= sortedTags.length + 1; i++) {
+ ws.getColumn(i).width = 12; // Tag columns
+ }
+
+ // Merge cells untuk header info
+ const totalCols = sortedTags.length + 1;
+ ws.mergeCells(1, 1, 1, totalCols); // Baris 1
+ ws.mergeCells(2, 1, 2, totalCols); // Baris 2
+ ws.mergeCells(3, 1, 3, totalCols); // Baris 3
+
+ // Style untuk header info (3 baris pertama - bold dan center)
+ for (let i = 1; i <= 3; i++) {
+ const cell = ws.getCell(i, 1);
+ cell.font = { bold: true, size: 12 };
+ cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
+ }
+
+ // Style untuk header kolom (bold, background color, center, border)
+ const headerRowIndex = 5; // Baris header
+ for (let col = 1; col <= totalCols; col++) {
+ const cell = ws.getCell(headerRowIndex, col);
+ cell.font = { bold: true, size: 11 };
+ cell.fill = {
+ type: 'pattern',
+ pattern: 'solid',
+ fgColor: { argb: 'FFDCDCDC' }
+ };
+ cell.alignment = { horizontal: 'center', vertical: 'middle', wrapText: true };
+ cell.border = {
+ top: { style: 'thin', color: { argb: 'FF000000' } },
+ bottom: { style: 'thin', color: { argb: 'FF000000' } },
+ left: { style: 'thin', color: { argb: 'FF000000' } },
+ right: { style: 'thin', color: { argb: 'FF000000' } }
+ };
+ }
+
+ // Style untuk data cells (border dan alignment) - PERBAIKAN: Format number dengan 2 desimal
+ for (let row = headerRowIndex + 1; row <= ws.rowCount; row++) {
+ for (let col = 1; col <= totalCols; col++) {
+ const cell = ws.getCell(row, col);
+
+ cell.alignment = {
+ horizontal: 'center',
+ vertical: 'middle',
+ wrapText: true
+ };
+ cell.border = {
+ top: { style: 'thin', color: { argb: 'FF000000' } },
+ bottom: { style: 'thin', color: { argb: 'FF000000' } },
+ left: { style: 'thin', color: { argb: 'FF000000' } },
+ right: { style: 'thin', color: { argb: 'FF000000' } }
+ };
+
+ // Format number dengan 2 desimal untuk kolom value (kolom 2 dst)
+ if (col > 1) {
+ const cellValue = cell.value;
+ // Hanya set format number jika cell berisi angka
+ if (typeof cellValue === 'number') {
+ cell.numFmt = '0.00';
+ }
+ }
+ }
+ }
+
+ // Generate file name
+ const fileName = `Report_Pivot_${startDate.format('DD-MM-YYYY')}_to_${endDate.format('DD-MM-YYYY')}.xlsx`;
+
+ // Save file
+ const buffer = await workbook.xlsx.writeBuffer();
+ const blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+ saveAs(blob, fileName);
+ };
+
const exportToPDF = async () => {
if (pivotData.length === 0) {
alert('No data to export');
@@ -393,7 +583,7 @@ const ListReport = memo(function ListReport(props) {
doc.setFontSize(9);
doc.setFont('helvetica', 'bold');
doc.setFontSize(10);
- doc.text(`Plant Section : ${sectionName}`, marginLeft + col1Width + col2Width / 2, 41, { align: 'center' });
+ doc.text(`${sectionName}`, marginLeft + col1Width + col2Width / 2, 38, { align: 'center' });
};
// Hitung total kolom tag chunks
@@ -534,7 +724,7 @@ const ListReport = memo(function ListReport(props) {
autoTable(doc, {
head: [headerRow],
body: pdfRows,
- startY: isFirstPage ? 50 : 15,
+ startY: isFirstPage ? 43 : 15,
theme: 'grid',
rowPageBreak: 'avoid',
styles: {
@@ -542,7 +732,7 @@ const ListReport = memo(function ListReport(props) {
cellPadding: 1.5,
minCellHeight: 8,
lineColor: [0, 0, 0],
- lineWidth: 0.1,
+ lineWidth: 0.5,
halign: 'center',
valign: 'middle',
overflow: 'linebreak',
@@ -554,7 +744,7 @@ const ListReport = memo(function ListReport(props) {
halign: 'center',
valign: 'middle',
lineColor: [0, 0, 0],
- lineWidth: 0.3,
+ lineWidth: 0.5,
},
columnStyles: {
0: {
@@ -694,11 +884,23 @@ const ListReport = memo(function ListReport(props) {
type="primary"
icon={}
onClick={exportToPDF}
- disabled={false}
+ disabled={pivotData.length === 0}
+ style={{ backgroundColor: '#1890ff', borderColor: '#1890ff' }}
>
Export PDF
+
+ }
+ onClick={exportToExcel}
+ disabled={pivotData.length === 0}
+ style={{ backgroundColor: '#28a745', borderColor: '#28a745' }}
+ >
+ Export Excel
+
+