Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Frontend/src/_components/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ const styledVariant: Interpolation<{ $variant: Variant }> = ({ $variant }) =>
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 6%);
`,
ink: css`
background-color: #23272a;
color: #fff;
border: 1px solid #23272a;
background-color: #ffffffff;
color: #23272a;
border: 1px solid #eee;
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 20%);
`,
})[$variant]
Expand Down
182 changes: 122 additions & 60 deletions Frontend/src/_components/Header/Menu/compare/CompareMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { FC } from 'react'
import { useState } from 'react'
import { useState, useEffect } from 'react'
import styled from 'styled-components'
import { usePinContext } from '@/_components/ContextHooks/usePinContext'
import { LatLng } from 'leaflet'

const AccordionItem = styled.div<{ isExpanded?: boolean }>`
background-color: white;
Expand Down Expand Up @@ -81,7 +83,6 @@ const ToggleButton = styled.button<{ disabled?: boolean }>`
margin-top: 8px;
opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')};

&:hover {
background-color: #005f99;
}
Expand All @@ -103,7 +104,6 @@ const ResultItem = styled.div<{ selected?: boolean }>`
border-radius: 4px;
cursor: pointer;
background-color: ${({ selected }) => (selected ? '#c2e9ff' : 'transparent')};

&:hover {
background-color: #d0eaff;
}
Expand All @@ -118,16 +118,19 @@ interface CompareMenuProps {
closeLoc2: () => void
}

const australianStates = [
'New South Wales',
'Victoria',
'Queensland',
'South Australia',
'Western Australia',
'Tasmania',
'Northern Territory',
'Australian Capital Territory',
]
// Dummy coordinates for Australian states
const stateCoordinates: Record<string, LatLng> = {
'New South Wales': new LatLng(-33.8688, 151.2093),
Victoria: new LatLng(-37.8136, 144.9631),
Queensland: new LatLng(-27.4698, 153.0251),
'South Australia': new LatLng(-34.9285, 138.6007),
'Western Australia': new LatLng(-31.9505, 115.8605),
Tasmania: new LatLng(-42.8821, 147.3272),
'Northern Territory': new LatLng(-12.4634, 130.8456),
'Australian Capital Territory': new LatLng(-35.2809, 149.13),
}

const australianStates = Object.keys(stateCoordinates)

