From ccbc642075ad698eaf03689292a723c0597d63d9 Mon Sep 17 00:00:00 2001 From: hagiye Date: Sat, 30 May 2026 11:19:40 +0300 Subject: [PATCH] rure engine --- apps/edr-freight-web/backoffice/package.json | 1 + apps/edr-freight-web/backoffice/src/App.tsx | 59 +- .../ruleEngine/ContractType copy 2.tsx | 718 +++++++++++ .../ruleEngine/ContractType copy 3.tsx | 366 ++++++ .../ruleEngine/ContractType copy.tsx | 221 ++++ .../components/ruleEngine/ContractType.tsx | 1054 +++++++++++++---- .../src/pages/ruleEngine/RuleEngine.tsx | 18 +- .../src/services/rule.engine/cargoType.ts | 5 + ....timestamp-1779977728892-a0ba1f5e8e96b.mjs | 24 + pnpm-lock.yaml | 26 + 10 files changed, 2257 insertions(+), 235 deletions(-) create mode 100644 apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy 2.tsx create mode 100644 apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy 3.tsx create mode 100644 apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy.tsx create mode 100644 apps/edr-freight-web/backoffice/src/services/rule.engine/cargoType.ts create mode 100644 apps/edr-freight-web/backoffice/vite.config.ts.timestamp-1779977728892-a0ba1f5e8e96b.mjs diff --git a/apps/edr-freight-web/backoffice/package.json b/apps/edr-freight-web/backoffice/package.json index 7ae00f78b..2a10e1d03 100644 --- a/apps/edr-freight-web/backoffice/package.json +++ b/apps/edr-freight-web/backoffice/package.json @@ -24,6 +24,7 @@ "radix-ui": "^1.4.3", "react": "19.2.6", "react-dom": "19.2.6", + "react-hot-toast": "^2.6.0", "react-router-dom": "^6.27.0", "recharts": "^3.8.1", "tailwind-merge": "^3.6.0", diff --git a/apps/edr-freight-web/backoffice/src/App.tsx b/apps/edr-freight-web/backoffice/src/App.tsx index f4bf0b950..8bf91a9a8 100644 --- a/apps/edr-freight-web/backoffice/src/App.tsx +++ b/apps/edr-freight-web/backoffice/src/App.tsx @@ -1,4 +1,5 @@ import { Navigate, Outlet, Route, Routes, useLocation, useNavigate } from "react-router-dom"; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { DashboardLayout, type SidebarItem } from "@edr/ui-common"; import { LayoutDashboard, Network, Settings } from "lucide-react"; @@ -12,6 +13,17 @@ import UserManagementPage from "./pages/dashboard/user-management/UserManagement import LoadingScreen from "./components/LoadingScreen"; import { RuleEnginePage } from "./pages/ruleEngine/RuleEngine"; +// Create a QueryClient instance +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: 1, + refetchOnWindowFocus: false, + staleTime: 5 * 60 * 1000, // 5 minutes + }, + }, +}); + const sidebarItems: SidebarItem[] = [ { label: "Overview", @@ -76,33 +88,34 @@ const App = () => { if (!user) { return ( - - } /> - } /> - + + + } /> + } /> + + ); } return ( - - } /> - } /> - }> - } /> - } /> - - } /> - - } /> - } /> - } /> - - } /> - } /> - - } /> - + + + } /> + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + + ); }; -export default App; +export default App; \ No newline at end of file diff --git a/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy 2.tsx b/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy 2.tsx new file mode 100644 index 000000000..d6275d717 --- /dev/null +++ b/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy 2.tsx @@ -0,0 +1,718 @@ +// src/components/ruleEngine/ContractType.tsx +import { useState, useEffect } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; + +// ==================== Toast Notification Component ==================== +const Toast = ({ message, type, onClose }: { message: string; type: string; onClose: () => void }) => { + useEffect(() => { + const timer = setTimeout(onClose, 3000); + return () => clearTimeout(timer); + }, [onClose]); + + const bgColor = type === 'success' ? 'bg-green-500' : type === 'error' ? 'bg-red-500' : 'bg-blue-500'; + + return ( +
+ {message} +
+ ); +}; + +// ==================== API Service ==================== +const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api'; + +const apiService = { + // Cargo Types + getCargoTypes: () => fetch(`${API_BASE_URL}/cargo-types`).then(res => res.json()), + createCargoType: (data: any) => fetch(`${API_BASE_URL}/cargo-types`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + updateCargoType: (id: string, data: any) => fetch(`${API_BASE_URL}/cargo-types/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + deleteCargoType: (id: string) => fetch(`${API_BASE_URL}/cargo-types/${id}`, { + method: 'DELETE' + }).then(res => res.json()), + + // Container Types + getContainerTypes: () => fetch(`${API_BASE_URL}/container-types`).then(res => res.json()), + createContainerType: (data: any) => fetch(`${API_BASE_URL}/container-types`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + updateContainerType: (id: string, data: any) => fetch(`${API_BASE_URL}/container-types/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + deleteContainerType: (id: string) => fetch(`${API_BASE_URL}/container-types/${id}`, { + method: 'DELETE' + }).then(res => res.json()), + + // Priority Rules + getPriorityRules: () => fetch(`${API_BASE_URL}/priority-rules`).then(res => res.json()), + createPriorityRule: (data: any) => fetch(`${API_BASE_URL}/priority-rules`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + updatePriorityRule: (id: string, data: any) => fetch(`${API_BASE_URL}/priority-rules/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + deletePriorityRule: (id: string) => fetch(`${API_BASE_URL}/priority-rules/${id}`, { + method: 'DELETE' + }).then(res => res.json()), + + // Service Types + getServiceTypes: () => fetch(`${API_BASE_URL}/service-types`).then(res => res.json()), + createServiceType: (data: any) => fetch(`${API_BASE_URL}/service-types`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + updateServiceType: (id: string, data: any) => fetch(`${API_BASE_URL}/service-types/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + deleteServiceType: (id: string) => fetch(`${API_BASE_URL}/service-types/${id}`, { + method: 'DELETE' + }).then(res => res.json()), + + // Surcharge Types + getSurchargeTypes: () => fetch(`${API_BASE_URL}/surcharge-types`).then(res => res.json()), + createSurchargeType: (data: any) => fetch(`${API_BASE_URL}/surcharge-types`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + updateSurchargeType: (id: string, data: any) => fetch(`${API_BASE_URL}/surcharge-types/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + deleteSurchargeType: (id: string) => fetch(`${API_BASE_URL}/surcharge-types/${id}`, { + method: 'DELETE' + }).then(res => res.json()), + + // Surcharges + getSurcharges: () => fetch(`${API_BASE_URL}/surcharges`).then(res => res.json()), + createSurcharge: (data: any) => fetch(`${API_BASE_URL}/surcharges`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + updateSurcharge: (id: string, data: any) => fetch(`${API_BASE_URL}/surcharges/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + deleteSurcharge: (id: string) => fetch(`${API_BASE_URL}/surcharges/${id}`, { + method: 'DELETE' + }).then(res => res.json()), + + // Weight Limit Rules + getWeightLimitRules: () => fetch(`${API_BASE_URL}/weight-limit-rules`).then(res => res.json()), + createWeightLimitRule: (data: any) => fetch(`${API_BASE_URL}/weight-limit-rules`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + updateWeightLimitRule: (id: string, data: any) => fetch(`${API_BASE_URL}/weight-limit-rules/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }).then(res => res.json()), + deleteWeightLimitRule: (id: string) => fetch(`${API_BASE_URL}/weight-limit-rules/${id}`, { + method: 'DELETE' + }).then(res => res.json()), +}; + +// ==================== Entity Table Component ==================== +const EntityTable = ({ + title, + data, + columns, + onAdd, + onEdit, + onDelete, + isLoading +}: any) => { + const [expanded, setExpanded] = useState(true); + const [searchTerm, setSearchTerm] = useState(''); + + const filteredData = data?.filter((item: any) => + Object.values(item).some(value => + String(value).toLowerCase().includes(searchTerm.toLowerCase()) + ) + ) || []; + + if (isLoading) { + return ( +
+
setExpanded(!expanded)} + > +
+ {expanded ? '▼' : '▶'} +

