mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 13:05:44 +00:00
fix conflict
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { Navigate, Outlet, Route, Routes, useLocation, useNavigate } from "react-router-dom";
|
||||
import { DashboardLayout, type SidebarItem } from "@edr/ui-common";
|
||||
import { LayoutDashboard, Network, Paperclip, Settings } from "lucide-react";
|
||||
|
||||
import { useAuth } from "./auth/useAuth";
|
||||
import LoginPage from "./pages/auth/LoginPage";
|
||||
import OverviewPage from "./pages/dashboard/OverviewPage";
|
||||
@@ -12,6 +11,8 @@ import UserManagementPage from "./pages/dashboard/user-management/UserManagement
|
||||
import LoadingScreen from "./components/LoadingScreen";
|
||||
import FileUploadSettingsPage from "./pages/documents/FileUploadSettingsPage";
|
||||
import DropdownSettingsPage from "./pages/dropdown_settings/DropdownSettingsPage";
|
||||
import DemoUser1Page from "./pages/dashboard/demo/DemoUser1Page";
|
||||
import DemoUser2Page from "./pages/dashboard/demo/DemoUser2Page";
|
||||
|
||||
const sidebarItems: SidebarItem[] = [
|
||||
{
|
||||
@@ -50,11 +51,73 @@ const sidebarItems: SidebarItem[] = [
|
||||
}
|
||||
];
|
||||
|
||||
const hasPermission = (
|
||||
user: ReturnType<typeof useAuth>["user"],
|
||||
key: string,
|
||||
) => {
|
||||
if (!user) return false;
|
||||
if (user.permissions?.some((p) => p.key === key)) return true;
|
||||
return (user.employee ?? []).some((emp) =>
|
||||
(emp.positions ?? []).some((pos) =>
|
||||
(pos.permissions ?? []).some((p) => p.key === key),
|
||||
),
|
||||
);
|
||||
};
|
||||
const DashboardShell = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { user, logout } = useAuth();
|
||||
|
||||
const sidebarItems: SidebarItem[] = [
|
||||
{
|
||||
label: "Overview",
|
||||
href: "/dashboard/overview",
|
||||
icon: <LayoutDashboard />,
|
||||
},
|
||||
{
|
||||
label: "User management",
|
||||
href: "/dashboard/user-management",
|
||||
icon: <Network />,
|
||||
children: [
|
||||
{
|
||||
label: "Employees",
|
||||
href: "/dashboard/user-management/employees",
|
||||
},
|
||||
{
|
||||
label: "Permissions",
|
||||
href: "/dashboard/user-management/permissions",
|
||||
},
|
||||
{
|
||||
label: "Roles",
|
||||
href: "/dashboard/user-management/roles",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Rule Engine",
|
||||
href: "/dashboard/rule-engine",
|
||||
icon: <Settings />,
|
||||
},
|
||||
...(hasPermission(user, "can:demo:user1")
|
||||
? ([
|
||||
{
|
||||
label: "User1",
|
||||
href: "/dashboard/user1",
|
||||
icon: <Settings />,
|
||||
},
|
||||
] as SidebarItem[])
|
||||
: []),
|
||||
...(hasPermission(user, "can:demo:user2")
|
||||
? ([
|
||||
{
|
||||
label: "User2",
|
||||
href: "/dashboard/user2",
|
||||
icon: <Settings />,
|
||||
},
|
||||
] as SidebarItem[])
|
||||
: []),
|
||||
];
|
||||
|
||||
const displayName = user?.name?.en || user?.username || user?.email || "User";
|
||||
|
||||
return (
|
||||
@@ -98,9 +161,12 @@ const App = () => {
|
||||
<Route path="user-management" element={<UserManagementPage />} />
|
||||
<Route path="file-settings" element={<FileUploadSettingsPage />} />
|
||||
<Route path="dropdown-settings" element={<DropdownSettingsPage />} />
|
||||
<Route path="rule-engine" element={<RuleEnginePage />} />
|
||||
<Route path="user-management/employees" element={<EmployeesPage />} />
|
||||
<Route path="user-management/permissions" element={<PermissionsPage />} />
|
||||
<Route path="user-management/roles" element={<RolesPage />} />
|
||||
<Route path="user1" element={<DemoUser1Page />} />
|
||||
<Route path="user2" element={<DemoUser2Page />} />
|
||||
<Route path="org-structure" element={<Navigate to="/dashboard/user-management" replace />} />
|
||||
<Route path="org-structure/*" element={<Navigate to="/dashboard/user-management" replace />} />
|
||||
</Route>
|
||||
|
||||
@@ -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<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | 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<HTMLButtonElement> | undefined) => (
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<div
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '10px',
|
||||
backgroundColor: '#f0f0f0',
|
||||
marginBottom: '10px'
|
||||
}}
|
||||
onClick={() => toggleSection(typeKey)}
|
||||
>
|
||||
<span style={{ marginRight: '10px', fontSize: '20px', color: '#138a49' }}>
|
||||
{expandedSections[typeKey] ? '▼' : '▶'}
|
||||
</span>
|
||||
<h3 style={{ margin: 0 }}>{title}</h3>
|
||||
</div>
|
||||
|
||||
{expandedSections[typeKey] && (
|
||||
<div style={{ marginLeft: '20px' }}>
|
||||
<button onClick={() => toggleAddForm(typeKey)}>
|
||||
Add {title.replace(' Types', ' type')}
|
||||
</button>
|
||||
|
||||
{showAddForm && (
|
||||
<div style={{
|
||||
marginTop: '10px',
|
||||
marginBottom: '10px',
|
||||
padding: '10px',
|
||||
border: '1px solid #ccc',
|
||||
borderRadius: '4px'
|
||||
}}>
|
||||
<h4>Add New {title.replace(' Types', '')}</h4>
|
||||
<div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value={newItem.name}
|
||||
onChange={(e) => setNewItem({ ...newItem, name: e.target.value })}
|
||||
style={{ marginRight: '10px', padding: '5px' }}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Description"
|
||||
value={newItem.description}
|
||||
onChange={(e) => setNewItem({ ...newItem, description: e.target.value })}
|
||||
style={{ marginRight: '10px', padding: '5px' }}
|
||||
/>
|
||||
<button onClick={addHandler}>Save</button>
|
||||
<button onClick={() => toggleAddForm(typeKey)} style={{ marginLeft: '5px' }}>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse', marginTop: '10px' }}>
|
||||
<thead>
|
||||
<tr style={{ backgroundColor: '#f2f2f2' }}>
|
||||
<th style={{ border: '1px solid #ddd', padding: '8px', textAlign: 'left' }}>ID</th>
|
||||
<th style={{ border: '1px solid #ddd', padding: '8px', textAlign: 'left' }}>Name</th>
|
||||
<th style={{ border: '1px solid #ddd', padding: '8px', textAlign: 'left' }}>Description</th>
|
||||
<th style={{ border: '1px solid #ddd', padding: '8px', textAlign: 'left' }}>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{types.map((type) => (
|
||||
<tr key={type.id}>
|
||||
<td style={{ border: '1px solid #ddd', padding: '8px' }}>{type.id}</td>
|
||||
<td style={{ border: '1px solid #ddd', padding: '8px' }}>{type.name}</td>
|
||||
<td style={{ border: '1px solid #ddd', padding: '8px' }}>{type.description}</td>
|
||||
<td style={{ border: '1px solid #ddd', padding: '8px' }}>
|
||||
<button onClick={() => handleEdit(typeKey, type.id)} style={{ marginRight: '5px' }}>Edit</button>
|
||||
<button onClick={() => handleDelete(typeKey === 'contractType' ? 'contract' : typeKey === 'serviceType' ? 'service' : 'cargo', type.id)}>Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{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
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { api } from "@/auth/http";
|
||||
|
||||
const DemoUser1Page = () => {
|
||||
const [data, setData] = useState<unknown>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const run = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await api.get("/test_user1");
|
||||
if (cancelled) return;
|
||||
setData(response.data);
|
||||
} catch (e: any) {
|
||||
if (cancelled) return;
|
||||
const message =
|
||||
e?.response?.data?.message ||
|
||||
e?.response?.data?.error ||
|
||||
e?.message ||
|
||||
"Request failed";
|
||||
setError(String(message));
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
void run();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="rounded-2xl border border-border bg-card p-6">
|
||||
<h1 className="text-lg font-semibold text-foreground">User1 Demo</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Calls <code className="font-mono">GET /api/test_user1</code> (requires{' '}
|
||||
<code className="font-mono">can:demo:user1</code>).
|
||||
</p>
|
||||
|
||||
<div className="mt-4">
|
||||
{loading ? <p className="text-sm text-muted-foreground">Loading...</p> : null}
|
||||
{error ? (
|
||||
<div className="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
{!loading && !error ? (
|
||||
<pre className="mt-3 overflow-auto rounded-xl border border-border bg-background p-4 text-xs text-foreground">
|
||||
{JSON.stringify(data, null, 2)}
|
||||
</pre>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DemoUser1Page;
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { api } from "@/auth/http";
|
||||
|
||||
const DemoUser2Page = () => {
|
||||
const [data, setData] = useState<unknown>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const run = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await api.get("/test_user2");
|
||||
if (cancelled) return;
|
||||
setData(response.data);
|
||||
} catch (e: any) {
|
||||
if (cancelled) return;
|
||||
const message =
|
||||
e?.response?.data?.message ||
|
||||
e?.response?.data?.error ||
|
||||
e?.message ||
|
||||
"Request failed";
|
||||
setError(String(message));
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
void run();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<div className="rounded-2xl border border-border bg-card p-6">
|
||||
<h1 className="text-lg font-semibold text-foreground">User2 Demo</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Calls <code className="font-mono">GET /api/test_user2</code> (requires{' '}
|
||||
<code className="font-mono">can:demo:user2</code>).
|
||||
</p>
|
||||
|
||||
<div className="mt-4">
|
||||
{loading ? <p className="text-sm text-muted-foreground">Loading...</p> : null}
|
||||
{error ? (
|
||||
<div className="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
{!loading && !error ? (
|
||||
<pre className="mt-3 overflow-auto rounded-xl border border-border bg-background p-4 text-xs text-foreground">
|
||||
{JSON.stringify(data, null, 2)}
|
||||
</pre>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DemoUser2Page;
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ContractTypePage, } from "@/components/ruleEngine/ContractType";
|
||||
|
||||
export const RuleEnginePage = () => {
|
||||
return <div>
|
||||
<h3>
|
||||
Rule Engine Page
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
<ContractTypePage />
|
||||
</div>
|
||||
|
||||
</div>;
|
||||
};
|
||||
Reference in New Issue
Block a user