diff --git a/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx b/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx
new file mode 100644
index 0000000..791c59b
--- /dev/null
+++ b/src/pages/master/brandDevice/component/ErrorCodeSimpleForm.jsx
@@ -0,0 +1,233 @@
+import {
+ Form,
+ Input,
+ Switch,
+ Upload,
+ Button,
+ Typography,
+ message,
+ ConfigProvider,
+} from 'antd';
+import { UploadOutlined } from '@ant-design/icons';
+import { uploadFile } from '../../../../api/file-uploads';
+
+const { Text } = Typography;
+
+const ErrorCodeSimpleForm = ({
+ errorCodeForm,
+ isErrorCodeFormReadOnly = false,
+ errorCodeIcon,
+ onErrorCodeIconUpload,
+ onErrorCodeIconRemove,
+ onAddErrorCode,
+}) => {
+ const statusValue = Form.useWatch('status', errorCodeForm);
+
+ const handleIconUpload = async (file) => {
+ // Check if file is an image
+ const isImage = file.type.startsWith('image/');
+ if (!isImage) {
+ message.error('You can only upload image files!');
+ return Upload.LIST_IGNORE;
+ }
+
+ // Check file size (max 2MB)
+ const isLt2M = file.size / 1024 / 1024 < 2;
+ if (!isLt2M) {
+ message.error('Image must be smaller than 2MB!');
+ return Upload.LIST_IGNORE;
+ }
+
+ try {
+ const fileExtension = file.name.split('.').pop().toLowerCase();
+ const isImageFile = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(
+ fileExtension
+ );
+ const fileType = isImageFile ? 'image' : 'pdf';
+ const folder = 'images';
+
+ const uploadResponse = await uploadFile(file, folder);
+ const iconPath = uploadResponse.data?.path_icon || uploadResponse.data?.path_solution || '';
+
+ if (iconPath) {
+ onErrorCodeIconUpload({
+ name: file.name,
+ uploadPath: iconPath,
+ fileExtension,
+ isImage: isImageFile,
+ size: file.size,
+ });
+ message.success(`${file.name} uploaded successfully!`);
+ } else {
+ message.error(`Failed to upload ${file.name}`);
+ }
+ } catch (error) {
+ console.error('Error uploading icon:', error);
+ message.error(`Failed to upload ${file.name}`);
+ }
+ };
+
+ const handleIconRemove = () => {
+ onErrorCodeIconRemove();
+ };
+
+ return (
+ <>
+ {/* Status Switch */}
+
+
+
+
+
+
+ {statusValue ? 'Active' : 'Inactive'}
+
+
+
+
+ {/* Error Code */}
+
+
+
+
+ {/* Error Name */}
+
+
+
+
+ {/* Error Description */}
+
+
+
+
+ {/* Color and Icon in same row */}
+
+
+
+
+
+
+
+ {!isErrorCodeFormReadOnly ? (
+
+ } style={{ width: '100%' }}>
+ Upload Icon
+
+
+ ) : (
+
+ No upload allowed
+
+ )}
+
+
+
+ {errorCodeIcon && (
+
+
+

+
+ {errorCodeIcon.name}
+
+
+ Size: {(errorCodeIcon.size / 1024).toFixed(1)} KB
+
+
+ {!isErrorCodeFormReadOnly && (
+
+ )}
+
+
+ )}
+
+
+ {/* Add Error Code Button */}
+ {!isErrorCodeFormReadOnly && (
+
+
+
+
+
+ )}
+ >
+ );
+};
+
+export default ErrorCodeSimpleForm;
\ No newline at end of file
diff --git a/src/pages/master/brandDevice/component/SolutionField.jsx b/src/pages/master/brandDevice/component/SolutionField.jsx
index dca30ec..6ec931e 100644
--- a/src/pages/master/brandDevice/component/SolutionField.jsx
+++ b/src/pages/master/brandDevice/component/SolutionField.jsx
@@ -139,7 +139,10 @@ const SolutionField = ({
icon={}
onClick={() => onRemove(fieldId)}
disabled={isReadOnly}
- style={{ borderColor: '#ff4d4f' }}
+ style={{
+ borderColor: '#ff4d4f',
+ color: '#ff4d4f'
+ }}
/>
@@ -161,7 +164,7 @@ const SolutionField = ({
/>
- {(watchedStatus ?? true) ? 'Active' : 'Non Active'}
+ {(watchedStatus ?? true) ? 'Active' : 'Inactive'}
diff --git a/src/pages/master/brandDevice/component/SolutionFieldNew.jsx b/src/pages/master/brandDevice/component/SolutionFieldNew.jsx
new file mode 100644
index 0000000..792db80
--- /dev/null
+++ b/src/pages/master/brandDevice/component/SolutionFieldNew.jsx
@@ -0,0 +1,243 @@
+import React, { useState, useEffect } from 'react';
+import { Form, Input, Button, Switch, Radio, Upload, Typography, Space } from 'antd';
+import { DeleteOutlined, UploadOutlined, EyeOutlined } from '@ant-design/icons';
+import { uploadFile, getFolderFromFileType } from '../../../../api/file-uploads';
+import { NotifAlert } from '../../../../components/Global/ToastNotif';
+
+const { Text } = Typography;
+const { TextArea } = Input;
+
+const SolutionFieldNew = ({
+ fieldKey,
+ fieldName,
+ index,
+ solutionType,
+ solutionStatus,
+ isReadOnly = false,
+ canRemove = true,
+ onTypeChange,
+ onStatusChange,
+ onRemove,
+ onFileUpload,
+ onFileView,
+ fileList = []
+}) => {
+ const [currentStatus, setCurrentStatus] = useState(solutionStatus ?? true);
+
+ // Watch form values
+ const getFieldValue = () => {
+ try {
+ const form = document.querySelector(`[data-field="${fieldName}"]`)?.form;
+ if (form) {
+ const formData = new FormData(form);
+ return formData.get(`${fieldName}.status`) === 'on';
+ }
+ return currentStatus;
+ } catch {
+ return currentStatus;
+ }
+ };
+
+ useEffect(() => {
+ setCurrentStatus(solutionStatus ?? true);
+ }, [solutionStatus]);
+ const handleFileUpload = async (file) => {
+ try {
+ const isAllowedType = [
+ 'application/pdf',
+ 'image/jpeg',
+ 'image/png',
+ 'image/gif',
+ ].includes(file.type);
+
+ if (!isAllowedType) {
+ NotifAlert({
+ icon: 'error',
+ title: 'Error',
+ message: `${file.name} bukan file PDF atau gambar yang diizinkan.`,
+ });
+ return;
+ }
+
+ const fileExtension = file.name.split('.').pop().toLowerCase();
+ const isImage = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(fileExtension);
+ const fileType = isImage ? 'image' : 'pdf';
+ const folder = getFolderFromFileType(fileType);
+
+ const uploadResponse = await uploadFile(file, folder);
+ const actualPath = uploadResponse.data?.path_solution || '';
+
+ if (actualPath) {
+ // Store the file info with the solution field
+ file.uploadPath = actualPath;
+ file.solutionId = fieldKey;
+ file.type_solution = fileType;
+ onFileUpload(file);
+ NotifAlert({
+ icon: 'success',
+ title: 'Berhasil',
+ message: `${file.name} berhasil diupload!`,
+ });
+ } else {
+ NotifAlert({
+ icon: 'error',
+ title: 'Gagal',
+ message: `Gagal mengupload ${file.name}`,
+ });
+ }
+ } catch (error) {
+ console.error('Error uploading file:', error);
+ NotifAlert({
+ icon: 'error',
+ title: 'Error',
+ message: `Gagal mengupload ${file.name}. Silakan coba lagi.`,
+ });
+ }
+ };
+
+ const renderSolutionContent = () => {
+ if (solutionType === 'text') {
+ return (
+
+
+
+ );
+ }
+
+ if (solutionType === 'file') {
+ const currentFiles = fileList.filter(file => file.solutionId === fieldKey);
+
+ return (
+
+
+
+ }
+ disabled={isReadOnly}
+ style={{ width: '100%' }}
+ >
+ Upload File (PDF/Image)
+
+
+
+
+ {currentFiles.length > 0 && (
+
+ {currentFiles.map((file, index) => (
+
+
+ {file.name}
+
+ ({(file.size / 1024).toFixed(1)} KB)
+
+
+
}
+ onClick={() => onFileView(file.uploadPath, file.type_solution)}
+ />
+
+ ))}
+
+ )}
+
+ );
+ }
+
+ return null;
+ };
+
+ return (
+
+
+
Solution #{index + 1}
+
+
+
+
+
+
+
+ {
+ onStatusChange(fieldKey, checked);
+ setCurrentStatus(checked);
+ }}
+ style={{
+ backgroundColor: currentStatus ? '#23A55A' : '#bfbfbf'
+ }}
+ />
+
+
+ {currentStatus ? 'Active' : 'Inactive'}
+
+
+
+ {canRemove && !isReadOnly && (
+ }
+ onClick={onRemove}
+ />
+ )}
+
+
+
+
+ onTypeChange(fieldKey, e.target.value)}
+ disabled={isReadOnly}
+ >
+ Text Solution
+ File Solution
+
+
+
+ {renderSolutionContent()}
+
+ );
+};
+
+export default SolutionFieldNew;
\ No newline at end of file
diff --git a/src/pages/master/brandDevice/component/SolutionForm.jsx b/src/pages/master/brandDevice/component/SolutionForm.jsx
new file mode 100644
index 0000000..0e3f457
--- /dev/null
+++ b/src/pages/master/brandDevice/component/SolutionForm.jsx
@@ -0,0 +1,80 @@
+import React from 'react';
+import { Form, Card, Typography, Divider, Button } from 'antd';
+import { PlusOutlined } from '@ant-design/icons';
+import SolutionFieldNew from './SolutionFieldNew';
+
+const { Text } = Typography;
+
+const SolutionForm = ({
+ solutionForm,
+ solutionFields,
+ solutionTypes,
+ solutionStatuses,
+ fileList,
+ solutionsToDelete,
+ firstSolutionValid,
+ onAddSolutionField,
+ onRemoveSolutionField,
+ onSolutionTypeChange,
+ onSolutionStatusChange,
+ onSolutionFileUpload,
+ onFileView,
+ isReadOnly = false,
+ onAddSolution
+}) => {
+ return (
+
+ );
+};
+
+export default SolutionForm;
\ No newline at end of file
diff --git a/src/pages/master/brandDevice/component/SparepartForm.jsx b/src/pages/master/brandDevice/component/SparepartForm.jsx
new file mode 100644
index 0000000..a6c70a1
--- /dev/null
+++ b/src/pages/master/brandDevice/component/SparepartForm.jsx
@@ -0,0 +1,259 @@
+import React, { useState, useEffect } from 'react';
+import { Form, Input, Button, Divider, Typography, Switch, Space, Card, Upload, message } from 'antd';
+import { PlusOutlined, DeleteOutlined, UploadOutlined } from '@ant-design/icons';
+import { uploadFile } from '../../../../api/file-uploads';
+
+const { Text } = Typography;
+
+const SparepartForm = ({
+ sparepartForm,
+ sparepartFields,
+ onAddSparepartField,
+ onRemoveSparepartField,
+ onSparepartTypeChange,
+ onSparepartStatusChange,
+ onSparepartImageUpload,
+ onSparepartImageRemove,
+ sparepartImages = {},
+ isReadOnly = false
+}) => {
+ const [fieldStatuses, setFieldStatuses] = useState({});
+
+ // Watch form values for each field
+ const getFieldValue = (fieldName) => {
+ try {
+ const values = sparepartForm?.getFieldsValue();
+ return values?.sparepart_items?.[fieldName]?.status ?? true;
+ } catch {
+ return true;
+ }
+ };
+
+ useEffect(() => {
+ // Update field statuses when form changes
+ const newStatuses = {};
+ sparepartFields.forEach(field => {
+ newStatuses[field.key] = getFieldValue(field.key);
+ });
+ setFieldStatuses(newStatuses);
+ }, [sparepartFields, sparepartForm]);
+
+ const handleImageUpload = async (fieldKey, file) => {
+ // Check if file is an image
+ const isImage = file.type.startsWith('image/');
+ if (!isImage) {
+ message.error('You can only upload image files!');
+ return Upload.LIST_IGNORE;
+ }
+
+ // Check file size (max 2MB)
+ const isLt2M = file.size / 1024 / 1024 < 2;
+ if (!isLt2M) {
+ message.error('Image must be smaller than 2MB!');
+ return Upload.LIST_IGNORE;
+ }
+
+ try {
+ const fileExtension = file.name.split('.').pop().toLowerCase();
+ const isImageFile = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp'].includes(fileExtension);
+ const fileType = isImageFile ? 'image' : 'pdf';
+ const folder = 'images';
+
+ const uploadResponse = await uploadFile(file, folder);
+ const imagePath = uploadResponse.data?.path_icon || uploadResponse.data?.path_solution || '';
+
+ if (imagePath) {
+ onSparepartImageUpload && onSparepartImageUpload(fieldKey, {
+ name: file.name,
+ uploadPath: imagePath,
+ fileExtension,
+ isImage: isImageFile,
+ size: file.size,
+ });
+ message.success(`${file.name} uploaded successfully!`);
+ } else {
+ message.error(`Failed to upload ${file.name}`);
+ }
+ } catch (error) {
+ console.error('Error uploading image:', error);
+ message.error(`Failed to upload ${file.name}`);
+ }
+ };
+
+ const handleImageRemove = (fieldKey) => {
+ onSparepartImageRemove && onSparepartImageRemove(fieldKey);
+ };
+ return (
+
+
+ {isReadOnly ? 'Sparepart Details' : 'Tambah Sparepart'}
+
+
+
+ }
+ >
+
+
+
+
+ {/* Description */}
+
+
+
+
+ {/* Image Upload */}
+
+ {!isReadOnly ? (
+ handleImageUpload(field.key, file)}
+ showUploadList={false}
+ accept="image/*"
+ style={{ width: '100%' }}
+ >
+ } style={{ width: '100%' }}>
+ Upload Sparepart Image
+
+
+ ) : (
+
+ No upload allowed
+
+ )}
+
+ {sparepartImages[field.key] && (
+
+
+

+
+ {sparepartImages[field.key].name}
+
+
+ Size: {(sparepartImages[field.key].size / 1024).toFixed(1)} KB
+
+
+ {!isReadOnly && (
+
+ )}
+
+
+ )}
+
+
+ {/* Delete Button */}
+ {!isReadOnly && sparepartFields.length > 1 && (
+
+ }
+ onClick={() => onRemoveSparepartField(field.key)}
+ style={{
+ borderColor: '#ff4d4f',
+ color: '#ff4d4f'
+ }}
+ >
+ Remove
+
+
+ )}
+
+
+ ))}
+
+ {!isReadOnly && (
+
+
+
+ )}
+
+ {!isReadOnly && (
+
+
+ * Add at least one sparepart for this error code.
+
+
+ )}
+
+
+ );
+};
+
+export default SparepartForm;
\ No newline at end of file
diff --git a/src/pages/master/brandDevice/hooks/solution.js b/src/pages/master/brandDevice/hooks/solution.js
new file mode 100644
index 0000000..4d83a54
--- /dev/null
+++ b/src/pages/master/brandDevice/hooks/solution.js
@@ -0,0 +1,166 @@
+import { useState } from 'react';
+
+export const useSolutionLogic = (solutionForm) => {
+ const [solutionFields, setSolutionFields] = useState([
+ { name: ['solution_items', 0], key: 0 }
+ ]);
+ const [solutionTypes, setSolutionTypes] = useState({ 0: 'text' });
+ const [solutionStatuses, setSolutionStatuses] = useState({ 0: true });
+ const [solutionsToDelete, setSolutionsToDelete] = useState([]);
+
+ const handleAddSolutionField = () => {
+ const newKey = Date.now(); // Use timestamp for unique key
+ const newField = { name: ['solution_items', newKey], key: newKey };
+
+ setSolutionFields(prev => [...prev, newField]);
+ setSolutionTypes(prev => ({ ...prev, [newKey]: 'text' }));
+ setSolutionStatuses(prev => ({ ...prev, [newKey]: true }));
+
+ // Set default values for the new field
+ setTimeout(() => {
+ solutionForm.setFieldValue(['solution_items', newKey, 'name'], '');
+ solutionForm.setFieldValue(['solution_items', newKey, 'type'], 'text');
+ solutionForm.setFieldValue(['solution_items', newKey, 'text'], '');
+ }, 0);
+ };
+
+ const handleRemoveSolutionField = (key) => {
+ if (solutionFields.length <= 1) {
+ return; // Keep at least one solution field
+ }
+
+ setSolutionFields(prev => prev.filter(field => field.key !== key));
+
+ // Clean up type and status
+ const newTypes = { ...solutionTypes };
+ const newStatuses = { ...solutionStatuses };
+ delete newTypes[key];
+ delete newStatuses[key];
+
+ setSolutionTypes(newTypes);
+ setSolutionStatuses(newStatuses);
+ };
+
+ const handleSolutionTypeChange = (key, value) => {
+ setSolutionTypes(prev => ({ ...prev, [key]: value }));
+ };
+
+ const handleSolutionStatusChange = (key, value) => {
+ setSolutionStatuses(prev => ({ ...prev, [key]: value }));
+ };
+
+ const resetSolutionFields = () => {
+ setSolutionFields([{ name: ['solution_items', 0], key: 0 }]);
+ setSolutionTypes({ 0: 'text' });
+ setSolutionStatuses({ 0: true });
+
+ // Reset form values
+ solutionForm.resetFields();
+ solutionForm.setFieldsValue({
+ solution_status_0: true,
+ solution_type_0: 'text',
+ });
+ };
+
+ const checkFirstSolutionValid = () => {
+ const values = solutionForm.getFieldsValue();
+ const firstSolution = values.solution_items?.[0];
+
+ if (!firstSolution || !firstSolution.name || firstSolution.name.trim() === '') {
+ return false;
+ }
+
+ if (solutionTypes[0] === 'text' && (!firstSolution.text || firstSolution.text.trim() === '')) {
+ return false;
+ }
+
+ return true;
+ };
+
+ const getSolutionData = () => {
+ const values = solutionForm.getFieldsValue();
+
+ const result = solutionFields.map(field => {
+ const key = field.key;
+ // Access form values using the key from field.name (AntD stores with comma)
+ const solutionPath = field.name.join(',');
+ const solution = values[solutionPath];
+
+ const validSolution = solution && solution.name && solution.name.trim() !== '';
+
+ if (validSolution) {
+ return {
+ solution_name: solution.name || 'Default Solution',
+ type_solution: solutionTypes[key] || 'text',
+ text_solution: solution.text || '',
+ path_solution: solution.file || '',
+ is_active: solution.status !== false, // Use form value directly
+ };
+ }
+ return null;
+ }).filter(Boolean);
+
+ return result;
+ };
+
+ const setSolutionsForExistingRecord = (solutions, form) => {
+ if (!solutions || solutions.length === 0) return;
+
+ const newFields = solutions.map((solution, index) => ({
+ name: ['solution_items', solution.id || index],
+ key: solution.id || index
+ }));
+
+ setSolutionFields(newFields);
+
+ // Set solution values
+ const solutionsValues = {};
+ const newTypes = {};
+ const newStatuses = {};
+
+ solutions.forEach((solution, index) => {
+ const key = solution.id || index;
+ solutionsValues[key] = {
+ name: solution.solution_name || '',
+ type: solution.type_solution || 'text',
+ text: solution.text_solution || '',
+ file: solution.path_solution || '',
+ };
+ newTypes[key] = solution.type_solution || 'text';
+ newStatuses[key] = solution.is_active !== false;
+ });
+
+ // Set all form values at once
+ const formValues = {};
+ Object.keys(solutionsValues).forEach(key => {
+ const solution = solutionsValues[key];
+ formValues[`solution_items,${key}`] = {
+ name: solution.name,
+ type: solution.type,
+ text: solution.text,
+ file: solution.file,
+ status: solution.is_active !== false
+ };
+ });
+
+ form.setFieldsValue(formValues);
+ 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/master/brandDevice/hooks/sparepart.js b/src/pages/master/brandDevice/hooks/sparepart.js
new file mode 100644
index 0000000..09ad295
--- /dev/null
+++ b/src/pages/master/brandDevice/hooks/sparepart.js
@@ -0,0 +1,115 @@
+import { useState } from 'react';
+
+export const useSparepartLogic = (sparepartForm) => {
+ const [sparepartFields, setSparepartFields] = useState([
+ { name: ['sparepart_items', 0], key: 0 }
+ ]);
+ const [sparepartTypes, setSparepartTypes] = useState({ 0: 'required' });
+ const [sparepartStatuses, setSparepartStatuses] = useState({ 0: true });
+
+ const handleAddSparepartField = () => {
+ const newKey = Date.now(); // Use timestamp for unique key
+ const newField = { name: ['sparepart_items', newKey], key: newKey };
+
+ setSparepartFields(prev => [...prev, newField]);
+ setSparepartTypes(prev => ({ ...prev, [newKey]: 'required' }));
+ setSparepartStatuses(prev => ({ ...prev, [newKey]: true }));
+
+ // Set default values for the new field
+ setTimeout(() => {
+ sparepartForm.setFieldValue(['sparepart_items', newKey, 'type'], 'required');
+ sparepartForm.setFieldValue(['sparepart_items', newKey, 'quantity'], 1);
+ }, 0);
+ };
+
+ const handleRemoveSparepartField = (key) => {
+ if (sparepartFields.length <= 1) {
+ return; // Keep at least one sparepart field
+ }
+
+ setSparepartFields(prev => prev.filter(field => field.key !== key));
+
+ // Clean up type and status
+ const newTypes = { ...sparepartTypes };
+ const newStatuses = { ...sparepartStatuses };
+ delete newTypes[key];
+ delete newStatuses[key];
+
+ setSparepartTypes(newTypes);
+ setSparepartStatuses(newStatuses);
+ };
+
+ const handleSparepartTypeChange = (key, value) => {
+ setSparepartTypes(prev => ({ ...prev, [key]: value }));
+ };
+
+ const handleSparepartStatusChange = (key, value) => {
+ setSparepartStatuses(prev => ({ ...prev, [key]: value }));
+ };
+
+ const resetSparepartFields = () => {
+ setSparepartFields([{ name: ['sparepart_items', 0], key: 0 }]);
+ setSparepartTypes({ 0: 'required' });
+ setSparepartStatuses({ 0: true });
+
+ // Reset form values
+ sparepartForm.resetFields();
+ sparepartForm.setFieldsValue({
+ sparepart_status_0: true,
+ sparepart_type_0: 'required',
+ });
+ };
+
+ const getSparepartData = () => {
+ const values = sparepartForm.getFieldsValue();
+ return sparepartFields.map(field => {
+ const key = field.key;
+ const sparepartPath = field.name.join(',');
+ const sparepart = values[sparepartPath];
+
+ return sparepart && sparepart.name && sparepart.name.trim() !== '' ? {
+ name: sparepart.name || '',
+ description: sparepart.description || '',
+ is_active: sparepart.status !== false,
+ } : null;
+ }).filter(Boolean);
+ };
+
+ const setSparepartForExistingRecord = (spareparts, form) => {
+ if (!spareparts || spareparts.length === 0) return;
+
+ const newFields = spareparts.map((sparepart, index) => ({
+ name: ['sparepart_items', sparepart.id || index],
+ key: sparepart.id || index
+ }));
+
+ setSparepartFields(newFields);
+
+ // Set sparepart values
+ const formValues = {};
+ Object.keys(spareparts).forEach(index => {
+ const key = spareparts[index].id || index;
+ const sparepart = spareparts[index];
+ formValues[`sparepart_items,${key}`] = {
+ name: sparepart.name || '',
+ description: sparepart.description || '',
+ status: sparepart.is_active !== false,
+ };
+ });
+
+ form.setFieldsValue(formValues);
+ };
+
+ return {
+ sparepartFields,
+ sparepartTypes,
+ sparepartStatuses,
+ handleAddSparepartField,
+ handleRemoveSparepartField,
+ handleSparepartTypeChange,
+ handleSparepartStatusChange,
+ resetSparepartFields,
+ getSparepartData,
+ setSparepartForExistingRecord,
+ };
+};
\ No newline at end of file