{title}

+
+
+ {expanded && ( +
+
+

Loading...

+
+ )} +
+ ); + } + + return ( +
+
setExpanded(!expanded)} + > +
+ + {expanded ? '▼' : '▶'} + +

{title}

+ + {filteredData.length} items + +
+
+ + {expanded && ( +
+
+ +
+ setSearchTerm(e.target.value)} + /> + + + +
+
+ +
+ + + + {columns.map((col: any) => ( + + ))} + + + + + {filteredData.map((item: any) => ( + + {columns.map((col: any) => ( + + ))} + + + ))} + +
+ {col.label} + + Actions +
+ {col.render ? col.render(item[col.key], item) : item[col.key]} + + + +
+ {filteredData.length === 0 && ( +
+ + + +

No data found

+
+ )} +
+
+ )} +
+ ); +}; + +// ==================== Main Component ==================== +const ContractTypePage = () => { + const [activeTab, setActiveTab] = useState('cargo-types'); + const [modalOpen, setModalOpen] = useState(false); + const [editingItem, setEditingItem] = useState(null); + const [currentEntity, setCurrentEntity] = useState(''); + const [formData, setFormData] = useState({}); + const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' } | null>(null); + const queryClient = useQueryClient(); + + const showToast = (message: string, type: 'success' | 'error') => { + setToast({ message, type }); + }; + + // Fetch all data + const { data: cargoTypes = [], isLoading: cargoLoading } = useQuery({ + queryKey: ['cargo-types'], + queryFn: apiService.getCargoTypes, + }); + + const { data: containerTypes = [], isLoading: containerLoading } = useQuery({ + queryKey: ['container-types'], + queryFn: apiService.getContainerTypes, + }); + + const { data: priorityRules = [], isLoading: priorityLoading } = useQuery({ + queryKey: ['priority-rules'], + queryFn: apiService.getPriorityRules, + }); + + const { data: serviceTypes = [], isLoading: serviceLoading } = useQuery({ + queryKey: ['service-types'], + queryFn: apiService.getServiceTypes, + }); + + const { data: surchargeTypes = [], isLoading: surchargeTypeLoading } = useQuery({ + queryKey: ['surcharge-types'], + queryFn: apiService.getSurchargeTypes, + }); + + const { data: surcharges = [], isLoading: surchargeLoading } = useQuery({ + queryKey: ['surcharges'], + queryFn: apiService.getSurcharges, + }); + + const { data: weightLimitRules = [], isLoading: weightLimitLoading } = useQuery({ + queryKey: ['weight-limit-rules'], + queryFn: apiService.getWeightLimitRules, + }); + + // Mutations for Cargo Types + const createCargoType = useMutation({ + mutationFn: apiService.createCargoType, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['cargo-types'] }); + showToast('Cargo type created successfully', 'success'); + setModalOpen(false); + setFormData({}); + }, + onError: () => showToast('Failed to create cargo type', 'error'), + }); + + const updateCargoType = useMutation({ + mutationFn: ({ id, data }: { id: string; data: any }) => apiService.updateCargoType(id, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['cargo-types'] }); + showToast('Cargo type updated successfully', 'success'); + setModalOpen(false); + setFormData({}); + setEditingItem(null); + }, + onError: () => showToast('Failed to update cargo type', 'error'), + }); + + const deleteCargoType = useMutation({ + mutationFn: apiService.deleteCargoType, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['cargo-types'] }); + showToast('Cargo type deleted successfully', 'success'); + }, + onError: () => showToast('Failed to delete cargo type', 'error'), + }); + + const handleAdd = (entity: string) => { + setCurrentEntity(entity); + setEditingItem(null); + setFormData(getDefaultFormData(entity)); + setModalOpen(true); + }; + + const handleEdit = (entity: string, item: any) => { + setCurrentEntity(entity); + setEditingItem(item); + setFormData(item); + setModalOpen(true); + }; + + const handleDelete = (entity: string, item: any) => { + if (window.confirm(`Are you sure you want to delete this ${entity}?`)) { + if (entity === 'cargo-types') deleteCargoType.mutate(item.id); + } + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (currentEntity === 'cargo-types') { + if (editingItem) { + updateCargoType.mutate({ id: editingItem.id, data: formData }); + } else { + createCargoType.mutate(formData); + } + } + }; + + const getDefaultFormData = (entity: string) => { + switch(entity) { + case 'cargo-types': + return { code: '', cargoTypeName: '', showFreeTextBox: false, requiresDirectorApproval: false, isActive: true, displayOrder: 1 }; + default: + return {}; + } + }; + + const getEntityData = (entity: string) => { + switch(entity) { + case 'cargo-types': return cargoTypes; + case 'container-types': return containerTypes; + case 'priority-rules': return priorityRules; + case 'service-types': return serviceTypes; + case 'surcharge-types': return surchargeTypes; + case 'surcharges': return surcharges; + case 'weight-limit-rules': return weightLimitRules; + default: return []; + } + }; + + const getEntityLoading = (entity: string) => { + switch(entity) { + case 'cargo-types': return cargoLoading; + case 'container-types': return containerLoading; + case 'priority-rules': return priorityLoading; + case 'service-types': return serviceLoading; + case 'surcharge-types': return surchargeTypeLoading; + case 'surcharges': return surchargeLoading; + case 'weight-limit-rules': return weightLimitLoading; + default: return false; + } + }; + + const getColumns = (entity: string) => { + switch(entity) { + case 'cargo-types': + return [ + { key: 'code', label: 'Code' }, + { key: 'cargoTypeName', label: 'Name' }, + { key: 'displayOrder', label: 'Order' }, + { + key: 'isActive', + label: 'Status', + render: (val: boolean) => ( + + {val ? 'Active' : 'Inactive'} + + ) + } + ]; + case 'container-types': + return [ + { key: 'sizeCode', label: 'Size Code' }, + { key: 'description', label: 'Description' }, + { key: 'containersPerWagon', label: 'Containers/Wagon' }, + { + key: 'isActive', + label: 'Status', + render: (val: boolean) => ( + + {val ? 'Active' : 'Inactive'} + + ) + } + ]; + case 'priority-rules': + return [ + { key: 'priorityType', label: 'Priority Type' }, + { key: 'ruleName', label: 'Rule Name' }, + { key: 'bonusPoints', label: 'Bonus Points' }, + { + key: 'isActive', + label: 'Status', + render: (val: boolean) => ( + + {val ? 'Active' : 'Inactive'} + + ) + } + ]; + case 'service-types': + return [ + { key: 'code', label: 'Code' }, + { key: 'serviceName', label: 'Service Name' }, + { key: 'displayOrder', label: 'Order' }, + { + key: 'isActive', + label: 'Status', + render: (val: boolean) => ( + + {val ? 'Active' : 'Inactive'} + + ) + } + ]; + case 'surcharge-types': + return [ + { key: 'code', label: 'Code' }, + { key: 'name', label: 'Name' }, + { + key: 'isActive', + label: 'Status', + render: (val: boolean) => ( + + {val ? 'Active' : 'Inactive'} + + ) + } + ]; + case 'surcharges': + return [ + { key: 'feeName', label: 'Fee Name' }, + { key: 'calculationMethod', label: 'Method' }, + { + key: 'rate', + label: 'Rate', + render: (val: number, item: any) => `${val} ${item.currency}` + }, + { + key: 'isActive', + label: 'Status', + render: (val: boolean) => ( + + {val ? 'Active' : 'Inactive'} + + ) + } + ]; + case 'weight-limit-rules': + return [ + { key: 'tradeDirection', label: 'Direction' }, + { + key: 'maxWeightTons', + label: 'Max Weight', + render: (val: number) => `${val} tons` + }, + { key: 'exceededAction', label: 'Action' }, + { + key: 'isActive', + label: 'Status', + render: (val: boolean) => ( + + {val ? 'Active' : 'Inactive'} + + ) + } + ]; + default: + return []; + } + }; + + const tabs = [ + { id: 'cargo-types', label: 'Cargo Types' }, + { id: 'container-types', label: 'Container Types' }, + { id: 'priority-rules', label: 'Priority Rules' }, + { id: 'service-types', label: 'Service Types' }, + { id: 'surcharge-types', label: 'Surcharge Types' }, + { id: 'surcharges', label: 'Surcharges' }, + { id: 'weight-limit-rules', label: 'Weight Limit Rules' }, + ]; + + const isLoading = cargoLoading || containerLoading || priorityLoading || serviceLoading || surchargeTypeLoading || surchargeLoading || weightLimitLoading; + + if (isLoading) { + return ( +
+
+
+