const CompareMenu: FC<CompareMenuProps> = ({
isLoc1Open,
Expand All @@ -137,6 +140,8 @@ const CompareMenu: FC<CompareMenuProps> = ({
closeLoc1,
closeLoc2,
}) => {
const { locationOnePin, locationTwoPin, addPin, removePin } = usePinContext()

const [searchInput1, setSearchInput1] = useState('')
const [searchInput2, setSearchInput2] = useState('')
const [results1, setResults1] = useState<string[]>([])
Expand All @@ -147,54 +152,75 @@ const CompareMenu: FC<CompareMenuProps> = ({
const [pendingSelection2, setPendingSelection2] = useState<string | null>(
null,
)
const [selected1, setSelected1] = useState<string | null>(null)
const [selected2, setSelected2] = useState<string | null>(null)
const [locked1, setLocked1] = useState(false)
const [locked2, setLocked2] = useState(false)

const handleSearch1 = (value: string) => {
setSearchInput1(value)
setResults1(
australianStates.filter((state) =>
state.toLowerCase().includes(value.toLowerCase()),
),
)
setPendingSelection1(null)
}
useEffect(() => {
if (locationOnePin) {
setSearchInput1(locationOnePin.locationName)
setLocked1(true)
setPendingSelection1(null)
setResults1([])
} else {
setSearchInput1('')
setLocked1(false)
}
}, [locationOnePin])

const handleSearch2 = (value: string) => {
setSearchInput2(value)
setResults2(
useEffect(() => {
if (locationTwoPin) {
setSearchInput2(locationTwoPin.locationName)
setLocked2(true)
setPendingSelection2(null)
setResults2([])
} else {
setSearchInput2('')
setLocked2(false)
}
}, [locationTwoPin])

const handleSearch = (
value: string,
setInput: React.Dispatch<React.SetStateAction<string>>,
setResults: React.Dispatch<React.SetStateAction<string[]>>,
locked: boolean,
setPending: React.Dispatch<React.SetStateAction<string | null>>,
) => {
if (locked) return
setInput(value)
setResults(
australianStates.filter((state) =>
state.toLowerCase().includes(value.toLowerCase()),
),
)
setPendingSelection2(null)
setPending(null)
}

const handleClickResult1 = (loc: string) => setPendingSelection1(loc)
const handleClickResult2 = (loc: string) => setPendingSelection2(loc)

const toggleSelection1 = () => {
if (pendingSelection1) {
setSelected1(pendingSelection1)
setSearchInput1(pendingSelection1)
setResults1([])
setPendingSelection1(null)
} else {
setSelected1(null)
setSearchInput1('')
}
const handleClickResult = (
loc: string,
setPending: React.Dispatch<React.SetStateAction<string | null>>,
) => {
setPending(loc)
}

const toggleSelection2 = () => {
if (pendingSelection2) {
setSelected2(pendingSelection2)
setSearchInput2(pendingSelection2)
setResults2([])
setPendingSelection2(null)
} else {
setSelected2(null)
setSearchInput2('')
const toggleSelection = async (
pendingSelection: string | null,
locked: boolean,
setLocked: React.Dispatch<React.SetStateAction<boolean>>,
locationPin: typeof locationOnePin | typeof locationTwoPin,
setInput: React.Dispatch<React.SetStateAction<string>>,
setResults: React.Dispatch<React.SetStateAction<string[]>>,
) => {
if (!locked && pendingSelection) {
const coords = stateCoordinates[pendingSelection]
if (!coords) return
await addPin(coords)
} else if (locked && locationPin) {
removePin(locationPin.id)
setInput('')
}
setLocked(!locked)
setResults([])
}

return (
Expand All @@ -214,14 +240,23 @@ const CompareMenu: FC<CompareMenuProps> = ({
<LocSearchInput
placeholder="Search Location 1"
value={searchInput1}
onChange={(e) => handleSearch1(e.target.value)}
onChange={(e) =>
handleSearch(
e.target.value,
setSearchInput1,
setResults1,
locked1,
setPendingSelection1,
)
}
disabled={locked1}
/>
{results1.length > 0 && (
<SearchResultsColumn>
{results1.map((item, idx) => (
<ResultItem
key={idx}
onClick={() => handleClickResult1(item)}
onClick={() => handleClickResult(item, setPendingSelection1)}
selected={pendingSelection1 === item}
>
{item}
Expand All @@ -230,10 +265,19 @@ const CompareMenu: FC<CompareMenuProps> = ({
</SearchResultsColumn>
)}
<ToggleButton
disabled={!pendingSelection1 && !selected1}
onClick={toggleSelection1}
disabled={!pendingSelection1 && !locked1}
onClick={() =>
toggleSelection(
pendingSelection1,
locked1,
setLocked1,
locationOnePin,
setSearchInput1,
setResults1,
)
}
>
{selected1 ? 'Deselect' : 'Select'}
{locked1 ? 'Deselect' : 'Select'}
</ToggleButton>
</AccordionContent>
</AccordionItem>
Expand All @@ -253,14 +297,23 @@ const CompareMenu: FC<CompareMenuProps> = ({
<LocSearchInput
placeholder="Search Location 2"
value={searchInput2}
onChange={(e) => handleSearch2(e.target.value)}
onChange={(e) =>
handleSearch(
e.target.value,
setSearchInput2,
setResults2,
locked2,
setPendingSelection2,
)
}
disabled={locked2}
/>
{results2.length > 0 && (
<SearchResultsColumn>
{results2.map((item, idx) => (
<ResultItem
key={idx}
onClick={() => handleClickResult2(item)}
onClick={() => handleClickResult(item, setPendingSelection2)}
selected={pendingSelection2 === item}
>
{item}
Expand All @@ -269,10 +322,19 @@ const CompareMenu: FC<CompareMenuProps> = ({
</SearchResultsColumn>
)}
<ToggleButton
disabled={!pendingSelection2 && !selected2}
onClick={toggleSelection2}
disabled={!pendingSelection2 && !locked2}
onClick={() =>
toggleSelection(
pendingSelection2,
locked2,
setLocked2,
locationTwoPin,
setSearchInput2,
setResults2,
)
}
>
{selected2 ? 'Deselect' : 'Select'}
{locked2 ? 'Deselect' : 'Select'}
</ToggleButton>
</AccordionContent>
</AccordionItem>
Expand Down
67 changes: 67 additions & 0 deletions Frontend/src/_components/Sidebar/ConfirmModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { FC } from 'react'
import { Modal } from './Modal'
import styled from 'styled-components'

interface ConfirmModalProps {
isOpen: boolean
message: string
onConfirm: () => void
onCancel: () => void
confirmText?: string
cancelText?: string
}

const ModalButtons = styled.div`
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 16px;
`

const Button = styled.button<{ $variant?: 'primary' | 'secondary' }>`
padding: 6px 16px;
border-radius: 6px;
border: 1px solid
${({ $variant }) => ($variant === 'primary' ? '#a0d1ff' : '#d1d5db')};
background: ${({ $variant }) =>
$variant === 'primary' ? '#b3e0ff' : 'white'};
color: ${({ $variant }) => ($variant === 'primary' ? '#1d3c66' : '#333')};
cursor: pointer;
font-family: 'Instrument Sans', sans-serif;
font-size: 14px;

&:hover {
background: ${({ $variant }) =>
$variant === 'primary' ? '#99ccff' : '#f3f4f6'};
}
`

const Message = styled.p`
margin: 0;
color: #1d3c66;
`

export const ConfirmModal: FC<ConfirmModalProps> = ({
isOpen,
message,
onConfirm,
onCancel,
confirmText = 'Yes',
cancelText = 'Cancel',
}) => {
return (
<Modal isOpen={isOpen} onClose={onCancel}>
<Message>{message}</Message>
<ModalButtons>
<Button $variant="primary" onClick={onConfirm}>
{confirmText}
</Button>
<Button $variant="secondary" onClick={onCancel}>
{cancelText}
</Button>
</ModalButtons>
</Modal>
)
}

export default ConfirmModal
Loading