diff --git a/frontend/src/components/Locations/AddLocationModal.tsx b/frontend/src/components/Locations/AddLocationModal.tsx deleted file mode 100644 index 9ddd382be..000000000 --- a/frontend/src/components/Locations/AddLocationModal.tsx +++ /dev/null @@ -1,223 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - Dialog, - DialogTitle, - DialogContent, - DialogActions, - TextField, - Button, - Select, - MenuItem, - FormControl, - InputLabel, -} from '@mui/material'; -import { PlacesSearch } from './PlacesSearch'; - -// TODO : Move to the index.ts since it is a constant -const CAMPUS_OPTIONS = [ - 'North Campus', - 'West Campus', - 'Central Campus', - 'South Campus', - 'Commons', - 'Other', -] as const; - -interface Location { - name: string; - address: string; - info: string; - tag: string; - lat: number; - lng: number; -} - -interface AddLocationModalProps { - open: boolean; - onClose: () => void; - onAdd: (location: Location) => void; -} - -export const AddLocationModal: React.FC = ({ - open, - onClose, - onAdd, -}) => { - const [newLocation, setNewLocation] = useState({ - name: '', - address: '', - info: '', - tag: 'Other', - lat: 0, - lng: 0, - }); - - const [errors, setErrors] = useState({ - name: '', - info: '', - }); - - useEffect(() => { - if (!open) { - setNewLocation({ - name: '', - address: '', - info: '', - tag: 'Other', - lat: 0, - lng: 0, - }); - setErrors({ - name: '', - info: '', - }); - } - }, [open]); - - const handleAddressSelect = (address: string, lat: number, lng: number) => { - console.log('Address selected:', { address, lat, lng }); - setNewLocation((prev) => ({ - ...prev, - address, - lat, - lng, - })); - }; - - const validateName = (name: string) => { - if (!name) { - return 'Location name is required'; - } - if (!/^[a-zA-Z0-9\s]+$/.test(name)) { - return 'Only alphanumeric characters and spaces allowed'; - } - return ''; - }; - - const handleNameChange = (e: React.ChangeEvent) => { - const name = e.target.value; - const error = validateName(name); - setErrors((prev) => ({ ...prev, name: error })); - setNewLocation((prev) => ({ ...prev, name })); - }; - - const handleSubmit = () => { - const nameError = validateName(newLocation.name); - const infoError = !newLocation.info ? 'Description is required' : ''; - - setErrors({ - name: nameError, - info: infoError, - }); - - if ( - !nameError && - !infoError && - newLocation.address && - newLocation.lat && - newLocation.lng - ) { - console.log('Submitting location:', newLocation); - onAdd(newLocation); - onClose(); - } - }; - - const handleClose = () => { - console.log('Closing modal'); - onClose(); - }; - - return ( - - Add New Location - -
- - -
- - -
- - { - const info = e.target.value; - setErrors((prev) => ({ - ...prev, - info: info ? '' : 'Description is required', - })); - setNewLocation((prev) => ({ ...prev, info })); - }} - error={!!errors.info} - helperText={errors.info} - /> - - - Campus - - -
-
- - - - -
- ); -}; diff --git a/frontend/src/components/Locations/LocationDialog.tsx b/frontend/src/components/Locations/LocationDialog.tsx index 0648500e2..3343c248f 100644 --- a/frontend/src/components/Locations/LocationDialog.tsx +++ b/frontend/src/components/Locations/LocationDialog.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Modal, Paper, @@ -9,6 +9,8 @@ import { Box, } from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; +import EditIcon from '@mui/icons-material/Edit'; +import { LocationFormModal } from './LocationFormModal'; import styles from './locations.module.css'; interface Location { @@ -24,75 +26,106 @@ interface Location { interface LocationDialogProps { location: Location | null; onClose: () => void; + onSave: (location: Location) => void; } const LocationDialog: React.FC = ({ location, onClose, + onSave, }) => { - if (!location) return null; + const [isEditMode, setIsEditMode] = useState(false); + const [currentLocation, setCurrentLocation] = useState(null); + + // Update currentLocation when location prop changes + useEffect(() => { + setCurrentLocation(location); + }, [location]); + + if (!location || !currentLocation) return null; + + const handleEditSubmit = (updatedLocation: Location) => { + onSave(updatedLocation); + setCurrentLocation(updatedLocation); + setIsEditMode(false); + }; return ( - - + - {/* Close button */} - e.stopPropagation()} // Prevent closing when clicking inside > - - + + + + + + + {currentLocation.name} + + + - {/* Title and tag */} - - - {location.name} - - - +
+ + Address + + {currentLocation.address} - {/* Content */} -
- - Address - - {location.address} + + Information + + {currentLocation.info} +
- - Information - - {location.info} -
+ + + + +
+
- {/* Actions */} - - - - - + setIsEditMode(false)} + onSubmit={handleEditSubmit} + initialData={currentLocation} + mode="edit" + /> + ); }; diff --git a/frontend/src/components/Locations/LocationFormModal.tsx b/frontend/src/components/Locations/LocationFormModal.tsx new file mode 100644 index 000000000..3d05cfe73 --- /dev/null +++ b/frontend/src/components/Locations/LocationFormModal.tsx @@ -0,0 +1,183 @@ +import React, { useState, useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + Button, + Select, + MenuItem, + FormControl, + InputLabel, +} from '@mui/material'; +import { PlacesSearch } from './PlacesSearch'; + +const CAMPUS_OPTIONS = [ + 'North Campus', + 'West Campus', + 'Central Campus', + 'South Campus', + 'Commons', + 'Other', +] as const; + +interface Location { + id: number; + name: string; + address: string; + info: string; + tag: string; + lat: number; + lng: number; +} + +interface LocationFormModalProps { + open: boolean; + onClose: () => void; + onSubmit: (location: Location) => void; + initialData?: Location; // For edit mode + mode: 'add' | 'edit'; +} + +export const LocationFormModal = ({ + open, + onClose, + onSubmit, + initialData, + mode, +}: LocationFormModalProps) => { + const [formData, setFormData] = useState({ + id: initialData?.id ?? 0, + name: '', + address: '', + info: '', + tag: 'Other', + lat: 0, + lng: 0, + }); + + useEffect(() => { + if (open && initialData && mode === 'edit') { + setFormData(initialData); + } else if (!open) { + setFormData({ + id: 0, + name: '', + address: '', + info: '', + tag: 'Other', + lat: 0, + lng: 0, + }); + } + }, [open, initialData, mode]); + + const handleAddressSelect = (address: string, lat: number, lng: number) => { + setFormData((prev) => ({ + ...prev, + address, + lat, + lng, + })); + }; + + const handleSubmit = () => { + onSubmit(formData); + onClose(); + }; + + return ( + + + {mode === 'add' ? 'Add New Location' : 'Edit Location'} + + +
+ + setFormData((prev) => ({ ...prev, name: e.target.value })) + } + /> + + {mode === 'add' ? ( +
+ + +
+ ) : ( + + )} + + + setFormData((prev) => ({ ...prev, info: e.target.value })) + } + /> + + + Campus + + +
+
+ + + + +
+ ); +}; diff --git a/frontend/src/components/Locations/LocationsContent.tsx b/frontend/src/components/Locations/LocationsContent.tsx index dde10007d..b91ff9b5d 100644 --- a/frontend/src/components/Locations/LocationsContent.tsx +++ b/frontend/src/components/Locations/LocationsContent.tsx @@ -18,9 +18,13 @@ interface Location { interface LocationsContentProps { locations: Location[]; + onUpdateLocation?: (updatedLocation: Location) => void; } -const LocationsContent: React.FC = ({ locations }) => { +const LocationsContent: React.FC = ({ + locations, + onUpdateLocation, +}) => { const [filteredLocations, setFilteredLocations] = useState(locations); const [selectedLocation, setSelectedLocation] = useState( @@ -42,10 +46,26 @@ const LocationsContent: React.FC = ({ locations }) => { setFilteredLocations(filteredItems); }; - // Handle list item click - opens dialog directly const handleListItemClick = (location: Location) => { - setSelectedLocation(location); // For map centering - setSelectedLocationForDialog(location); // Opens dialog + setSelectedLocation(location); + setSelectedLocationForDialog(location); + }; + + const handleLocationUpdate = (updatedLocation: Location) => { + if (onUpdateLocation) { + onUpdateLocation(updatedLocation); + + // Update the selected location with new data + setSelectedLocation(updatedLocation); + setSelectedLocationForDialog(updatedLocation); + + // Update the filtered locations list + setFilteredLocations((prev) => + prev.map((loc) => + loc.id === updatedLocation.id ? updatedLocation : loc + ) + ); + } }; return ( @@ -118,6 +138,7 @@ const LocationsContent: React.FC = ({ locations }) => { setSelectedLocationForDialog(null)} + onSave={handleLocationUpdate} /> ); diff --git a/frontend/src/components/Locations/locations.module.css b/frontend/src/components/Locations/locations.module.css index 12ce2d26e..bf2176f0e 100644 --- a/frontend/src/components/Locations/locations.module.css +++ b/frontend/src/components/Locations/locations.module.css @@ -131,13 +131,6 @@ padding-top: 1rem; } -.inputLabel { - display: block; - margin-bottom: 0.5rem; - color: rgba(0, 0, 0, 0.6); - font-size: 0.9rem; -} - /* Places autocomplete */ .placesAutocomplete { position: relative; @@ -175,6 +168,7 @@ background-color: rgba(0, 0, 0, 0.04); } +/* Map popup styling */ .mapPopup { padding: 0.5rem; min-width: 200px; @@ -191,17 +185,3 @@ font-size: 0.9rem; color: #666; } - -.dialogContent { - padding: 1rem 0; -} - -.dialogContent h6 { - color: #1976d2; - margin-bottom: 0.5rem; -} - -.dialogContent p { - margin-bottom: 1.5rem; - line-height: 1.5; -} diff --git a/frontend/src/pages/Admin/Locations.tsx b/frontend/src/pages/Admin/Locations.tsx index 6678295ca..be1382584 100644 --- a/frontend/src/pages/Admin/Locations.tsx +++ b/frontend/src/pages/Admin/Locations.tsx @@ -4,8 +4,8 @@ import { Button } from '../../components/FormElements/FormElements'; import CopyButton from '../../components/CopyButton/CopyButton'; import Notification from '../../components/Notification/Notification'; import LocationsContent from 'components/Locations/LocationsContent'; -import { AddLocationModal } from 'components/Locations/AddLocationModal'; import styles from './page.module.css'; +import { LocationFormModal } from 'components/Locations/LocationFormModal'; // TODO : Move interface to index.ts @@ -69,6 +69,14 @@ const Locations = () => { setIsAddDialogOpen(false); }; + const handleUpdateLocation = (updatedLocation: Location) => { + setLocations((prevLocations) => + prevLocations.map((location) => + location.id === updatedLocation.id ? updatedLocation : location + ) + ); + }; + return ( { - + - setIsAddDialogOpen(false)} - onAdd={handleAddLocation} + onSubmit={handleAddLocation} + mode="add" />