Loading master data...

+
+
+ ); + } + + return ( +
+ {toast && ( + setToast(null)} + /> + )} + +
+
+ {tabs.map((tab) => ( + + ))} +
+
+ +
+ {tabs.map((tab) => ( +
+ handleAdd(tab.id)} + onEdit={(item: any) => handleEdit(tab.id, item)} + onDelete={(item: any) => handleDelete(tab.id, item)} + isLoading={getEntityLoading(tab.id)} + /> +
+ ))} +
+ + {modalOpen && currentEntity === 'cargo-types' && ( +
+
+
+

+ {editingItem ? 'Edit Cargo Type' : 'Add Cargo Type'} +

+ +
+
+
+
+ + setFormData({...formData, code: e.target.value.toUpperCase()})} + required + /> +
+
+ + setFormData({...formData, cargoTypeName: e.target.value})} + required + /> +
+
+ + setFormData({...formData, displayOrder: parseInt(e.target.value)})} + /> +
+
+ setFormData({...formData, showFreeTextBox: e.target.checked})} + /> + +
+
+ setFormData({...formData, requiresDirectorApproval: e.target.checked})} + /> + +
+
+ setFormData({...formData, isActive: e.target.checked})} + /> + +
+
+
+ + +
+
+
+
+ )} +
+ ); +}; + +export default ContractTypePage; + diff --git a/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy 3.tsx b/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy 3.tsx new file mode 100644 index 000000000..208423745 --- /dev/null +++ b/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy 3.tsx @@ -0,0 +1,366 @@ +// src/components/ruleEngine/ContractType.tsx +import { useState } from 'react'; + +// ==================== MOCK DATA (Replace with your API calls later) ==================== +const mockCargoTypes = [ + { id: '1', code: 'BULK', cargoTypeName: 'Bulk Cargo', displayOrder: 1, isActive: true }, + { id: '2', code: 'BREAK_BULK', cargoTypeName: 'Break Bulk', displayOrder: 2, isActive: true }, + { id: '3', code: 'CONTAINER', cargoTypeName: 'Containerized', displayOrder: 3, isActive: false }, + { id: '4', code: 'LIQUID', cargoTypeName: 'Liquid Bulk', displayOrder: 4, isActive: true }, +]; + +const mockContainerTypes = [ + { id: '1', sizeCode: '20FT', description: '20 Foot Standard Container', containersPerWagon: 2, isActive: true }, + { id: '2', sizeCode: '40FT', description: '40 Foot Standard Container', containersPerWagon: 1, isActive: true }, + { id: '3', sizeCode: '20RF', description: '20 Foot Refrigerated', containersPerWagon: 2, isActive: true }, +]; + +const mockPriorityRules = [ + { id: '1', priorityType: 'HIGH', ruleName: 'High Priority Booking', bonusPoints: 100, isActive: true }, + { id: '2', priorityType: 'URGENT', ruleName: 'Urgent Delivery', bonusPoints: 200, isActive: true }, + { id: '3', priorityType: 'LOW', ruleName: 'Standard Booking', bonusPoints: 0, isActive: true }, +]; + +const mockServiceTypes = [ + { id: '1', code: 'RAIL', serviceName: 'Rail Only', displayOrder: 1, isActive: true }, + { id: '2', code: 'RAIL_FIRST', serviceName: 'Rail + First Mile', displayOrder: 2, isActive: true }, + { id: '3', code: 'RAIL_LAST', serviceName: 'Rail + Last Mile', displayOrder: 3, isActive: false }, +]; + +const mockSurchargeTypes = [ + { id: '1', code: 'HAZ', name: 'Hazardous Material', isActive: true }, + { id: '2', code: 'REF', name: 'Refrigerated', isActive: true }, + { id: '3', code: 'OVR', name: 'Overweight', isActive: true }, +]; + +const mockSurcharges = [ + { id: '1', feeName: 'Hazardous Fee', calculationMethod: 'FLAT', rate: 150, currency: 'USD', isActive: true }, + { id: '2', feeName: 'Refrigeration Fee', calculationMethod: 'PER_TON', rate: 25, currency: 'USD', isActive: true }, +]; + +const mockWeightLimitRules = [ + { id: '1', tradeDirection: 'IMPORT', maxWeightTons: 20, exceededAction: 'WARNING_ONLY', isActive: true }, + { id: '2', tradeDirection: 'EXPORT', maxWeightTons: 22, exceededAction: 'BLOCK', isActive: true }, +]; + +// ==================== Toast Component ==================== +const Toast = ({ message, type, onClose }: { message: string; type: string; onClose: () => void }) => { + setTimeout(onClose, 3000); + const bgColor = type === 'success' ? 'bg-green-500' : 'bg-red-500'; + return ( +
+ {message} +
+ ); +}; + +// ==================== Entity Table Component ==================== +const EntityTable = ({ title, data, columns, onAdd, onEdit, onDelete }: any) => { + const [expanded, setExpanded] = useState(true); + const [searchTerm, setSearchTerm] = useState(''); + + const filteredData = Array.isArray(data) ? data.filter((item: any) => + Object.values(item).some(value => + String(value).toLowerCase().includes(searchTerm.toLowerCase()) + ) + ) : []; + + return ( +
+
setExpanded(!expanded)} + > +
+ {expanded ? '▼' : '▶'} +

