ApiLogicServer 15.0.35__py3-none-any.whl → 15.0.38__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- api_logic_server_cli/api_logic_server.py +2 -2
- api_logic_server_cli/api_logic_server_info.yaml +3 -3
- api_logic_server_cli/genai/genai_svcs.py +4 -3
- api_logic_server_cli/manager.py +7 -5
- api_logic_server_cli/prototypes/base/docs/training/react_map.prompt.md +13 -0
- api_logic_server_cli/prototypes/base/docs/training/react_tree.prompt.md +10 -0
- api_logic_server_cli/prototypes/base/integration/mcp/examples/mcp_context_results.txt +142 -0
- api_logic_server_cli/prototypes/base/integration/mcp/mcp_client_executor.py +65 -29
- api_logic_server_cli/prototypes/manager/system/Manager_workspace.code-workspace +3 -3
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/app_learning/Admin-App-Resource-Learning-Prompt.md +3 -2
- api_logic_server_cli/prototypes/manager/system/genai/app_templates/app_learning/Admin-App-js-Learning-Prompt.md +4 -14
- api_logic_server_cli/prototypes/nw/api/api_discovery/authentication_expose_api_models.py +53 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/auto_discovery.py +27 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/count_orders_by_month.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/count_orders_by_month.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/dashboard_services.py +143 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/mcp_discovery.py +97 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/new_service.py +21 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/newer_service.py +21 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/number_of_sales_per_category.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/number_of_sales_per_category.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/ontimize_api.py +495 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/sales_by_category.html +76 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/sales_by_category.sql +1 -0
- api_logic_server_cli/prototypes/nw/api/api_discovery/system.py +77 -0
- api_logic_server_cli/prototypes/nw/database/database_discovery/graphics_services.py +173 -0
- api_logic_server_cli/prototypes/nw/ui/admin/home.js +5 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/DEPARTMENT_TREE_VIEW.md +66 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/package.json +4 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/public/index.html +3 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/App.js +8 -1
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/CustomLayout.js +20 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Department.js +511 -24
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/DepartmentTree.js +147 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Employee.js +230 -18
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/LandingPage.js +264 -0
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/Supplier.js +359 -121
- api_logic_server_cli/prototypes/nw/ui/reference_react_app/src/index.js +1 -0
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/METADATA +1 -1
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/RECORD +44 -23
- api_logic_server_cli/prototypes/base/docs/training/admin_app_unused.md +0 -156
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/WHEEL +0 -0
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/entry_points.txt +0 -0
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-15.0.35.dist-info → apilogicserver-15.0.38.dist-info}/top_level.txt +0 -0
|
@@ -1,24 +1,450 @@
|
|
|
1
|
-
// begin MANDATORY imports (always generated
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { List,
|
|
1
|
+
// begin MANDATORY imports (always generated)
|
|
2
|
+
import React, { useState } from 'react';
|
|
3
|
+
import { List, Datagrid, TextField, DateField, NumberField } from 'react-admin';
|
|
4
4
|
import { ReferenceField, ReferenceManyField } from 'react-admin';
|
|
5
|
-
import {
|
|
5
|
+
import { SimpleShowLayout, TextInput, NumberInput } from 'react-admin';
|
|
6
6
|
import { ReferenceInput, SelectInput, SimpleForm, Show, Edit, Create } from 'react-admin';
|
|
7
|
-
import {
|
|
8
|
-
import { EditButton, DeleteButton, CreateButton
|
|
9
|
-
import { Grid, Typography, Box, Divider, Button } from '@mui/material';
|
|
10
|
-
import { useRecordContext, useRedirect,
|
|
7
|
+
import { Pagination, Labeled } from 'react-admin';
|
|
8
|
+
import { EditButton, DeleteButton, CreateButton } from 'react-admin';
|
|
9
|
+
import { Grid, Typography, Box, Divider, Button, ToggleButton, ToggleButtonGroup, Paper, Collapse, IconButton, Tabs, Tab as MuiTab } from '@mui/material';
|
|
10
|
+
import { useRecordContext, useRedirect, useGetList } from 'react-admin';
|
|
11
11
|
import AddIcon from '@mui/icons-material/Add';
|
|
12
|
+
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
13
|
+
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
|
14
|
+
import ViewListIcon from '@mui/icons-material/ViewList';
|
|
15
|
+
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
|
12
16
|
// end mandatory imports
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
18
|
+
// Expandable Tree Node Component
|
|
19
|
+
const ExpandableTreeNode = ({ department, allDepartments, onDepartmentClick, level = 0 }) => {
|
|
20
|
+
const [expanded, setExpanded] = useState(false);
|
|
21
|
+
|
|
22
|
+
// Get children for this department - children have this department as their parent
|
|
23
|
+
const children = allDepartments?.filter(dept =>
|
|
24
|
+
dept.DepartmentId && parseInt(dept.DepartmentId) === parseInt(department.id)
|
|
25
|
+
) || [];
|
|
26
|
+
|
|
27
|
+
const hasChildren = children.length > 0;
|
|
28
|
+
|
|
29
|
+
const handleToggle = () => {
|
|
30
|
+
if (hasChildren) {
|
|
31
|
+
setExpanded(!expanded);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Box>
|
|
37
|
+
{/* Department Row */}
|
|
38
|
+
<Box sx={{ display: 'flex', alignItems: 'center', mb: 0.5 }}>
|
|
39
|
+
{/* Expand/Collapse Icon */}
|
|
40
|
+
<IconButton
|
|
41
|
+
size="small"
|
|
42
|
+
onClick={handleToggle}
|
|
43
|
+
sx={{
|
|
44
|
+
width: 24,
|
|
45
|
+
height: 24,
|
|
46
|
+
mr: 0.5,
|
|
47
|
+
visibility: hasChildren ? 'visible' : 'hidden'
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{expanded ? <ExpandMoreIcon fontSize="small" /> : <ChevronRightIcon fontSize="small" />}
|
|
51
|
+
</IconButton>
|
|
52
|
+
|
|
53
|
+
{/* Department Button */}
|
|
54
|
+
<Button
|
|
55
|
+
variant="text"
|
|
56
|
+
onClick={() => onDepartmentClick(department)}
|
|
57
|
+
sx={{
|
|
58
|
+
textAlign: 'left',
|
|
59
|
+
textTransform: 'none',
|
|
60
|
+
fontWeight: level === 0 ? 'bold' : 'normal',
|
|
61
|
+
justifyContent: 'flex-start',
|
|
62
|
+
p: 0.5,
|
|
63
|
+
color: level === 0 ? 'text.primary' : level === 1 ? 'text.secondary' : 'text.disabled',
|
|
64
|
+
fontSize: level === 0 ? '1rem' : level === 1 ? '0.9rem' : '0.85rem',
|
|
65
|
+
minWidth: 'auto',
|
|
66
|
+
flex: 1
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
{level === 0 ? '📁' : level === 1 ? '📄' : '📃'} {department.DepartmentName}
|
|
70
|
+
</Button>
|
|
71
|
+
</Box>
|
|
72
|
+
|
|
73
|
+
{/* Children (with collapse animation) */}
|
|
74
|
+
{hasChildren && (
|
|
75
|
+
<Collapse in={expanded}>
|
|
76
|
+
<Box sx={{ ml: 3 }}>
|
|
77
|
+
{children.map(child => (
|
|
78
|
+
<ExpandableTreeNode
|
|
79
|
+
key={child.id}
|
|
80
|
+
department={child}
|
|
81
|
+
allDepartments={allDepartments}
|
|
82
|
+
onDepartmentClick={onDepartmentClick}
|
|
83
|
+
level={level + 1}
|
|
84
|
+
/>
|
|
85
|
+
))}
|
|
86
|
+
</Box>
|
|
87
|
+
</Collapse>
|
|
88
|
+
)}
|
|
89
|
+
</Box>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Department Tree View Component
|
|
94
|
+
const DepartmentTreeView = ({ onDepartmentClick }) => {
|
|
95
|
+
const { data: allDepartments, isLoading, error } = useGetList('Department', {
|
|
96
|
+
pagination: { page: 1, perPage: 1000 },
|
|
97
|
+
sort: { field: 'DepartmentName', order: 'ASC' }
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (isLoading) return <div>Loading departments...</div>;
|
|
101
|
+
if (error) return <div>Error loading departments: {error.message}</div>;
|
|
102
|
+
|
|
103
|
+
// Get root departments (those with no parent)
|
|
104
|
+
const rootDepartments = allDepartments?.filter(d => !d.DepartmentId || d.DepartmentId === null) || [];
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<Paper sx={{ p: 2, height: '100%', overflow: 'auto' }}>
|
|
108
|
+
<Typography variant="h6" sx={{ mb: 2 }}>
|
|
109
|
+
Department Hierarchy
|
|
110
|
+
</Typography>
|
|
111
|
+
{rootDepartments.length === 0 ? (
|
|
112
|
+
<Typography variant="body2" color="text.secondary">
|
|
113
|
+
No departments found
|
|
114
|
+
</Typography>
|
|
115
|
+
) : (
|
|
116
|
+
<Box>
|
|
117
|
+
{rootDepartments.map(rootDept => (
|
|
118
|
+
<ExpandableTreeNode
|
|
119
|
+
key={rootDept.id}
|
|
120
|
+
department={rootDept}
|
|
121
|
+
allDepartments={allDepartments}
|
|
122
|
+
onDepartmentClick={onDepartmentClick}
|
|
123
|
+
level={0}
|
|
124
|
+
/>
|
|
125
|
+
))}
|
|
126
|
+
</Box>
|
|
127
|
+
)}
|
|
128
|
+
</Paper>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Enhanced Department List with View Toggle
|
|
133
|
+
export const DepartmentList = (props) => {
|
|
134
|
+
const [viewMode, setViewMode] = useState('list');
|
|
135
|
+
const [selectedDepartment, setSelectedDepartment] = useState(null);
|
|
136
|
+
|
|
137
|
+
const handleViewChange = (event, newView) => {
|
|
138
|
+
if (newView !== null) {
|
|
139
|
+
setViewMode(newView);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const handleDepartmentClick = (department) => {
|
|
144
|
+
setSelectedDepartment(department);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (viewMode === 'tree') {
|
|
148
|
+
return (
|
|
149
|
+
<Box sx={{ p: 2 }}>
|
|
150
|
+
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
|
151
|
+
<Typography variant="h4">Departments</Typography>
|
|
152
|
+
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
|
|
153
|
+
<CreateButton />
|
|
154
|
+
<ToggleButtonGroup
|
|
155
|
+
value={viewMode}
|
|
156
|
+
exclusive
|
|
157
|
+
onChange={handleViewChange}
|
|
158
|
+
size="small"
|
|
159
|
+
>
|
|
160
|
+
<ToggleButton value="list">
|
|
161
|
+
<ViewListIcon sx={{ mr: 1 }} />
|
|
162
|
+
List
|
|
163
|
+
</ToggleButton>
|
|
164
|
+
<ToggleButton value="tree">
|
|
165
|
+
<AccountTreeIcon sx={{ mr: 1 }} />
|
|
166
|
+
Tree
|
|
167
|
+
</ToggleButton>
|
|
168
|
+
</ToggleButtonGroup>
|
|
169
|
+
</Box>
|
|
170
|
+
</Box>
|
|
171
|
+
|
|
172
|
+
<Grid container spacing={2} sx={{ height: 'calc(100vh - 200px)' }}>
|
|
173
|
+
<Grid item xs={12} md={selectedDepartment ? 6 : 12}>
|
|
174
|
+
<DepartmentTreeView onDepartmentClick={handleDepartmentClick} />
|
|
175
|
+
</Grid>
|
|
176
|
+
{selectedDepartment && (
|
|
177
|
+
<Grid item xs={12} md={6}>
|
|
178
|
+
<Paper sx={{ p: 2, height: '100%', overflow: 'auto' }}>
|
|
179
|
+
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
|
180
|
+
<Typography variant="h6">
|
|
181
|
+
{selectedDepartment.DepartmentName}
|
|
182
|
+
</Typography>
|
|
183
|
+
<Button
|
|
184
|
+
variant="outlined"
|
|
185
|
+
onClick={() => setSelectedDepartment(null)}
|
|
186
|
+
size="small"
|
|
187
|
+
>
|
|
188
|
+
Close
|
|
189
|
+
</Button>
|
|
190
|
+
</Box>
|
|
191
|
+
<DepartmentDetails department={selectedDepartment} />
|
|
192
|
+
</Paper>
|
|
193
|
+
</Grid>
|
|
194
|
+
)}
|
|
195
|
+
</Grid>
|
|
196
|
+
</Box>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<Box sx={{ p: 2 }}>
|
|
202
|
+
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
|
203
|
+
<Typography variant="h4">Departments</Typography>
|
|
204
|
+
<ToggleButtonGroup
|
|
205
|
+
value={viewMode}
|
|
206
|
+
exclusive
|
|
207
|
+
onChange={handleViewChange}
|
|
208
|
+
size="small"
|
|
209
|
+
>
|
|
210
|
+
<ToggleButton value="list">
|
|
211
|
+
<ViewListIcon sx={{ mr: 1 }} />
|
|
212
|
+
List
|
|
213
|
+
</ToggleButton>
|
|
214
|
+
<ToggleButton value="tree">
|
|
215
|
+
<AccountTreeIcon sx={{ mr: 1 }} />
|
|
216
|
+
Tree
|
|
217
|
+
</ToggleButton>
|
|
218
|
+
</ToggleButtonGroup>
|
|
219
|
+
</Box>
|
|
220
|
+
<List {...props} sort={{ field: 'DepartmentName', order: 'ASC' }} pagination={<Pagination rowsPerPageOptions={[5, 10, 25]} showFirstLastButtons />}>
|
|
221
|
+
<Datagrid rowClick="show">
|
|
222
|
+
<TextField source="DepartmentName" label="Department Name" />
|
|
223
|
+
<NumberField source="SecurityLevel" label="Security Level" />
|
|
224
|
+
<ReferenceField source="DepartmentId" reference="Department" label="Parent Department">
|
|
225
|
+
<TextField source="DepartmentName" />
|
|
226
|
+
</ReferenceField>
|
|
227
|
+
</Datagrid>
|
|
228
|
+
</List>
|
|
229
|
+
</Box>
|
|
230
|
+
);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Department Details Component (for the split view)
|
|
234
|
+
const DepartmentDetails = ({ department }) => {
|
|
235
|
+
const [tabValue, setTabValue] = useState(0);
|
|
236
|
+
|
|
237
|
+
const { data: allDepartments } = useGetList('Department', {
|
|
238
|
+
pagination: { page: 1, perPage: 1000 },
|
|
239
|
+
sort: { field: 'DepartmentName', order: 'ASC' }
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const { data: employees } = useGetList('Employee', {
|
|
243
|
+
filter: { WorksForDepartmentId: department.id },
|
|
244
|
+
pagination: { page: 1, perPage: 100 },
|
|
245
|
+
sort: { field: 'LastName', order: 'ASC' }
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Filter sub-departments on the client side (fix the ID comparison)
|
|
249
|
+
const subDepartments = allDepartments?.filter(dept =>
|
|
250
|
+
dept.DepartmentId && parseInt(dept.DepartmentId) === parseInt(department.id)
|
|
251
|
+
) || [];
|
|
252
|
+
|
|
253
|
+
// Find parent department if exists (fix the ID comparison)
|
|
254
|
+
const parentDepartment = department.DepartmentId ?
|
|
255
|
+
allDepartments?.find(dept => parseInt(dept.id) === parseInt(department.DepartmentId)) : null;
|
|
256
|
+
|
|
257
|
+
const handleTabChange = (event, newValue) => {
|
|
258
|
+
setTabValue(newValue);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
return (
|
|
262
|
+
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
|
263
|
+
{/* Department Information Header */}
|
|
264
|
+
<Box sx={{ mb: 3 }}>
|
|
265
|
+
<Typography variant="h5" component="h2" sx={{ mb: 2, fontWeight: 'bold' }}>
|
|
266
|
+
Department Information
|
|
267
|
+
</Typography>
|
|
268
|
+
<Grid container spacing={3} sx={{ mb: 2 }}>
|
|
269
|
+
<Grid item xs={12} sm={6} md={4}>
|
|
270
|
+
<Box sx={{ p: 1 }}>
|
|
271
|
+
<Typography variant="body2" color="text.secondary" sx={{ mb: 0.5 }}>
|
|
272
|
+
Department Name
|
|
273
|
+
</Typography>
|
|
274
|
+
<Typography variant="body1" sx={{ fontWeight: 'medium' }}>
|
|
275
|
+
{department.DepartmentName}
|
|
276
|
+
</Typography>
|
|
277
|
+
</Box>
|
|
278
|
+
</Grid>
|
|
279
|
+
<Grid item xs={12} sm={6} md={4}>
|
|
280
|
+
<Box sx={{ p: 1 }}>
|
|
281
|
+
<Typography variant="body2" color="text.secondary" sx={{ mb: 0.5 }}>
|
|
282
|
+
Security Level
|
|
283
|
+
</Typography>
|
|
284
|
+
<Typography variant="body1" sx={{ fontWeight: 'medium' }}>
|
|
285
|
+
{department.SecurityLevel}
|
|
286
|
+
</Typography>
|
|
287
|
+
</Box>
|
|
288
|
+
</Grid>
|
|
289
|
+
<Grid item xs={12} sm={6} md={4}>
|
|
290
|
+
<Box sx={{ p: 1 }}>
|
|
291
|
+
<Typography variant="body2" color="text.secondary" sx={{ mb: 0.5 }}>
|
|
292
|
+
Parent Department
|
|
293
|
+
</Typography>
|
|
294
|
+
<Typography variant="body1" sx={{ fontWeight: 'medium' }}>
|
|
295
|
+
{parentDepartment ? parentDepartment.DepartmentName : 'No Parent'}
|
|
296
|
+
</Typography>
|
|
297
|
+
</Box>
|
|
298
|
+
</Grid>
|
|
299
|
+
</Grid>
|
|
300
|
+
<Divider sx={{ my: 2 }} />
|
|
301
|
+
</Box>
|
|
302
|
+
|
|
303
|
+
{/* Tabbed Content */}
|
|
304
|
+
<Box sx={{ flexGrow: 1 }}>
|
|
305
|
+
<Tabs value={tabValue} onChange={handleTabChange} sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
|
306
|
+
<MuiTab label={`Sub-Departments (${subDepartments.length})`} />
|
|
307
|
+
<MuiTab label={`Employees (${employees?.length || 0})`} />
|
|
308
|
+
</Tabs>
|
|
309
|
+
|
|
310
|
+
{/* Tab Panels */}
|
|
311
|
+
{tabValue === 0 && (
|
|
312
|
+
<Box sx={{ p: 2, height: 'calc(100% - 48px)', overflow: 'auto' }}>
|
|
313
|
+
{subDepartments.length === 0 ? (
|
|
314
|
+
<Typography variant="body2" color="text.secondary">
|
|
315
|
+
No sub-departments found
|
|
316
|
+
</Typography>
|
|
317
|
+
) : (
|
|
318
|
+
<Box>
|
|
319
|
+
{subDepartments.map(subDept => (
|
|
320
|
+
<Paper key={subDept.id} sx={{ p: 2, mb: 1 }}>
|
|
321
|
+
<Grid container spacing={2}>
|
|
322
|
+
<Grid item xs={6}>
|
|
323
|
+
<Typography variant="body2" color="text.secondary">Department Name</Typography>
|
|
324
|
+
<Typography variant="body1">{subDept.DepartmentName}</Typography>
|
|
325
|
+
</Grid>
|
|
326
|
+
<Grid item xs={6}>
|
|
327
|
+
<Typography variant="body2" color="text.secondary">Security Level</Typography>
|
|
328
|
+
<Typography variant="body1">{subDept.SecurityLevel}</Typography>
|
|
329
|
+
</Grid>
|
|
330
|
+
</Grid>
|
|
331
|
+
</Paper>
|
|
332
|
+
))}
|
|
333
|
+
</Box>
|
|
334
|
+
)}
|
|
335
|
+
</Box>
|
|
336
|
+
)}
|
|
337
|
+
|
|
338
|
+
{tabValue === 1 && (
|
|
339
|
+
<Box sx={{ p: 2, height: 'calc(100% - 48px)', overflow: 'auto' }}>
|
|
340
|
+
{!employees || employees.length === 0 ? (
|
|
341
|
+
<Typography variant="body2" color="text.secondary">
|
|
342
|
+
No employees found
|
|
343
|
+
</Typography>
|
|
344
|
+
) : (
|
|
345
|
+
<EmployeeGrid employees={employees} />
|
|
346
|
+
)}
|
|
347
|
+
</Box>
|
|
348
|
+
)}
|
|
349
|
+
</Box>
|
|
350
|
+
</Box>
|
|
351
|
+
);
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
// Employee Grid Component (for displaying employees in table format)
|
|
355
|
+
const EmployeeGrid = ({ employees }) => {
|
|
356
|
+
const redirect = useRedirect();
|
|
357
|
+
|
|
358
|
+
const handleEmployeeClick = (employee) => {
|
|
359
|
+
redirect(`/Employee/${employee.id}/show`);
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
<Paper sx={{ width: '100%', overflow: 'hidden' }}>
|
|
364
|
+
<Box sx={{
|
|
365
|
+
display: 'grid',
|
|
366
|
+
gridTemplateColumns: '2fr 2fr 1.5fr 1fr',
|
|
367
|
+
gap: 0,
|
|
368
|
+
'& > div': {
|
|
369
|
+
p: 1,
|
|
370
|
+
borderBottom: '1px solid',
|
|
371
|
+
borderColor: 'divider',
|
|
372
|
+
borderRight: '1px solid',
|
|
373
|
+
borderRightColor: 'divider',
|
|
374
|
+
}
|
|
375
|
+
}}>
|
|
376
|
+
{/* Header Row */}
|
|
377
|
+
<Box sx={{ fontWeight: 'bold', bgcolor: 'grey.100' }}>
|
|
378
|
+
<Typography variant="body2" color="text.secondary">Name</Typography>
|
|
379
|
+
</Box>
|
|
380
|
+
<Box sx={{ fontWeight: 'bold', bgcolor: 'grey.100' }}>
|
|
381
|
+
<Typography variant="body2" color="text.secondary">Title</Typography>
|
|
382
|
+
</Box>
|
|
383
|
+
<Box sx={{ fontWeight: 'bold', bgcolor: 'grey.100' }}>
|
|
384
|
+
<Typography variant="body2" color="text.secondary">Hire Date</Typography>
|
|
385
|
+
</Box>
|
|
386
|
+
<Box sx={{ fontWeight: 'bold', bgcolor: 'grey.100', borderRight: 'none' }}>
|
|
387
|
+
<Typography variant="body2" color="text.secondary">Salary</Typography>
|
|
388
|
+
</Box>
|
|
389
|
+
|
|
390
|
+
{/* Data Rows */}
|
|
391
|
+
{employees.map(employee => (
|
|
392
|
+
<React.Fragment key={employee.id}>
|
|
393
|
+
<Box
|
|
394
|
+
sx={{
|
|
395
|
+
cursor: 'pointer',
|
|
396
|
+
'&:hover': { bgcolor: 'action.hover' },
|
|
397
|
+
transition: 'background-color 0.2s'
|
|
398
|
+
}}
|
|
399
|
+
onClick={() => handleEmployeeClick(employee)}
|
|
400
|
+
>
|
|
401
|
+
<Typography variant="body2" sx={{ fontWeight: 'medium' }}>
|
|
402
|
+
{employee.FirstName} {employee.LastName}
|
|
403
|
+
</Typography>
|
|
404
|
+
</Box>
|
|
405
|
+
<Box
|
|
406
|
+
sx={{
|
|
407
|
+
cursor: 'pointer',
|
|
408
|
+
'&:hover': { bgcolor: 'action.hover' },
|
|
409
|
+
transition: 'background-color 0.2s'
|
|
410
|
+
}}
|
|
411
|
+
onClick={() => handleEmployeeClick(employee)}
|
|
412
|
+
>
|
|
413
|
+
<Typography variant="body2">
|
|
414
|
+
{employee.Title || 'N/A'}
|
|
415
|
+
</Typography>
|
|
416
|
+
</Box>
|
|
417
|
+
<Box
|
|
418
|
+
sx={{
|
|
419
|
+
cursor: 'pointer',
|
|
420
|
+
'&:hover': { bgcolor: 'action.hover' },
|
|
421
|
+
transition: 'background-color 0.2s'
|
|
422
|
+
}}
|
|
423
|
+
onClick={() => handleEmployeeClick(employee)}
|
|
424
|
+
>
|
|
425
|
+
<Typography variant="body2">
|
|
426
|
+
{employee.HireDate ? new Date(employee.HireDate).toLocaleDateString() : 'N/A'}
|
|
427
|
+
</Typography>
|
|
428
|
+
</Box>
|
|
429
|
+
<Box
|
|
430
|
+
sx={{
|
|
431
|
+
cursor: 'pointer',
|
|
432
|
+
'&:hover': { bgcolor: 'action.hover' },
|
|
433
|
+
transition: 'background-color 0.2s',
|
|
434
|
+
borderRight: 'none'
|
|
435
|
+
}}
|
|
436
|
+
onClick={() => handleEmployeeClick(employee)}
|
|
437
|
+
>
|
|
438
|
+
<Typography variant="body2">
|
|
439
|
+
{employee.Salary ? `$${employee.Salary.toLocaleString()}` : 'N/A'}
|
|
440
|
+
</Typography>
|
|
441
|
+
</Box>
|
|
442
|
+
</React.Fragment>
|
|
443
|
+
))}
|
|
444
|
+
</Box>
|
|
445
|
+
</Paper>
|
|
446
|
+
);
|
|
447
|
+
};
|
|
22
448
|
|
|
23
449
|
// Department Show
|
|
24
450
|
export const DepartmentShow = (props) => (
|
|
@@ -43,12 +469,36 @@ export const DepartmentShow = (props) => (
|
|
|
43
469
|
</Labeled>
|
|
44
470
|
</Box>
|
|
45
471
|
</Grid>
|
|
472
|
+
<Grid item xs={12} sm={6} md={3}>
|
|
473
|
+
<Box sx={{ p: 1 }}>
|
|
474
|
+
<Labeled label="Parent Department">
|
|
475
|
+
<ReferenceField source="DepartmentId" reference="Department" emptyText="No Parent">
|
|
476
|
+
<TextField source="DepartmentName" />
|
|
477
|
+
</ReferenceField>
|
|
478
|
+
</Labeled>
|
|
479
|
+
</Box>
|
|
480
|
+
</Grid>
|
|
46
481
|
</Grid>
|
|
47
482
|
<Divider sx={{ my: 2 }} />
|
|
48
483
|
</Box>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<
|
|
484
|
+
|
|
485
|
+
{/* Sub-Departments */}
|
|
486
|
+
<Box sx={{ mb: 3 }}>
|
|
487
|
+
<Typography variant="h6" sx={{ mb: 2 }}>Sub-Departments</Typography>
|
|
488
|
+
<ReferenceManyField reference="Department" target="DepartmentId" addLabel={false} pagination={<Pagination />}>
|
|
489
|
+
<Datagrid rowClick="show">
|
|
490
|
+
<TextField source="DepartmentName" label="Department Name" />
|
|
491
|
+
<NumberField source="SecurityLevel" label="Security Level" />
|
|
492
|
+
<EditButton />
|
|
493
|
+
<DeleteButton />
|
|
494
|
+
</Datagrid>
|
|
495
|
+
</ReferenceManyField>
|
|
496
|
+
<AddSubDepartmentButton />
|
|
497
|
+
</Box>
|
|
498
|
+
|
|
499
|
+
{/* Employees */}
|
|
500
|
+
<Box>
|
|
501
|
+
<Typography variant="h6" sx={{ mb: 2 }}>Employee List</Typography>
|
|
52
502
|
<ReferenceManyField reference="Employee" target="OnLoanDepartmentId" addLabel={false} pagination={<Pagination />}>
|
|
53
503
|
<Datagrid rowClick="show">
|
|
54
504
|
<TextField source="LastName" label="Last Name" />
|
|
@@ -58,12 +508,33 @@ export const DepartmentShow = (props) => (
|
|
|
58
508
|
<DeleteButton />
|
|
59
509
|
</Datagrid>
|
|
60
510
|
</ReferenceManyField>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
</TabbedShowLayout>
|
|
511
|
+
</Box>
|
|
512
|
+
</SimpleShowLayout>
|
|
64
513
|
</Show>
|
|
65
514
|
);
|
|
66
515
|
|
|
516
|
+
// Custom Add Sub-Department Button
|
|
517
|
+
const AddSubDepartmentButton = () => {
|
|
518
|
+
const record = useRecordContext();
|
|
519
|
+
const redirect = useRedirect();
|
|
520
|
+
|
|
521
|
+
const handleClick = () => {
|
|
522
|
+
redirect(`/Department/create?source=${encodeURIComponent(JSON.stringify({ DepartmentId: record?.id }))}`);
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<Button
|
|
527
|
+
variant="contained"
|
|
528
|
+
color="primary"
|
|
529
|
+
startIcon={<AddIcon />}
|
|
530
|
+
onClick={handleClick}
|
|
531
|
+
sx={{ mt: 2 }}
|
|
532
|
+
>
|
|
533
|
+
Add Sub-Department
|
|
534
|
+
</Button>
|
|
535
|
+
);
|
|
536
|
+
};
|
|
537
|
+
|
|
67
538
|
// Custom Add Employee Button
|
|
68
539
|
const AddEmployeeButton = () => {
|
|
69
540
|
const record = useRecordContext();
|
|
@@ -105,6 +576,13 @@ export const DepartmentCreate = (props) => (
|
|
|
105
576
|
<NumberInput source="SecurityLevel" label="Security Level" fullWidth />
|
|
106
577
|
</Box>
|
|
107
578
|
</Grid>
|
|
579
|
+
<Grid item xs={12} sm={6} md={4}>
|
|
580
|
+
<Box sx={{ p: 1 }}>
|
|
581
|
+
<ReferenceInput source="DepartmentId" reference="Department" label="Parent Department">
|
|
582
|
+
<SelectInput optionText="DepartmentName" />
|
|
583
|
+
</ReferenceInput>
|
|
584
|
+
</Box>
|
|
585
|
+
</Grid>
|
|
108
586
|
</Grid>
|
|
109
587
|
</Box>
|
|
110
588
|
</SimpleForm>
|
|
@@ -130,15 +608,24 @@ export const DepartmentEdit = (props) => (
|
|
|
130
608
|
<NumberInput source="SecurityLevel" label="Security Level" fullWidth />
|
|
131
609
|
</Box>
|
|
132
610
|
</Grid>
|
|
611
|
+
<Grid item xs={12} sm={6} md={4}>
|
|
612
|
+
<Box sx={{ p: 1 }}>
|
|
613
|
+
<ReferenceInput source="DepartmentId" reference="Department" label="Parent Department">
|
|
614
|
+
<SelectInput optionText="DepartmentName" />
|
|
615
|
+
</ReferenceInput>
|
|
616
|
+
</Box>
|
|
617
|
+
</Grid>
|
|
133
618
|
</Grid>
|
|
134
619
|
</Box>
|
|
135
620
|
</SimpleForm>
|
|
136
621
|
</Edit>
|
|
137
622
|
);
|
|
138
623
|
|
|
139
|
-
|
|
624
|
+
const DepartmentResource = {
|
|
140
625
|
list: DepartmentList,
|
|
141
626
|
show: DepartmentShow,
|
|
142
627
|
create: DepartmentCreate,
|
|
143
628
|
edit: DepartmentEdit,
|
|
144
|
-
};
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
export default DepartmentResource;
|