Skip to content

Commit 4d7c997

Browse files
committed
feat(admin): add site creation and editing page with MongoDB integration
- Create new page for creating and editing sites - Integrate with new MongoDB endpoints for site management - Add routing for /admin/create-edit-site
1 parent 2deaff3 commit 4d7c997

File tree

5 files changed

+301
-5
lines changed

5 files changed

+301
-5
lines changed

src/admin/AdminBody.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import UserPage from './UserPage';
22
import EditSite from './EditSite';
33
import EditData from './EditData';
44
import ListSites from './ListSites';
5+
import CreateEditSite from './CreateEditSite';
56

67
interface AdminBodyProps {
78
page: AdminPage;
@@ -17,6 +18,8 @@ export default function AdminBody(props: AdminBodyProps) {
1718
return <EditData />;
1819
case 'list-sites':
1920
return <ListSites />;
21+
case 'create-edit-site':
22+
return <CreateEditSite />;
2023
default:
2124
return <h1>Error</h1>;
2225
}

src/admin/CreateEditSite.tsx

Lines changed: 290 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import React, { useState } from 'react';
2+
import {
3+
Box,
4+
Container,
5+
Paper,
6+
Typography,
7+
TextField,
8+
Button,
9+
IconButton,
10+
Select,
11+
MenuItem,
12+
FormControl,
13+
InputLabel,
14+
Checkbox,
15+
FormControlLabel,
16+
Divider,
17+
} from '@mui/material';
18+
import {
19+
ArrowBack as ArrowBackIcon,
20+
Add as AddIcon,
21+
Delete as DeleteIcon,
22+
} from '@mui/icons-material';
23+
24+
interface CellEntry {
25+
id: string;
26+
cellId: string;
27+
}
28+
29+
interface BoundaryPoint {
30+
id: string;
31+
lat: string;
32+
lng: string;
33+
}
34+
35+
export default function CreateEditSite() {
36+
const [name, setName] = useState('');
37+
const [longitude, setLongitude] = useState('');
38+
const [latitude, setLatitude] = useState('');
39+
const [status, setStatus] = useState('');
40+
const [address, setAddress] = useState('');
41+
const [cells, setCells] = useState<CellEntry[]>([]);
42+
const [colorEnabled, setColorEnabled] = useState(true);
43+
const [colorValue, setColorValue] = useState('');
44+
const [boundaryEnabled, setBoundaryEnabled] = useState(true);
45+
const [boundaryPoints, setBoundaryPoints] = useState<BoundaryPoint[]>([]);
46+
47+
const handleBack = () => {
48+
console.log('Navigate back');
49+
window.open('/admin/list-sites', '_self');
50+
};
51+
52+
const handleSave = () => {
53+
console.log('Save site');
54+
};
55+
56+
const addCell = () => {
57+
const newCell: CellEntry = {
58+
id: Date.now().toString(),
59+
cellId: ''
60+
};
61+
setCells([...cells, newCell]);
62+
};
63+
64+
const deleteCell = (id: string) => {
65+
setCells(cells.filter(cell => cell.id !== id));
66+
};
67+
68+
const updateCellId = (id: string, cellId: string) => {
69+
setCells(cells.map(cell => cell.id === id ? { ...cell, cellId } : cell));
70+
};
71+
72+
const addBoundaryPoint = () => {
73+
const newPoint: BoundaryPoint = {
74+
id: Date.now().toString(),
75+
lat: '',
76+
lng: ''
77+
};
78+
setBoundaryPoints([...boundaryPoints, newPoint]);
79+
};
80+
81+
const deleteBoundaryPoint = (id: string) => {
82+
setBoundaryPoints(boundaryPoints.filter(point => point.id !== id));
83+
};
84+
85+
const updateBoundaryPoint = (id: string, field: 'lat' | 'lng', value: string) => {
86+
setBoundaryPoints(boundaryPoints.map(point =>
87+
point.id === id ? { ...point, [field]: value } : point
88+
));
89+
};
90+
91+
return (
92+
<Container maxWidth="md" sx={{ mt: 4, mb: 4 }}>
93+
<Paper elevation={3} sx={{ p: 3 }}>
94+
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
95+
<IconButton onClick={handleBack} sx={{ mr: 2 }}>
96+
<ArrowBackIcon />
97+
</IconButton>
98+
<Button
99+
variant="contained"
100+
onClick={handleSave}
101+
sx={{
102+
backgroundColor: '#4caf50',
103+
'&:hover': {
104+
backgroundColor: '#388e3c',
105+
},
106+
}}
107+
>
108+
Save
109+
</Button>
110+
</Box>
111+
<Box sx={{ mb: 3 }}>
112+
<TextField
113+
fullWidth
114+
label="Name"
115+
value={name}
116+
onChange={(e) => setName(e.target.value)}
117+
sx={{ mb: 2 }}
118+
/>
119+
120+
<Box sx={{ display: 'flex', gap: 2, mb: 2 }}>
121+
<TextField
122+
fullWidth
123+
label="Longitude"
124+
value={longitude}
125+
onChange={(e) => setLongitude(e.target.value)}
126+
/>
127+
<TextField
128+
fullWidth
129+
label="Latitude"
130+
value={latitude}
131+
onChange={(e) => setLatitude(e.target.value)}
132+
/>
133+
</Box>
134+
135+
<FormControl fullWidth sx={{ mb: 2 }}>
136+
<InputLabel>Status</InputLabel>
137+
<Select
138+
value={status}
139+
onChange={(e) => setStatus(e.target.value)}
140+
label="Status"
141+
>
142+
<MenuItem value="active">Active</MenuItem>
143+
<MenuItem value="inactive">Inactive</MenuItem>
144+
<MenuItem value="maintenance">Maintenance</MenuItem>
145+
</Select>
146+
</FormControl>
147+
148+
<TextField
149+
fullWidth
150+
label="Address"
151+
value={address}
152+
onChange={(e) => setAddress(e.target.value)}
153+
sx={{ mb: 2 }}
154+
/>
155+
</Box>
156+
<Box sx={{ mb: 3 }}>
157+
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
158+
<Typography variant="h6" sx={{ mr: 2 }}>Cells</Typography>
159+
<IconButton
160+
onClick={addCell}
161+
sx={{
162+
backgroundColor: '#4caf50',
163+
color: 'white',
164+
width: 24,
165+
height: 24,
166+
'&:hover': {
167+
backgroundColor: '#388e3c',
168+
},
169+
}}
170+
>
171+
<AddIcon sx={{ fontSize: 16 }} />
172+
</IconButton>
173+
</Box>
174+
175+
{cells.map((cell) => (
176+
<Box key={cell.id} sx={{ display: 'flex', alignItems: 'center', mb: 1, gap: 2 }}>
177+
<Typography variant="body2" sx={{ minWidth: 60 }}>Cell ID</Typography>
178+
<TextField
179+
size="small"
180+
value={cell.cellId}
181+
onChange={(e) => updateCellId(cell.id, e.target.value)}
182+
sx={{ flexGrow: 1 }}
183+
/>
184+
<Button
185+
variant="contained"
186+
color="error"
187+
onClick={() => deleteCell(cell.id)}
188+
sx={{
189+
backgroundColor: '#d32f2f',
190+
minWidth: 80,
191+
'&:hover': {
192+
backgroundColor: '#b71c1c',
193+
},
194+
}}
195+
>
196+
Delete
197+
</Button>
198+
</Box>
199+
))}
200+
</Box>
201+
202+
<Divider sx={{ my: 3 }} />
203+
204+
<Box sx={{ mb: 3 }}>
205+
<FormControlLabel
206+
control={
207+
<Checkbox
208+
checked={colorEnabled}
209+
onChange={(e) => setColorEnabled(e.target.checked)}
210+
/>
211+
}
212+
label="Color"
213+
/>
214+
{colorEnabled && (
215+
<TextField
216+
fullWidth
217+
value={colorValue}
218+
onChange={(e) => setColorValue(e.target.value)}
219+
sx={{ mt: 1 }}
220+
/>
221+
)}
222+
</Box>
223+
<Box sx={{ mb: 3 }}>
224+
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
225+
<FormControlLabel
226+
control={
227+
<Checkbox
228+
checked={boundaryEnabled}
229+
onChange={(e) => setBoundaryEnabled(e.target.checked)}
230+
/>
231+
}
232+
label="Boundary"
233+
/>
234+
{boundaryEnabled && (
235+
<IconButton
236+
onClick={addBoundaryPoint}
237+
sx={{
238+
backgroundColor: '#4caf50',
239+
color: 'white',
240+
width: 24,
241+
height: 24,
242+
ml: 2,
243+
'&:hover': {
244+
backgroundColor: '#388e3c',
245+
},
246+
}}
247+
>
248+
<AddIcon sx={{ fontSize: 16 }} />
249+
</IconButton>
250+
)}
251+
</Box>
252+
253+
{boundaryEnabled && boundaryPoints.map((point) => (
254+
<Box key={point.id} sx={{ display: 'flex', alignItems: 'center', mb: 1, gap: 2 }}>
255+
<Typography variant="body2" sx={{ minWidth: 80 }}>(Lat, Long)</Typography>
256+
<TextField
257+
size="small"
258+
placeholder="Lat"
259+
value={point.lat}
260+
onChange={(e) => updateBoundaryPoint(point.id, 'lat', e.target.value)}
261+
sx={{ flexGrow: 1 }}
262+
/>
263+
<TextField
264+
size="small"
265+
placeholder="Long"
266+
value={point.lng}
267+
onChange={(e) => updateBoundaryPoint(point.id, 'lng', e.target.value)}
268+
sx={{ flexGrow: 1 }}
269+
/>
270+
<Button
271+
variant="contained"
272+
color="error"
273+
onClick={() => deleteBoundaryPoint(point.id)}
274+
sx={{
275+
backgroundColor: '#d32f2f',
276+
minWidth: 80,
277+
'&:hover': {
278+
backgroundColor: '#b71c1c',
279+
},
280+
}}
281+
>
282+
Delete
283+
</Button>
284+
</Box>
285+
))}
286+
</Box>
287+
</Paper>
288+
</Container>
289+
);
290+
}

src/admin/ListSites.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export default function ListSites() {
4646
const [sites, setSites] = useState<components['schemas']['Site'][]>([]);
4747
const handleEdit = (siteName: string) => {
4848
console.log(`Edit site with ID: ${siteName}`);
49+
window.open('/admin/create-edit-site', '_self');
4950
};
5051

5152
const handleDelete = (siteName: string) => {
@@ -58,6 +59,7 @@ export default function ListSites() {
5859

5960
const handleAdd = () => {
6061
console.log('Add new site');
62+
window.open('/admin/create-edit-site', '_self')
6163
};
6264

6365
const reloadSites = () => {
@@ -125,7 +127,7 @@ export default function ListSites() {
125127
}).catch(err => {
126128
console.error(`Error creating site: ${err}`);
127129
});
128-
130+
}
129131
return (
130132
<Container maxWidth='md' sx={{ mt: 4, mb: 4 }}>
131133
<Paper elevation={3} sx={{ p: 3 }}>
@@ -185,8 +187,6 @@ export default function ListSites() {
185187
))}
186188
</List>
187189
</Paper>
188-
189-
{/* Floating Action Button */}
190190
<Fab
191191
color='primary'
192192
aria-label='add'
@@ -206,4 +206,3 @@ export default function ListSites() {
206206
</Container>
207207
);
208208
}
209-
}

src/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ root.render(
2525
<Route path='/admin' element={<AdminPortal />} />
2626
<Route path='/admin/users' element={<AdminPortal page={'users'} />} />
2727
<Route path='/admin/list-sites' element={<ListSites />} />
28+
<Route
29+
path='/admin/create-edit-site'
30+
element={<AdminPortal page='create-edit-site' />}
31+
/>
2832
<Route
2933
path='/admin/edit-site'
3034
element={<AdminPortal page='edit-site' />}

src/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type DisplayOption = {
2222

2323
type SiteStatus = 'active' | 'confirmed' | 'in-conversation' | 'unknown';
2424

25-
type AdminPage = 'users' | 'edit-site' | 'edit-data' | 'list-sites' | 'test';
25+
type AdminPage = 'users' | 'edit-site' | 'edit-data' | 'list-sites' | 'create-edit-site';
2626

2727
type UserRow = {
2828
identity: string;

0 commit comments

Comments
 (0)