{title}

+ + {filteredData.length} items + +
+
+ + {expanded && ( +
+
+ +
+ setSearchTerm(e.target.value)} + /> + + + +
+
+ +
+ + + + {columns.map((col: any) => ( + + ))} + + + + + {filteredData.map((item: any) => ( + + {columns.map((col: any) => ( + + ))} + + + ))} + +
+ {col.label} + Actions
+ {col.render ? col.render(item[col.key], item) : item[col.key]} + + + +
+ {filteredData.length === 0 && ( +
No data found
+ )} +
+
+ )} +
+ ); +}; + +// ==================== Main Component ==================== +const ContractTypePage = () => { + const [activeTab, setActiveTab] = useState('cargo-types'); + const [toast, setToast] = useState<{ message: string; type: 'success' | 'error' } | null>(null); + + // State for each entity + const [cargoTypes, setCargoTypes] = useState(mockCargoTypes); + const [containerTypes, setContainerTypes] = useState(mockContainerTypes); + const [priorityRules, setPriorityRules] = useState(mockPriorityRules); + const [serviceTypes, setServiceTypes] = useState(mockServiceTypes); + const [surchargeTypes, setSurchargeTypes] = useState(mockSurchargeTypes); + const [surcharges, setSurcharges] = useState(mockSurcharges); + const [weightLimitRules, setWeightLimitRules] = useState(mockWeightLimitRules); + + const showToast = (message: string, type: 'success' | 'error') => { + setToast({ message, type }); + setTimeout(() => setToast(null), 3000); + }; + + const handleAdd = (entity: string) => { + const newId = String(Date.now()); + let newItem; + + switch(entity) { + case 'cargo-types': + newItem = { id: newId, code: 'NEW', cargoTypeName: 'New Type', displayOrder: cargoTypes.length + 1, isActive: true }; + setCargoTypes([...cargoTypes, newItem]); + break; + case 'container-types': + newItem = { id: newId, sizeCode: 'NEW', description: 'New Container', containersPerWagon: 1, isActive: true }; + setContainerTypes([...containerTypes, newItem]); + break; + case 'priority-rules': + newItem = { id: newId, priorityType: 'MEDIUM', ruleName: 'New Rule', bonusPoints: 0, isActive: true }; + setPriorityRules([...priorityRules, newItem]); + break; + case 'service-types': + newItem = { id: newId, code: 'NEW', serviceName: 'New Service', displayOrder: serviceTypes.length + 1, isActive: true }; + setServiceTypes([...serviceTypes, newItem]); + break; + case 'surcharge-types': + newItem = { id: newId, code: 'NEW', name: 'New Surcharge Type', isActive: true }; + setSurchargeTypes([...surchargeTypes, newItem]); + break; + case 'surcharges': + newItem = { id: newId, feeName: 'New Fee', calculationMethod: 'FLAT', rate: 0, currency: 'USD', isActive: true }; + setSurcharges([...surcharges, newItem]); + break; + case 'weight-limit-rules': + newItem = { id: newId, tradeDirection: 'IMPORT', maxWeightTons: 20, exceededAction: 'WARNING_ONLY', isActive: true }; + setWeightLimitRules([...weightLimitRules, newItem]); + break; + } + showToast(`${entity} added successfully`, 'success'); + }; + + const handleEdit = (entity: string, item: any) => { + showToast(`Edit ${item.code || item.sizeCode || item.ruleName || item.serviceName || item.name || item.feeName}`, 'success'); + }; + + const handleDelete = (entity: string, item: any) => { + if (confirm('Are you sure you want to delete this item?')) { + switch(entity) { + case 'cargo-types': + setCargoTypes(cargoTypes.filter(c => c.id !== item.id)); + break; + case 'container-types': + setContainerTypes(containerTypes.filter(c => c.id !== item.id)); + break; + case 'priority-rules': + setPriorityRules(priorityRules.filter(p => p.id !== item.id)); + break; + case 'service-types': + setServiceTypes(serviceTypes.filter(s => s.id !== item.id)); + break; + case 'surcharge-types': + setSurchargeTypes(surchargeTypes.filter(s => s.id !== item.id)); + break; + case 'surcharges': + setSurcharges(surcharges.filter(s => s.id !== item.id)); + break; + case 'weight-limit-rules': + setWeightLimitRules(weightLimitRules.filter(w => w.id !== item.id)); + break; + } + showToast(`${entity} deleted successfully`, 'success'); + } + }; + + const getColumns = (entity: string) => { + switch(entity) { + case 'cargo-types': + return [ + { key: 'code', label: 'Code' }, + { key: 'cargoTypeName', label: 'Name' }, + { key: 'displayOrder', label: 'Order' }, + { key: 'isActive', label: 'Status', render: (val: boolean) => val ? '✅ Active' : '❌ Inactive' } + ]; + case 'container-types': + return [ + { key: 'sizeCode', label: 'Size Code' }, + { key: 'description', label: 'Description' }, + { key: 'containersPerWagon', label: 'Containers/Wagon' }, + { key: 'isActive', label: 'Status', render: (val: boolean) => val ? '✅ Active' : '❌ Inactive' } + ]; + case 'priority-rules': + return [ + { key: 'priorityType', label: 'Priority Type' }, + { key: 'ruleName', label: 'Rule Name' }, + { key: 'bonusPoints', label: 'Bonus Points' }, + { key: 'isActive', label: 'Status', render: (val: boolean) => val ? '✅ Active' : '❌ Inactive' } + ]; + case 'service-types': + return [ + { key: 'code', label: 'Code' }, + { key: 'serviceName', label: 'Service Name' }, + { key: 'displayOrder', label: 'Order' }, + { key: 'isActive', label: 'Status', render: (val: boolean) => val ? '✅ Active' : '❌ Inactive' } + ]; + case 'surcharge-types': + return [ + { key: 'code', label: 'Code' }, + { key: 'name', label: 'Name' }, + { key: 'isActive', label: 'Status', render: (val: boolean) => val ? '✅ Active' : '❌ Inactive' } + ]; + case 'surcharges': + return [ + { key: 'feeName', label: 'Fee Name' }, + { key: 'calculationMethod', label: 'Method' }, + { key: 'rate', label: 'Rate', render: (val: number, item: any) => `${val} ${item.currency}` }, + { key: 'isActive', label: 'Status', render: (val: boolean) => val ? '✅ Active' : '❌ Inactive' } + ]; + case 'weight-limit-rules': + return [ + { key: 'tradeDirection', label: 'Direction' }, + { key: 'maxWeightTons', label: 'Max Weight', render: (val: number) => `${val} tons` }, + { key: 'exceededAction', label: 'Action' }, + { key: 'isActive', label: 'Status', render: (val: boolean) => val ? '✅ Active' : '❌ Inactive' } + ]; + default: + return []; + } + }; + + const getEntityData = (entity: string) => { + switch(entity) { + case 'cargo-types': return cargoTypes; + case 'container-types': return containerTypes; + case 'priority-rules': return priorityRules; + case 'service-types': return serviceTypes; + case 'surcharge-types': return surchargeTypes; + case 'surcharges': return surcharges; + case 'weight-limit-rules': return weightLimitRules; + default: return []; + } + }; + + const tabs = [ + { id: 'cargo-types', label: 'Cargo Types' }, + { id: 'container-types', label: 'Container Types' }, + { id: 'priority-rules', label: 'Priority Rules' }, + { id: 'service-types', label: 'Service Types' }, + { id: 'surcharge-types', label: 'Surcharge Types' }, + { id: 'surcharges', label: 'Surcharges' }, + { id: 'weight-limit-rules', label: 'Weight Limit Rules' }, + ]; + + return ( +
+ {toast && setToast(null)} />} + +
+

