diff --git a/src/Components/AdditionalFields.jsx b/src/Components/AdditionalFields.jsx index 676976b..b578c41 100644 --- a/src/Components/AdditionalFields.jsx +++ b/src/Components/AdditionalFields.jsx @@ -27,70 +27,86 @@ const tempTasks = [ { id: 1, name: 'Search Area 1', - startTime: '2025-03-15T23:54:26.305Z', + startTime: dayjs('2025-03-15T23:54:26.305Z').toString(), }, { id: 2, name: 'Search Area 2', - startTime: '2025-03-15T23:54:26.305Z', + startTime: dayjs('2025-03-15T23:54:26.305Z').toString(), }, { id: 3, name: 'Search Area 3', - startTime: '2025-03-15T23:54:26.305Z', + startTime: dayjs('2025-03-15T23:54:26.305Z').toString(), }, ]; -export default function AdditionalFields({ incidentId, changePath }) { +export default function AdditionalFields({ + changePath, + appRouter, + incidentId, + opInfo, +}) { const [selectedCard, setSelectedCard] = useState(0); return ( - + + Tasks - {tempTasks.map((task) => ( - - - - changePath( - `/incident/${incidentId}/task/${task.id}` - ) - } - data-active={ - selectedCard === task.id ? '' : undefined - } - sx={{ - height: 100, - '&[data-active]': { - backgroundColor: 'action.selected', - '&:hover': { - backgroundColor: - 'action.selectedHover', + {appRouter.pathname != '/incident/newIncident' && + tempTasks.map((task) => ( + + + + changePath( + `/incident/${incidentId}/task/${task.id}` + ) + } + data-active={ + selectedCard === task.id + ? '' + : undefined + } + sx={{ + height: 100, + '&[data-active]': { + backgroundColor: 'action.selected', + '&:hover': { + backgroundColor: + 'action.selectedHover', + }, }, - }, - }} - > - - - {task.name} - - - {task.startTime} - - - - - - ))} + }} + > + + + {task.name} + + + {task.startTime} + + + + + + ))} - + {!/incident\/pastIncident\/./.test(appRouter.pathname) && ( + + )} ); } diff --git a/src/Components/Dashboard.jsx b/src/Components/Dashboard.jsx index e730576..e649860 100644 --- a/src/Components/Dashboard.jsx +++ b/src/Components/Dashboard.jsx @@ -13,8 +13,10 @@ import PersonnelInfo from '../app/PersonnelInfo/page'; import PastIncident from '../app/PastIncident/page'; import MyCerts from '../app/MyCerts/page'; import Profile from '../app/Profile/page'; +import CompleteIncident from '../app/CompleteIncident/page'; import { useNavigate } from 'react-router-dom'; import MuiCard from '@mui/material/Card'; +import AdditionalFields from './AdditionalFields'; import Stack from '@mui/material/Stack'; import HomeIcon from '@mui/icons-material/Home'; @@ -33,6 +35,7 @@ import TroubleshootIcon from '@mui/icons-material/Troubleshoot'; import Register from '../app/Regsiter/page'; import { Widgets } from '@mui/icons-material'; import CreateTask from '../app/CreateTask/page'; +import FileUpload from './FileUpload'; const lightDarkTheme = extendTheme({ colorSchemes: { light: true, dark: true }, @@ -71,9 +74,10 @@ const ContentContainer = styled(Stack)(({ theme }) => ({ }, })); -export default function Dashboard(props) { +export default function Dashboard({ user }) { const [pathname, setPathname] = useState('/incident'); - + const [selectedIncident, setSelectedIncident] = useState([]); + const [addIncidentInfo, setAddIncidentInfo] = useState([]); const router = useMemo(() => { return { pathname, @@ -120,6 +124,7 @@ export default function Dashboard(props) { segment: 'activeIncident', title: 'Active Incidents', icon: , + pattern: 'activeIncident{/:incidentId}*', }, { segment: 'newIncident', @@ -197,19 +202,26 @@ export default function Dashboard(props) { icon: , }, ]; - return ( + title: ( + <> + {' '} + {' '} + {' '} + ), + logo: '', }} > @@ -224,20 +236,31 @@ export default function Dashboard(props) { justifyContent="space-between" > {router.pathname == '/incident/activeIncident' && ( - + )} {router.pathname == '/incident' && ( - + )} {router.pathname == '/incident/newIncident' && ( )} {router.pathname == '/incident/pastIncident' && ( - + )} {router.pathname == '/personnelInfo' && ( @@ -247,19 +270,57 @@ export default function Dashboard(props) { {router.pathname == '/myCertifications' && } {router.pathname == '/settings/profile' && } {router.pathname == '/userProfile' && } - {/incident\/activeIncident\/./.test( + {/activeIncident\/./.test(router.pathname) && ( + <> + + + + + )} + {/^\/incident\/([^/]+)\/newTask$/.test( router.pathname ) && ( - )} - {/incident\/.\/newTask/.test(router.pathname) && ( + {/incident\/[^/]+\/task\/[^/]+$/.test( + router.pathname + ) && ( )} + + {/incident\/pastIncident\/./.test(router.pathname) && ( + <> + + + + + )} diff --git a/src/Components/FileUpload.jsx b/src/Components/FileUpload.jsx new file mode 100644 index 0000000..93663e7 --- /dev/null +++ b/src/Components/FileUpload.jsx @@ -0,0 +1,129 @@ +import { useState } from 'react'; +import { styled } from '@mui/material/styles'; +import Button from '@mui/material/Button'; +import CloudUploadIcon from '@mui/icons-material/CloudUpload'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import { Typography } from '@mui/material'; +import Grid from '@mui/material/Grid2'; +import CardActionArea from '@mui/material/CardActionArea'; +import CardMedia from '@mui/material/CardMedia'; +import Modal from '@mui/material/Modal'; +import CardActions from '@mui/material/CardActions'; +import Box from '@mui/material/Box'; + +const VisuallyHiddenInput = styled('input')({ + clip: 'rect(0 0 0 0)', + clipPath: 'inset(50%)', + height: 1, + overflow: 'hidden', + position: 'absolute', + bottom: 0, + left: 0, + whiteSpace: 'nowrap', + width: 1, +}); + +const modalStyle = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '55vw', + bgcolor: 'background.paper', + border: '2px solid #000', + boxShadow: 24, + p: 4, +}; + +const tempImageObjects = [ + { + id: 1, + type: 'image', + content: 'src/app/TempFile/IncidentBriefing.jpg', + }, + { + id: 2, + type: 'image', + content: 'src/app/TempFile/CommsExample.jpg', + }, +]; + +export default function FileUpload({ appRouter }) { + const [selectedImage, setSelectedImage] = useState(null); + + const handleOpen = (image) => setSelectedImage(image); + const handleClose = () => setSelectedImage(null); + console.log(appRouter.pathname); + return ( + + Files + + {appRouter.pathname != '/incident/newIncident' && + tempImageObjects.map((file) => ( + + + handleOpen(file.content)} + > + + + + + ))} + + + + + + {selectedImage && ( + <> + + + + + + )}{' '} + + + + ); +} diff --git a/src/Components/ResponsiveAppBar.jsx b/src/Components/ResponsiveAppBar.jsx index 17eabdc..7fe4aa6 100644 --- a/src/Components/ResponsiveAppBar.jsx +++ b/src/Components/ResponsiveAppBar.jsx @@ -18,11 +18,9 @@ import TroubleshootIcon from '@mui/icons-material/Troubleshoot'; import { extendTheme, ThemeProvider } from '@mui/material'; import 'tailwindcss'; import Dashboard from './Dashboard'; -import Register from '../app/Regsiter/page'; import Login from '../app/Login/page'; -import LandingPage from '../app/LandingPage/page'; -const pages = ['About', 'Register', 'Login']; +const pages = ['Login']; const settings = ['Profile', 'Account', 'Dashboard', 'Logout']; export default function ResponsiveAppBar() { @@ -68,33 +66,31 @@ export default function ResponsiveAppBar() { return ( {user ? ( - + ) : ( - - - - - SAR FORGE - + + + + + + + + - - {page} - + + {page} + + ))} - - - SAR FORGE - + + + + + + + {pages.map((page) => ( @@ -180,11 +188,15 @@ export default function ResponsiveAppBar() { key={page} href={`/${page}`} onClick={handleCloseNavMenu} + variant={page == 'Login' && 'contained'} sx={{ textDecoration: 'none', my: 2, - color: '#037AFF ', + width: page == 'Login' && '8vw', + mr: page == 'Login' ? '4vw' : '2vw', display: 'block', + color: page == 'Login' && 'white', + textAlign: 'center', }} > {page} diff --git a/src/Components/Row.jsx b/src/Components/Row.jsx index 6295d6d..cb25e63 100644 --- a/src/Components/Row.jsx +++ b/src/Components/Row.jsx @@ -12,12 +12,14 @@ import Typography from '@mui/material/Typography'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import Checkbox from '@mui/material/Checkbox'; +import TextField from '@mui/material/TextField'; +import dayjs from 'dayjs'; export default function Row(props) { - const { row, setSelectedRow, selectedRows } = props; + const { row, setSelectedRow, selectedRows, setRoles, roles } = props; const [open, setOpen] = useState(false); + const [responderRole, setResponderRole] = useState([]); let isSelected = selectedRows.includes(row.id); - const handleSelect = () => { if (isSelected) { setSelectedRow(selectedRows.filter((item) => item != row.id)); @@ -26,13 +28,34 @@ export default function Row(props) { } }; - useEffect(() => {}, [selectedRows]); + const handRoleUpdate = (resRole) => { + const newResponderRole = { + responderId: row.id, + role: resRole, + }; + + setResponderRole(newResponderRole); + setRoles((roles) => { + const existingIndex = roles.findIndex( + (role) => role.responderId === row.id + ); + if (existingIndex !== -1) { + const updatedRoles = [...roles]; + updatedRoles[existingIndex] = newResponderRole; + return updatedRoles; + } else { + return [...roles, newResponderRole]; + } + }); + }; + useEffect(() => {}, [selectedRows]); return ( *': { borderBottom: 'unset' } }}> {row.name} + {/* Time in and out */} + + {row.startDate + ? dayjs(row.startDate).format('YYYY-MM-DD HH:mm:ss') + : 'N/A'} + + + {row.endDate + ? dayjs(row.endDate).format('YYYY-MM-DD HH:mm:ss') + : 'N/A'} + + + handRoleUpdate(event.target.value)} + /> + {row.phoneNumber} + {/* Certification drop down section */} {row.certificateInfo.map((cert) => ( { - changeResponderInfo((prevState) => - prevState.map((responder) => ({ - ...responder, - checkedStatus: selected.some((item) => item == responder.id) - ? !responder.checkedStatus - : responder.checkedStatus, - })) - ); - setSelected([]); - }; - - const handleCheckIn = () => { - changeStatus(selected); - }; - return ( )} - {numSelected > 0 && ( + {/* {numSelected > 0 && ( - )} + )} */} ); } -export default function ResponderTable() { - const [selected, setSelected] = useState([]); - const [responderInfo, setResponderInfo] = useState([ - { - id: 1, - name: 'Jordan Kelsey', - phone: '123-456-7890', - checkedStatus: false, - cert: tempCert1, - }, - { - id: 2, - name: 'Blake Velimirovich', - phone: '098-765-4321', - checkedStatus: false, - cert: tempCert2, - }, - { - id: 3, - name: 'Alfred Parks', - phone: '555-123-4567', - checkedStatus: false, - cert: tempCert3, - }, - ]); +const tempCert1 = { + id: 1, + name: 'Chainsaw Operation', + expiryDate: dayjs('2026-07-20T23:54:26.305Z').format('YYYY-MM-DD HH:mm:ss'), + certificate: 'Cert', +}; - const rows = responderInfo.map((info) => - createData( - info.id, - info.name, - info.phone, - info.checkedStatus, - info.cert - ) - ); +const tempCert2 = { + id: 2, + name: 'Chainsaw Operation', + expiryDate: dayjs('2026-05-05T23:54:26.305Z').format('YYYY-MM-DD HH:mm:ss'), + certificate: 'Cert', +}; + +export default function ResponderTable({ + setRoles, + roles, + responderInfo, + setResponderInfo, + selected, + setSelected, + taskID, + tempTasks, +}) { + const tempCert3 = { + id: 3, + name: 'Drone Operation', + expiryDate: dayjs('2026-03-15T23:54:26.305Z').format( + 'YYYY-MM-DD HH:mm:ss' + ), + certificate: 'Cert', + }; + + const rows = !taskID + ? responderInfo.map((responder) => + createData( + responder.responderId, + responder.responderName, + responder.phone, + responder.checkedIn, + responder.startDate, + responder.endDate, + { + id: 3, + name: 'Drone Operation', + expiryDate: dayjs('2026-03-15T23:54:26.305Z').format( + 'YYYY-MM-DD HH:mm:ss' + ), + certificate: 'Cert', + } + ) + ) + : tempTasks[taskID].team.map((info) => + createData( + info.responderId, + info.name, + info.phone, + info.checkedStatus, + info.startDate, + info.endDate, + info.cert + ) + ); + + async function getAllResponders() { + try { + const response = await axios.get( + `${import.meta.env.VITE_API_ENDPOINT}/get-allResponders` + ); + if (response.status == 200) { + console.log('Responders retrieved: ', response.data); + setResponderInfo(response.data); + } + } catch (error) { + consoel.log('Error: ', error.message); + } + } + + useEffect(() => { + if (responderInfo.length === 0) { + getAllResponders(); + } + }, []); return ( @@ -152,13 +174,16 @@ export default function ResponderTable() { changeResponderInfo={setResponderInfo} setSelected={setSelected} /> - + Checkin/out Checked In Responder Name + Start Date time + End Date time + Responder Role Phone Number Certificates @@ -170,6 +195,8 @@ export default function ResponderTable() { row={row} setSelectedRow={setSelected} selectedRows={selected} + setRoles={setRoles} + roles={roles} /> ))} diff --git a/src/app/ActiveIncident/page.jsx b/src/app/ActiveIncident/page.jsx index b4a12bb..7366905 100644 --- a/src/app/ActiveIncident/page.jsx +++ b/src/app/ActiveIncident/page.jsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Link } from '@mui/material'; import Grid from '@mui/material/Grid2'; import { styled } from '@mui/material/styles'; @@ -8,45 +8,68 @@ import Typography from '@mui/material/Typography'; import CardActionArea from '@mui/material/CardActionArea'; import CreateIncident from '../CreateIncident/page'; import { useActivePage } from '@toolpad/core/useActivePage'; +import axios from 'axios'; -const tempIncidents = [ - { - id: 1, - incidentName: 'Incident 101-03-15-2025', - agency: 'Search and Rerscue', - incidentType: 'Missing person', - }, - { - id: 2, - incidentName: 'Incident 102-03-16-2025', - agency: 'Police', - incidentType: 'Assist SAR with Missing Person Incident', - }, - { - id: 3, - incidentName: 'Incident 103-03-17-2025', - agency: 'EMO', - incidentType: 'Lost at Sea', - }, -]; +async function getIncidents() { + try { + const response = await axios.get( + `${import.meta.env.VITE_API_ENDPOINT}/get-allIncidents` + ); + if (response.status == 200) { + console.log(response.data); + return response.data; + } + } catch (error) { + console.log('Fetch failed: ', error.message); + } +} -export default function ActiveIncident({ changePath }) { - console.log(useActivePage()); +export default function ActiveIncident({ + appRouter, + setSelectedIncident, + setAddIncidentInfo, +}) { const [selectedCard, setSelectedCard] = useState(0); + const [allIncidents, setAllIncidents] = useState([]); + const [incidentInfo, setIncidentInfo] = useState([]); + useEffect(() => { + async function fetchIncidents() { + const incidents = await getIncidents(); + setAllIncidents(incidents); + } + fetchIncidents(); + }, []); + + async function getIncident(incidentId, index) { + try { + const response = await axios.get( + `${import.meta.env.VITE_API_ENDPOINT}/get-viewIncident/${incidentId}` + ); + if (response.status == 200) { + console.log('Incident retrieved: ', response.data); + // State passed down from Dashboard + setAddIncidentInfo(allIncidents[index]); + setSelectedIncident(response.data); + appRouter.navigate(`/activeIncident/${incidentId}`); + } + } catch (error) { + console.log('Error retrieving incident: ', error.message); + console.log(response); + } + } + return ( <> - - {tempIncidents.map((incident) => ( + + {allIncidents.map((incident, index) => ( - + - changePath( - `/incident/activeIncident/${incident.id}` - ) + getIncident(incident.incidentId, index) } data-active={ - selectedCard === incident.id + selectedCard === incident.incidentId ? '' : undefined } @@ -75,7 +98,7 @@ export default function ActiveIncident({ changePath }) { variant="h6" color="text.secondary" > - Agency: {incident.agency} + Agency: {incident.agencyName} diff --git a/src/app/Branding/icon.png b/src/app/Branding/icon.png new file mode 100644 index 0000000..df700e6 Binary files /dev/null and b/src/app/Branding/icon.png differ diff --git a/src/app/Branding/profileThin.png b/src/app/Branding/profileThin.png new file mode 100644 index 0000000..c6c2fd8 Binary files /dev/null and b/src/app/Branding/profileThin.png differ diff --git a/src/app/CompleteIncident/page.jsx b/src/app/CompleteIncident/page.jsx new file mode 100644 index 0000000..72cc5e8 --- /dev/null +++ b/src/app/CompleteIncident/page.jsx @@ -0,0 +1,559 @@ +import { useState, useEffect } from 'react'; +import { CssBaseline } from '@mui/material'; +import TextField from '@mui/material/TextField'; +import Container from '@mui/material/Container'; +import MenuItem from '@mui/material/MenuItem'; +import Button from '@mui/material/Button'; +import FormControl from '@mui/material/FormControl'; +import Stack from '@mui/material/Stack'; +import Box from '@mui/material/Box'; +import axios from 'axios'; +import { useActivePage } from '@toolpad/core/useActivePage'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import { Typography } from '@mui/material'; + +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { DatePicker } from '@mui/x-date-pickers/DatePicker'; +import { TimePicker } from '@mui/x-date-pickers/TimePicker'; +import dayjs from 'dayjs'; +import AdditionalFields from '../../Components/AdditionalFields'; +import FileUpload from '../../Components/FileUpload'; + +const agencies = [ + { + id: '', + value: '', + label: '', + }, + { + id: '8afd6fa8-17ce-4591-8d57-59b29933dd61', + value: 'SAR', + label: 'Search and Rescue', + }, +]; + +const incidentType = [ + { + value: '', + label: '', + }, + { + value: 'Missing Person', + label: 'Missing Person', + }, + { + value: 'Entrapment', + label: 'Entrapment', + }, + { + value: 'Structure Fire', + label: 'Structure Fire', + }, + { + value: 'Lost at Sea', + label: 'Lost at Sea', + }, + { + value: 'Other', + label: 'Other', + }, +]; + +const province = [ + { + value: '', + label: '', + }, + { + value: 'AB', + label: 'Alberta', + }, + { + value: 'BC', + label: 'British Columbia', + }, + { + value: 'MB', + label: 'Manitoba', + }, + { + value: 'NB', + label: 'New Brunswick', + }, + { + value: 'NL', + label: 'Newfoundland and Labrador', + }, + { + value: 'NT', + label: 'NorthWest Territories', + }, + { + value: 'NS', + label: 'Nova Scotia', + }, + { + value: 'NU', + label: 'Nunavut', + }, + { + value: 'ON', + label: 'Ontario', + }, + { + value: 'PEI', + label: 'Prince Edward Island', + }, + { + value: 'QC', + label: 'Quebec', + }, + { + value: 'SK', + label: 'Saskatchewan', + }, + { + value: 'YT', + label: 'Yukon', + }, +]; + +export default function CompleteIncident({ appRouter, changePathFunction }) { + const [currentUser, setCurrentUser] = useState({}); + const [incidentName, setIncidentName] = useState(''); + const [incidentCommander, setIncidentCommander] = useState(''); + const [agency, setAgency] = useState(''); + const [incidentTypeChoice, setIncidentTypeChoice] = useState(''); + const [op, setOp] = useState(''); + const [address, setAddress] = useState(''); + const [city, setCity] = useState(''); + const [postal, setPostal] = useState(''); + const [provinceChoice, setProvinceChoice] = useState(''); + const [date, setDate] = useState(''); + const [summary, setSummary] = useState(''); + const [objectives, setObjectives] = useState(''); + const [activeIncidentId, setActiveIncidentId] = useState(''); + + const [startDate, setStartDate] = useState(dayjs()); + const [startTime, setStartTime] = useState(dayjs()); + + function getCookie() { + let name = 'user='; + let decodedCookie = decodeURIComponent(document.cookie); + let ca = decodedCookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == ' ') {} + if (c.indexOf(name) == 0) { + const userInfo = JSON.parse(c.substring(name.length, c.length)); + setCurrentUser(userInfo); + } + } + return; + } + + async function handleCancel() { + setIncidentName(''); + setIncidentCommander(''); + setAddress(''); + setAgency(''); + setIncidentTypeChoice(''); + setOp(''); + setCity(''); + setPostal(''); + setProvinceChoice(''); + setSummary(''); + setObjectives(''); + } + + async function handleSubmit(e) { + e.preventDefault(); + + const payload = { + incidentName: incidentName, + incidentCommander: incidentCommander, + agency: agency, + incidentType: incidentTypeChoice, + operationPeriod: op, + address: address, + city: city, + postal: postal, + province: provinceChoice, + date: startDate.toISOString(), + summary: summary, + objectives: objectives, + }; + + // try { + // const response = await axios.post('http://localhost:5185/newIncident', payload); + + // if (response.status == 200) { + // setMessage(`Registration successful: ${response.data.message}`); + // } + // } catch (error) { + // setMessage(`Error: ${error.message}`); + // console.log(`Error: ${error.message}`); + // } + } + + const tempIncident1 = { + incidentName: 'Incident 107-05-21-2024', + incidentCommander: 'Alfred Parks', + agency: 'SAR', + incidentType: 'Lost at Sea', + operationPeriod: 1, + address: '123 Main Street', + city: 'Kentville', + postal: 'B4P1P2', + province: 'NS', + date: '2024-05-21T23:54:26.305Z', + summary: "Fishing vessel hasn't been seen for 2 days", + objectives: 'Determine search locations', + }; + const tempIncident2 = { + incidentName: 'Incident 104-07-16-2023', + incidentCommander: 'Jordan Kelsey', + agency: 'SAR', + incidentType: 'Missing Person', + operationPeriod: 1, + address: '123 Main Street', + city: 'Kentville', + postal: 'B4P1P2', + province: 'NS', + date: '2023-07-16T23:54:26.305Z', + summary: 'Elderly person escaped nursing home', + objectives: 'Determine search locations', + }; + const tempIncident3 = { + incidentName: 'Incident 108-01-17-2025', + incidentCommander: 'Bob Straford', + agency: 'SAR', + incidentType: 'Missing Person', + operationPeriod: 1, + address: '123 Pleasant St', + city: 'Wolfville', + postal: 'B4P2B9', + province: 'NS', + date: '2025-01-17T23:54:26.305Z', + summary: "Hiker hasn't been seen in 24 hours", + objectives: 'Dertime search area', + }; + + const tempIncidents = { + 1: tempIncident1, + 2: tempIncident2, + 3: tempIncident3, + }; + + function loadIncident() { + const incidentId = appRouter.pathname.split('/')[3]; + setActiveIncidentId(incidentId); + if (tempIncidents[incidentId]) { + const data = tempIncidents[incidentId]; + setIncidentName(data.incidentName); + setIncidentCommander(data.incidentCommander); + setAddress(data.address); + setAgency(data.agency); + setIncidentTypeChoice(data.incidentType); + setOp(data.operationPeriod); + setCity(data.city); + setPostal(data.postal); + setProvinceChoice(data.province); + setSummary(data.summary); + setObjectives(data.objectives); + } + } + + useEffect(() => { + getCookie(); + if (/incident\/pastIncident\/./.test(appRouter.pathname)) { + console.log(appRouter.pathname); + loadIncident(); + } + }, []); + + return ( + <> + + + Incident Information + + + + setIncidentName(event.target.value) + } + sx={{ + width: { + md: '13vw', + lg: '13vw', + xl: '13vw', + }, + margin: '1vh', + }} + /> + + + + + setIncidentCommander(event.target.value) + } + sx={{ + width: { + md: '13vw', + lg: '13vw', + xl: '13vw', + }, + margin: '1vh', + }} + /> + + + + setAgency(event.target.value)} + sx={{ + width: { + md: '13vw', + lg: '13vw', + xl: '13vw', + }, + margin: '1vh', + }} + > + {agencies.map((option) => ( + + {option.label} + + ))} + + + + + + setIncidentTypeChoice(event.target.value) + } + sx={{ + width: { + md: '13vw', + lg: '13vw', + xl: '13vw', + }, + minWidth: '10vh', + margin: '1vh', + }} + > + {incidentType.map((option) => ( + + {option.label} + + ))} + + + + + setOp(event.target.value)} + /> + + + + + setStartDate(newValue)} + /> + + + + setStartTime(newValue)} + /> + + + + + setAddress(event.target.value)} + /> + + + + + setProvinceChoice(event.target.value) + } + sx={{ + width: { + md: '13vw', + lg: '13vw', + xl: '13vw', + }, + minWidth: '10vh', + margin: '1vh', + }} + > + {province.map((option) => ( + + {option.label} + + ))} + + + + + setCity(event.target.value)} + /> + + + + setPostal(event.target.value)} + /> + + + + setSummary(event.target.value)} + maxRows={1000} + minRows={8} + /> + + + + setObjectives(event.target.value)} + /> + + + + ); +} diff --git a/src/app/CreateIncident/page.jsx b/src/app/CreateIncident/page.jsx index 17870e0..f2865ab 100644 --- a/src/app/CreateIncident/page.jsx +++ b/src/app/CreateIncident/page.jsx @@ -8,6 +8,8 @@ import FormControl from '@mui/material/FormControl'; import Stack from '@mui/material/Stack'; import Box from '@mui/material/Box'; import axios from 'axios'; +import { Typography } from '@mui/material'; +import FileUpload from '../../Components/FileUpload'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; @@ -23,30 +25,10 @@ const agencies = [ label: '', }, { - id: '1', - value: 'SAR', + id: '8afd6fa8-17ce-4591-8d57-59b29933dd61', + value: 'Search and Rescue', label: 'Search and Rescue', }, - { - id: '2', - value: 'Fire', - label: 'Fire', - }, - { - id: '3', - value: 'Police', - label: 'Police', - }, - { - id: '4', - value: 'EMO', - label: 'EMO', - }, - { - id: '5', - value: 'Other', - label: 'Other', - }, ]; const incidentType = [ @@ -135,7 +117,13 @@ const province = [ }, ]; -export default function CreateIncident({ appRouter, changePathFunction }) { +export default function CreateIncident({ + appRouter, + changePathFunction, + user, + selectedIncident, + addIncidentInfo, +}) { const [currentUser, setCurrentUser] = useState({}); const [incidentName, setIncidentName] = useState(''); const [incidentCommander, setIncidentCommander] = useState(''); @@ -150,17 +138,20 @@ export default function CreateIncident({ appRouter, changePathFunction }) { const [summary, setSummary] = useState(''); const [objectives, setObjectives] = useState(''); const [activeIncidentId, setActiveIncidentId] = useState(''); + const [payload, setPayload] = useState(null); + const [startDate, setStartDate] = useState(dayjs()); + const [startTime, setStartTime] = useState(dayjs()); + const [endDate, setEndDate] = useState(null); + const [endTime, setEndTime] = useState(null); + const [opInfo, setOpInfo] = useState(null); useEffect(() => { getCookie(); - if (/incident\/activeIncident\/./.test(appRouter.pathname)) { + if (/activeIncident\/./.test(appRouter.pathname)) { loadIncident(); } }, []); - const [startDate, setStartDate] = useState(dayjs()); - const [startTime, setStartTime] = useState(dayjs()); - function getCookie() { let name = 'user='; let decodedCookie = decodeURIComponent(document.cookie); @@ -190,12 +181,29 @@ export default function CreateIncident({ appRouter, changePathFunction }) { setObjectives(''); } + async function updateEndDate() { + try { + const response = await axios.post( + `${import.meta.env.VITE_API_ENDPOINT}/update-incidentEndDate`, + { + incidentId: selectedIncident.incident.incidentId, + endDate: endDate, + } + ); + if (response.status == 200) { + console.log('End date updated'); + } + } catch (error) { + console.log('Error updating end date: ', error.message); + } + } + async function handleSubmit(e) { e.preventDefault(); const payload = { incidentName: incidentName, - incidentCommander: incidentCommander, + incidentCommander: user.responderId, agency: agency, incidentType: incidentTypeChoice, operationPeriod: op, @@ -203,107 +211,63 @@ export default function CreateIncident({ appRouter, changePathFunction }) { city: city, postal: postal, province: provinceChoice, - date: startDate.toISOString(), + startDate: dayjs(startDate), summary: summary, objectives: objectives, }; + setPayload(payload); - // try { - // const response = await axios.post('http://localhost:5185/newIncident', payload); + try { + const response = await axios.post( + `${import.meta.env.VITE_API_ENDPOINT}/create-incident`, + payload + ); - // if (response.status == 200) { - // setMessage(`Registration successful: ${response.data.message}`); - // } - // } catch (error) { - // setMessage(`Error: ${error.message}`); - // console.log(`Error: ${error.message}`); - // } + if (response.status == 200) { + console.log('RESPONSE', response.data); + console.log(`Incident creation successful: ${response}`); + } + } catch (error) { + console.log(`Error: ${error.message}`); + } } - const tempIncident1 = { - incidentName: 'Incident 101-03-15-2025', - incidentCommander: 'Alfred Parks', - agency: 'SAR', - incidentType: 'Missing Person', - operationPeriod: 1, - address: '123 Main Street', - city: 'Kentville', - postal: 'B4P1P2', - province: 'NS', - date: '2025-03-15T23:54:26.305Z', - summary: 'Elderly person escaped nursing home', - objectives: 'Determine search locations', - }; - const tempIncident2 = { - incidentName: 'Incident 102-03-16-2025', - incidentCommander: 'Const. Marshal', - agency: 'Police', - incidentType: 'Other', - operationPeriod: 1, - address: '123 Main Street', - city: 'Kentville', - postal: 'B4P1P2', - province: 'NS', - date: '2025-03-15T23:54:26.305Z', - summary: 'Elderly person escaped nursing home', - objectives: 'Determine search locations', - }; - const tempIncident3 = { - incidentName: 'Incident 103-03-17-2025', - incidentCommander: 'Bob Straford', - agency: 'EMO', - incidentType: 'Lost at Sea', - operationPeriod: 1, - address: '123 Pleasant St', - city: 'Wolfville', - postal: 'B4P2B9', - province: 'NS', - date: '2025-03-15T23:54:26.305Z', - summary: 'Two car head on colission', - objectives: 'Prepapre to begin CPR immidiately upon arrival', - }; - - const tempIncidents = { - 1: tempIncident1, - 2: tempIncident2, - 3: tempIncident3, - }; + async function handleUpdate(e) { + e.preventDefault(); + console.log('UPDATE'); + } + console.log('ADD: ', addIncidentInfo); function loadIncident() { - console.log(`From: ${appRouter.pathname.split('/')[3]}`); - const incidentId = appRouter.pathname.split('/')[3]; - setActiveIncidentId(incidentId); - if (tempIncidents[incidentId]) { - const data = tempIncidents[incidentId]; - setIncidentName(data.incidentName); - setIncidentCommander(data.incidentCommander); - setAddress(data.address); - setAgency(data.agency); - setIncidentTypeChoice(data.incidentType); - setOp(data.operationPeriod); - setCity(data.city); - setPostal(data.postal); - setProvinceChoice(data.province); - setSummary(data.summary); - setObjectives(data.objectives); - } - - // try { - // const response = await axios.get(`http://localhost:5185/activeIncident/${variable}`, payload); - - // if (response.status == 200) { - // setMessage(`Registration successful: ${response.data.message}`); - // } - // } catch (error) { - // setMessage(`Error: ${error.message}`); - // console.log(`Error: ${error.message}`); - // } + const incidentId = appRouter.pathname.split('/')[2]; + setActiveIncidentId(selectedIncident.incident.incidentId); + setIncidentName(selectedIncident.incident.incidentName); + setAddress(selectedIncident.incident.address); + setCity(selectedIncident.incident.city); + setAgency(addIncidentInfo.agencyName); + setIncidentTypeChoice(selectedIncident.incident.incidentType); + setObjectives(selectedIncident.incident.objectives); + setPostal(selectedIncident.incident.postal); + setProvinceChoice(selectedIncident.incident.province); + setSummary(selectedIncident.incident.summary); + setOp(selectedIncident.operationalPeriods[0].operationalPeriod); + setOpInfo(selectedIncident); + console.log('INFO', selectedIncident); } return ( <> - + + + Incident Information + + setStartTime(newValue)} /> + + + setEndDate(newValue)} + /> + + + + setEndTime(newValue)} + /> + @@ -464,28 +454,28 @@ export default function CreateIncident({ appRouter, changePathFunction }) { setCity(event.target.value)} + onChange={(event) => setPostal(event.target.value)} /> setPostal(event.target.value)} + onChange={(event) => setCity(event.target.value)} /> @@ -501,6 +491,7 @@ export default function CreateIncident({ appRouter, changePathFunction }) { multiline onChange={(event) => setSummary(event.target.value)} maxRows={1000} + minRows={8} /> @@ -515,17 +506,16 @@ export default function CreateIncident({ appRouter, changePathFunction }) { label="Objectives" multiline maxRows={1000} + minRows={8} onChange={(event) => setObjectives(event.target.value)} /> - {activeIncidentId && ( - - )} - + diff --git a/src/app/LandingPage/page.jsx b/src/app/LandingPage/page.jsx index 81e4dbd..6686dee 100644 --- a/src/app/LandingPage/page.jsx +++ b/src/app/LandingPage/page.jsx @@ -1,3 +1,118 @@ +import Card from '@mui/material/Card'; +import CardMedia from '@mui/material/CardMedia'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import Grid from '@mui/material/Grid2'; +import { Button, Divider } from '@mui/material'; +import GroupsIcon from '@mui/icons-material/Groups'; + export default function LandingPage() { - return

Landing Page

; + return ( + + + + An Efficient Incident Management System + + + Built for First Responders, by First Responders - A Smarter + Way to Manage Incidents. + + + + + + + + + + + Trusted by First Responder Across Canada + + + + {/* SAR Rescue Icon */} + + + + + + + + + + + + + + + + ); } diff --git a/src/app/Login/page.jsx b/src/app/Login/page.jsx index aeb4030..0db8659 100644 --- a/src/app/Login/page.jsx +++ b/src/app/Login/page.jsx @@ -16,6 +16,7 @@ import MuiCard from '@mui/material/Card'; import { styled } from '@mui/material/styles'; // import AppTheme from './theme/AppTheme'; // import ColorModeSelect from './theme/ColorModeSelect'; +import axios from 'axios'; const Card = styled(MuiCard)(({ theme }) => ({ display: 'flex', @@ -143,22 +144,13 @@ export default function Login() { async function handleSubmit(e) { e.preventDefault(); - context.setUser({ - fName: 'Jordan', - lName: 'Kelsey', - role: 'admin', - }); - setCookie('user', context.user, 10); - navigate('/Dashboard'); - - const data = new FormData(e.currentTarget); - - console.log({ - name: data.get('name'), - lastName: data.get('lastName'), - email: data.get('email'), - password: data.get('password'), - }); + // context.setUser({ + // fName: 'Jordan', + // lName: 'Kelsey', + // role: 'admin', + // }); + // setCookie('user', context.user, 10); + // navigate('/Dashboard'); const payload = { email: email, @@ -166,28 +158,34 @@ export default function Login() { }; try { - const response = await fetch( - `${import.meta.env.API_ENDPOINT}/login`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(payload), - } + const response = await axios.post( + `${import.meta.env.VITE_API_ENDPOINT}/login`, + payload ); - const data = await response.json(); - if (response.ok) { + if (response.status == 200) { // If registration is successful - context.setUser({ - fName: 'Jordan', - lName: 'Kelsey', - role: 'admin', - }); - setCookie('user', context.user, 10); - console.log('Login Successful'); - navigate('/Dashboard'); + try { + const responderResponse = await axios.post( + `${import.meta.env.VITE_API_ENDPOINT}/get-responderId`, + { email: email } + ); + if (responderResponse.status == 200) { + context.setUser({ + fName: 'Jordan', + lName: 'Kelsey', + email: email, + role: 'admin', + responderId: responderResponse.data, + accessToken: response.data.accessToken, + }); + setCookie('user', context.user, 10); + console.log('Login Successful'); + navigate('/Dashboard'); + } + } catch (error) { + console.log('Error fetching UID: ', error.message); + } } else { // If registration fails setMessage('Email or password is incorrect.'); @@ -201,7 +199,7 @@ export default function Login() { <> - +
- - - or - - - - - Already have an account?{' '} - - Sign in - - -
diff --git a/src/app/PastIncident/page.jsx b/src/app/PastIncident/page.jsx index 63e2849..bf5ba27 100644 --- a/src/app/PastIncident/page.jsx +++ b/src/app/PastIncident/page.jsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Link } from '@mui/material'; import Grid from '@mui/material/Grid2'; import { styled } from '@mui/material/styles'; @@ -8,6 +8,7 @@ import Typography from '@mui/material/Typography'; import CardActionArea from '@mui/material/CardActionArea'; import CreateIncident from '../CreateIncident/page'; import { useActivePage } from '@toolpad/core/useActivePage'; +import axios from 'axios'; const tempIncidents = [ { @@ -26,22 +27,88 @@ const tempIncidents = [ id: 3, incidentName: 'Incident 108-01-17-2025', agency: 'Search and Rescue', - incidentType: 'MIssing Hiker', + incidentType: 'Missing Hiker', }, ]; export default function PastIncident({ changePath }) { const [selectedCard, setSelectedCard] = useState(0); + const [pastIncidents, setPastIncidents] = useState([]); + + async function getAllPastIncidents() { + try { + const response = await axios.get( + `${import.meta.env.VITE_API_ENDPOINT}/get-allPastIncidents` + ); + console.log(response.data); + if (response.status == 200) { + setPastIncidents(response.data); + console.log('Retrieved all past incidents'); + } + } catch (error) { + console.log('Error getting incidents: ', error.message); + } + } + + useEffect(() => { + getAllPastIncidents(); + }, []); return ( <> - + + {pastIncidents.map((incident) => ( + + + + changePath( + `/incident/pastIncident/${incident.incidentId}` + ) + } + data-active={ + selectedCard === incident.incidentId + ? '' + : undefined + } + sx={{ + height: 200, + '&[data-active]': { + backgroundColor: 'action.selected', + '&:hover': { + backgroundColor: + 'action.selectedHover', + }, + }, + }} + > + + + {incident.incidentName} + + + {incident.incidentType} + + + Agency: {incident.agency} + + + + + + ))} {tempIncidents.map((incident) => ( - + changePath( - `/incident/activeIncident/${incident.id}` + `/incident/pastIncident/${incident.id}` ) } data-active={ diff --git a/src/app/Regsiter/page.jsx b/src/app/Regsiter/page.jsx index 8a94ec2..2e44d1f 100644 --- a/src/app/Regsiter/page.jsx +++ b/src/app/Regsiter/page.jsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Checkbox from '@mui/material/Checkbox'; @@ -123,20 +124,10 @@ const SignUpContainer = styled(Stack)(({ theme }) => ({ }, })); -/* - * name - * province - */ - const tempAgency = [ { - id: 1, - name: 'King Search and Rescue', - province: 'Nova Scotia', - }, - { - id: '2', - name: 'Greenwhich Fire', + id: '8afd6fa8-17ce-4591-8d57-59b29933dd61', + name: 'Kings County Search and Rescue', province: 'Nova Scotia', }, ]; @@ -161,6 +152,7 @@ export default function Register() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [message, setMessage] = useState(''); + const navigate = useNavigate(); const validateInputs = () => { const email = document.getElementById('email'); @@ -230,33 +222,53 @@ export default function Register() { const fromData = new FormData(e.currentTarget); const email = fromData.get('email'); const password = fromData.get('password'); - const name = fromData.get('name'); + const fName = fromData.get('fName'); + const lName = fromData.get('lName'); const phone = fromData.get('phone'); const payload = { - name: name, - brithdate: birthDate.toISOString(), + role: userRole, + firstName: fName, + lastName: lName, + brithdate: dayjs(birthDate), phone: phone, - agency: agency, province: provinceChoice, + agencyId: agency, + checkedIn: false, email: email, - password: password, - role: userRole, }; console.log(payload); + try { const response = await axios.post( - `${import.meta.env.API_ENDPOINT}/register`, + `${import.meta.env.VITE_API_ENDPOINT}/register`, { email, password } ); if (response.status == 200) { - setMessage(`Registration successful: ${response.data.message}`); - } else { - if (response.errors.DuplicateUserName) { - setMessage('Username already exists'); + setMessage( + `User registration successful: ${response.data.message}` + ); + //* Call asign-role endpoint to create the responder + try { + const response = await axios.post( + `${import.meta.env.VITE_API_ENDPOINT}/assign-role`, + payload + ); + if (response.status == 200) { + console.log( + `Responder registration successful: ${response.data.message}` + ); + } + } catch (error) { + setMessage(`Error: ${error.message}`); + console.log(`Error: ${error.message}`); + console.log(response); } } } catch (error) { + if (error.DuplicateUserName) { + setMessage('Username already exists'); + } setMessage(`Error: ${error.message}`); console.log(`Error: ${error.message}`); } @@ -275,7 +287,7 @@ export default function Register() { fontSize: 'clamp(2rem, 10vw, 2.15rem)', }} > - Sign up + Regsiter Personnel - Full name + First Name + + + + Last Name - Admin - User + Admin + User diff --git a/src/app/SARImages/Helicopter.jpg b/src/app/SARImages/Helicopter.jpg new file mode 100644 index 0000000..608e91c Binary files /dev/null and b/src/app/SARImages/Helicopter.jpg differ diff --git a/src/app/TempFile/CommsExample.jpg b/src/app/TempFile/CommsExample.jpg new file mode 100644 index 0000000..4e63427 Binary files /dev/null and b/src/app/TempFile/CommsExample.jpg differ diff --git a/src/app/TempFile/IncidentBriefing.jpg b/src/app/TempFile/IncidentBriefing.jpg new file mode 100644 index 0000000..fb11fc5 Binary files /dev/null and b/src/app/TempFile/IncidentBriefing.jpg differ diff --git a/src/assets/Icons/FF-Icon.jpg b/src/assets/Icons/FF-Icon.jpg new file mode 100644 index 0000000..ad8e687 Binary files /dev/null and b/src/assets/Icons/FF-Icon.jpg differ diff --git a/src/assets/Icons/Police-Icon.jpg b/src/assets/Icons/Police-Icon.jpg new file mode 100644 index 0000000..d0c5fa9 Binary files /dev/null and b/src/assets/Icons/Police-Icon.jpg differ diff --git a/src/assets/Icons/SAR-Icon-Yellow.png b/src/assets/Icons/SAR-Icon-Yellow.png new file mode 100644 index 0000000..212cf96 Binary files /dev/null and b/src/assets/Icons/SAR-Icon-Yellow.png differ diff --git a/src/assets/Icons/github.png b/src/assets/Icons/github.png new file mode 100644 index 0000000..3f4485c Binary files /dev/null and b/src/assets/Icons/github.png differ diff --git a/src/router.jsx b/src/router.jsx index af6f34f..9aa722b 100644 --- a/src/router.jsx +++ b/src/router.jsx @@ -12,7 +12,6 @@ export const router = createBrowserRouter([ children: [ { path: '/', element: }, { path: '/Login', element: }, - { path: '/Register', element: }, { element: ,