Rule Engine - Master Data

+

Manage cargo types, container types, priority rules, and more

+
+ +
+
+ {tabs.map((tab) => ( + + ))} +
+
+ +
+ {tabs.map((tab) => ( +
+ handleAdd(tab.id)} + onEdit={(item: any) => handleEdit(tab.id, item)} + onDelete={(item: any) => handleDelete(tab.id, item)} + /> +
+ ))} +
+
+ ); +}; + +export default ContractTypePage; \ No newline at end of file diff --git a/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy.tsx b/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy.tsx new file mode 100644 index 000000000..dff4d0c7c --- /dev/null +++ b/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType copy.tsx @@ -0,0 +1,221 @@ +import { JSXElementConstructor, MouseEventHandler, ReactElement, ReactNode, SetStateAction, useState } from 'react'; + +export const ContractTypePage = () => { + const [expandedSections, setExpandedSections] = useState({ + contractType: true, + serviceType: false, + cargoType: false + }); + + const [contractTypes, setContractTypes] = useState([ + { id: 1, name: 'Shipper', description: 'Company that sends the freight' }, + { id: 2, name: 'Consignee', description: 'Company that receives the freight' }, + { id: 3, name: 'Third Party', description: 'Company that is neither the shipper nor the consignee but is involved in the freight process' } + ]); + + const [serviceTypes, setServiceTypes] = useState([ + { id: 1, name: 'Standard', description: 'Regular shipping service' }, + { id: 2, name: 'Express', description: 'Fast delivery service' }, + { id: 3, name: 'Economy', description: 'Cost-effective shipping option' } + ]); + + const [cargoTypes, setCargoTypes] = useState([ + { id: 1, name: 'General Cargo', description: 'Standard packaged goods' }, + { id: 2, name: 'Temperature Controlled', description: 'Goods requiring specific temperature' }, + { id: 3, name: 'Hazardous Materials', description: 'Dangerous goods requiring special handling' } + ]); + + const [newContractType, setNewContractType] = useState({ name: '', description: '' }); + const [newServiceType, setNewServiceType] = useState({ name: '', description: '' }); + const [newCargoType, setNewCargoType] = useState({ name: '', description: '' }); + const [showAddForms, setShowAddForms] = useState({ + contractType: false, + serviceType: false, + + cargoType: false + }); + + type SectionKey = 'contractType' | 'serviceType' | 'cargoType'; + + const toggleSection = (section: SectionKey) => { + setExpandedSections(prev => ({ + ...prev, + [section]: !prev[section] + })); + }; + + const toggleAddForm = (section: SectionKey) => { + setShowAddForms(prev => ({ + ...prev, + [section]: !prev[section] + })); + }; + + const handleAddContractType = () => { + if (newContractType.name && newContractType.description) { + setContractTypes([ + ...contractTypes, + { id: Date.now(), ...newContractType } + ]); + setNewContractType({ name: '', description: '' }); + toggleAddForm('contractType'); + } + }; + + const handleAddServiceType = () => { + if (newServiceType.name && newServiceType.description) { + setServiceTypes([ + ...serviceTypes, + { id: Date.now(), ...newServiceType } + ]); + setNewServiceType({ name: '', description: '' }); + toggleAddForm('serviceType'); + } + }; + + const handleAddCargoType = () => { + if (newCargoType.name && newCargoType.description) { + setCargoTypes([ + ...cargoTypes, + { id: Date.now(), ...newCargoType } + ]); + setNewCargoType({ name: '', description: '' }); + toggleAddForm('cargoType'); + } + }; + + const handleDelete = (type: string, id: number) => { + if (type === 'contract') { + setContractTypes(contractTypes.filter(item => item.id !== id)); + } else if (type === 'service') { + setServiceTypes(serviceTypes.filter(item => item.id !== id)); + } else if (type === 'cargo') { + setCargoTypes(cargoTypes.filter(item => item.id !== id)); + } + }; + + const handleEdit = (type: any, id: any) => { + // Implement edit functionality as needed + alert(`Edit ${type} type with id: ${id}`); + }; + + const renderTable = (title: string | number | boolean | ReactElement> | Iterable | null | undefined, types: any[], onAdd: { (): void; (): void; (): void; }, newItem: { name: any; description: any; }, setNewItem: { (value: SetStateAction<{ name: string; description: string; }>): void; (value: SetStateAction<{ name: string; description: string; }>): void; (value: SetStateAction<{ name: string; description: string; }>): void; (arg0: any): void; }, showAddForm: boolean, typeKey: string, addHandler: MouseEventHandler | undefined) => ( +
+
toggleSection(typeKey)} + > + + {expandedSections[typeKey] ? '▼' : '▶'} + +

{title}

+
+ + {expandedSections[typeKey] && ( +
+ + + {showAddForm && ( +
+

Add New {title.replace(' Types', '')}

+
+ setNewItem({ ...newItem, name: e.target.value })} + style={{ marginRight: '10px', padding: '5px' }} + /> + setNewItem({ ...newItem, description: e.target.value })} + style={{ marginRight: '10px', padding: '5px' }} + /> + + +
+
+ )} + + + + + + + + + + + + {types.map((type) => ( + + + + + + + ))} + +
IDNameDescriptionActions
{type.id}{type.name}{type.description} + + +
+
+ )} +
+ ); + + return ( +
+ {renderTable( + 'Contract Types', + contractTypes, + handleAddContractType, + newContractType, + setNewContractType, + showAddForms.contractType, + 'contractType', + handleAddContractType + )} + + {renderTable( + 'Service Types', + serviceTypes, + handleAddServiceType, + newServiceType, + setNewServiceType, + showAddForms.serviceType, + 'serviceType', + handleAddServiceType + )} + + {renderTable( + 'Cargo Types', + cargoTypes, + handleAddCargoType, + newCargoType, + setNewCargoType, + showAddForms.cargoType, + 'cargoType', + handleAddCargoType + )} +
+ ); +}; \ No newline at end of file diff --git a/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType.tsx b/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType.tsx index dff4d0c7c..e8be84130 100644 --- a/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType.tsx +++ b/apps/edr-freight-web/backoffice/src/components/ruleEngine/ContractType.tsx @@ -1,221 +1,873 @@ -import { JSXElementConstructor, MouseEventHandler, ReactElement, ReactNode, SetStateAction, useState } from 'react'; +// src/components/ruleEngine/ContractType.tsx +import { useState, useEffect } from 'react'; -export const ContractTypePage = () => { - const [expandedSections, setExpandedSections] = useState({ - contractType: true, - serviceType: false, - cargoType: false - }); +// ==================== API Service ==================== +const API_BASE_URL = 'http://localhost:3001/api'; - const [contractTypes, setContractTypes] = useState([ - { id: 1, name: 'Shipper', description: 'Company that sends the freight' }, - { id: 2, name: 'Consignee', description: 'Company that receives the freight' }, - { id: 3, name: 'Third Party', description: 'Company that is neither the shipper nor the consignee but is involved in the freight process' } - ]); - - const [serviceTypes, setServiceTypes] = useState([ - { id: 1, name: 'Standard', description: 'Regular shipping service' }, - { id: 2, name: 'Express', description: 'Fast delivery service' }, - { id: 3, name: 'Economy', description: 'Cost-effective shipping option' } - ]); - - const [cargoTypes, setCargoTypes] = useState([ - { id: 1, name: 'General Cargo', description: 'Standard packaged goods' }, - { id: 2, name: 'Temperature Controlled', description: 'Goods requiring specific temperature' }, - { id: 3, name: 'Hazardous Materials', description: 'Dangerous goods requiring special handling' } - ]); - - const [newContractType, setNewContractType] = useState({ name: '', description: '' }); - const [newServiceType, setNewServiceType] = useState({ name: '', description: '' }); - const [newCargoType, setNewCargoType] = useState({ name: '', description: '' }); - const [showAddForms, setShowAddForms] = useState({ - contractType: false, - serviceType: false, +const apiFetch = async (endpoint: string, options?: RequestInit): Promise => { + try { + const url = `${API_BASE_URL}${endpoint}`; + const response = await fetch(url, { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + ...options, + }); - cargoType: false + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`HTTP ${response.status}: ${errorText || response.statusText}`); + } + + return await response.json(); + } catch (error) { + console.error(`API Error (${endpoint}):`, error); + throw error; + } +}; + +const apiService = { + getCargoTypes: (): Promise => apiFetch('/cargo-types'), + createCargoType: (data: any): Promise => apiFetch('/cargo-types', { method: 'POST', body: JSON.stringify(data) }), + updateCargoType: (id: string, data: any): Promise => apiFetch(`/cargo-types/${id}`, { method: 'PATCH', body: JSON.stringify(data) }), + deleteCargoType: (id: string): Promise => apiFetch(`/cargo-types/${id}`, { method: 'DELETE' }), + + getContainerTypes: (): Promise => apiFetch('/container-types'), + createContainerType: (data: any): Promise => apiFetch('/container-types', { method: 'POST', body: JSON.stringify(data) }), + updateContainerType: (id: string, data: any): Promise => apiFetch(`/container-types/${id}`, { method: 'PATCH', body: JSON.stringify(data) }), + deleteContainerType: (id: string): Promise => apiFetch(`/container-types/${id}`, { method: 'DELETE' }), + + getPriorityRules: (): Promise => apiFetch('/priority-rules'), + createPriorityRule: (data: any): Promise => apiFetch('/priority-rules', { method: 'POST', body: JSON.stringify(data) }), + updatePriorityRule: (id: string, data: any): Promise => apiFetch(`/priority-rules/${id}`, { method: 'PATCH', body: JSON.stringify(data) }), + deletePriorityRule: (id: string): Promise => apiFetch(`/priority-rules/${id}`, { method: 'DELETE' }), + + getServiceTypes: (): Promise => apiFetch('/service-types'), + createServiceType: (data: any): Promise => apiFetch('/service-types', { method: 'POST', body: JSON.stringify(data) }), + updateServiceType: (id: string, data: any): Promise => apiFetch(`/service-types/${id}`, { method: 'PATCH', body: JSON.stringify(data) }), + deleteServiceType: (id: string): Promise => apiFetch(`/service-types/${id}`, { method: 'DELETE' }), + + getSurchargeTypes: (): Promise => apiFetch('/surcharge-types'), + createSurchargeType: (data: any): Promise => apiFetch('/surcharge-types', { method: 'POST', body: JSON.stringify(data) }), + updateSurchargeType: (id: string, data: any): Promise => apiFetch(`/surcharge-types/${id}`, { method: 'PATCH', body: JSON.stringify(data) }), + deleteSurchargeType: (id: string): Promise => apiFetch(`/surcharge-types/${id}`, { method: 'DELETE' }), + + getSurcharges: (): Promise => apiFetch('/surcharges'), + createSurcharge: (data: any): Promise => apiFetch('/surcharges', { method: 'POST', body: JSON.stringify(data) }), + updateSurcharge: (id: string, data: any): Promise => apiFetch(`/surcharges/${id}`, { method: 'PATCH', body: JSON.stringify(data) }), + deleteSurcharge: (id: string): Promise => apiFetch(`/surcharges/${id}`, { method: 'DELETE' }), + + getWeightLimitRules: (): Promise => apiFetch('/weight-limit-rules'), + createWeightLimitRule: (data: any): Promise => apiFetch('/weight-limit-rules', { method: 'POST', body: JSON.stringify(data) }), + updateWeightLimitRule: (id: string, data: any): Promise => apiFetch(`/weight-limit-rules/${id}`, { method: 'PATCH', body: JSON.stringify(data) }), + deleteWeightLimitRule: (id: string): Promise => apiFetch(`/weight-limit-rules/${id}`, { method: 'DELETE' }), +}; + +// ==================== Toast Component ==================== +const Toast = ({ message, type, onClose }: { message: string; type: string; onClose: () => void }) => { + useEffect(() => { + const timer = setTimeout(onClose, 3000); + return () => clearTimeout(timer); + }, [onClose]); + + const bgColor = type === 'success' ? 'bg-green-500' : 'bg-red-500'; + return ( +
+ {message} +
+ ); +}; + +// ==================== Modal Component ==================== +const Modal = ({ isOpen, onClose, title, children }: { isOpen: boolean; onClose: () => void; title: string; children: React.ReactNode }) => { + if (!isOpen) return null; + + return ( +
+
+
+

{title}

+ +
+
{children}
+
+
+ ); +}; + +// ==================== Form Components ==================== + +// 1. Cargo Type Form +const CargoTypeForm = ({ initialData, onSubmit, onCancel, isSubmitting }: any) => { + const [formData, setFormData] = useState({ + code: initialData?.code || '', + cargoTypeName: initialData?.cargoTypeName || '', + parentGroupId: initialData?.parentGroupId || '', + showFreeTextBox: initialData?.showFreeTextBox || false, + requiresDirectorApproval: initialData?.requiresDirectorApproval || false, + isActive: initialData?.isActive !== undefined ? initialData.isActive : true, + displayOrder: initialData?.displayOrder || 1, }); - type SectionKey = 'contractType' | 'serviceType' | 'cargoType'; - - const toggleSection = (section: SectionKey) => { - setExpandedSections(prev => ({ - ...prev, - [section]: !prev[section] - })); + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const submitData = { + code: formData.code.toUpperCase(), + cargoTypeName: formData.cargoTypeName, + parentGroupId: formData.parentGroupId || undefined, + showFreeTextBox: formData.showFreeTextBox, + requiresDirectorApproval: formData.requiresDirectorApproval, + isActive: formData.isActive, + displayOrder: Number(formData.displayOrder), + }; + onSubmit(submitData); }; - const toggleAddForm = (section: SectionKey) => { - setShowAddForms(prev => ({ - ...prev, - [section]: !prev[section] - })); - }; - - const handleAddContractType = () => { - if (newContractType.name && newContractType.description) { - setContractTypes([ - ...contractTypes, - { id: Date.now(), ...newContractType } - ]); - setNewContractType({ name: '', description: '' }); - toggleAddForm('contractType'); - } - }; - - const handleAddServiceType = () => { - if (newServiceType.name && newServiceType.description) { - setServiceTypes([ - ...serviceTypes, - { id: Date.now(), ...newServiceType } - ]); - setNewServiceType({ name: '', description: '' }); - toggleAddForm('serviceType'); - } - }; - - const handleAddCargoType = () => { - if (newCargoType.name && newCargoType.description) { - setCargoTypes([ - ...cargoTypes, - { id: Date.now(), ...newCargoType } - ]); - setNewCargoType({ name: '', description: '' }); - toggleAddForm('cargoType'); - } - }; - - const handleDelete = (type: string, id: number) => { - if (type === 'contract') { - setContractTypes(contractTypes.filter(item => item.id !== id)); - } else if (type === 'service') { - setServiceTypes(serviceTypes.filter(item => item.id !== id)); - } else if (type === 'cargo') { - setCargoTypes(cargoTypes.filter(item => item.id !== id)); - } - }; - - const handleEdit = (type: any, id: any) => { - // Implement edit functionality as needed - alert(`Edit ${type} type with id: ${id}`); - }; - - const renderTable = (title: string | number | boolean | ReactElement> | Iterable | null | undefined, types: any[], onAdd: { (): void; (): void; (): void; }, newItem: { name: any; description: any; }, setNewItem: { (value: SetStateAction<{ name: string; description: string; }>): void; (value: SetStateAction<{ name: string; description: string; }>): void; (value: SetStateAction<{ name: string; description: string; }>): void; (arg0: any): void; }, showAddForm: boolean, typeKey: string, addHandler: MouseEventHandler | undefined) => ( -
-
toggleSection(typeKey)} - > - - {expandedSections[typeKey] ? '▼' : '▶'} - -

{title}

+ return ( +
+
+
+ + setFormData({...formData, code: e.target.value})} required /> +
+
+ + setFormData({...formData, cargoTypeName: e.target.value})} required /> +
- - {expandedSections[typeKey] && ( -
- - - {showAddForm && ( -
-

Add New {title.replace(' Types', '')}

-
- setNewItem({ ...newItem, name: e.target.value })} - style={{ marginRight: '10px', padding: '5px' }} - /> - setNewItem({ ...newItem, description: e.target.value })} - style={{ marginRight: '10px', padding: '5px' }} - /> - - -
+
+ + setFormData({...formData, parentGroupId: e.target.value})} /> +
+
+
+ + setFormData({...formData, displayOrder: parseInt(e.target.value)})} /> +
+
+
+ + + +
+
+ + +
+ + ); +}; + +// 2. Container Type Form +const ContainerTypeForm = ({ initialData, onSubmit, onCancel, isSubmitting }: any) => { + const [formData, setFormData] = useState({ + sizeCode: initialData?.sizeCode || '', + description: initialData?.description || '', + containersPerWagon: initialData?.containersPerWagon || 1, + isActive: initialData?.isActive !== undefined ? initialData.isActive : true, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSubmit(formData); + }; + + return ( +
+
+ + setFormData({...formData, sizeCode: e.target.value})} required /> +
+
+ +