Skip to content
Open
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
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
VITE_MEILISEARCH_HOST=https://search.bettergov.ph
VITE_MEILISEARCH_PORT=443
VITE_MEILISEARCH_SEARCH_API_KEY= # Meilisearch Search API Key
VITE_MEILISEARCH_SEARCH_API_KEY= # Meilisearch Search API Key
VITE_MAPBOX_ACCESS_TOKEN=
473 changes: 232 additions & 241 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"jsdom": "^26.1.0",
"leaflet": "^1.9.4",
"lucide-react": "^0.513.0",
"mapbox-gl": "^3.16.0",
"meilisearch": "^0.50.0",
"nuqs": "^2.6.0",
"react": "19.1.0",
Expand Down
11 changes: 11 additions & 0 deletions src/enum/map.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export enum FloodYearEnum {
FIVE_YEAR = '5-Year Flood',
TWENTY_FIVE_YEAR = '25-Year Flood',
ONE_HUNDRED_YEAR = '100-Year Flood',
}

export enum HazardLevelEnum {
LOW = 1,
MEDIUM = 2,
HIGH = 3,
}
53 changes: 53 additions & 0 deletions src/pages/flood-control-projects/components/About.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { InfoIcon } from 'lucide-react';

const About = () => (
<div className='bg-white rounded-lg shadow-md p-4'>
<div>
<div className='flex items-center mb-4'>
<InfoIcon className='w-5 h-5 text-blue-600 mr-2' />
<h2 className='text-lg font-semibold text-gray-800'>About This Data</h2>
</div>
<p className='text-gray-800 mb-4'>
This map displays flood control infrastructure projects across the
Philippines. Click on a region to filter projects by that area. Zoom in
to see individual project locations. You can also use the filters to
narrow down projects by year, type of work, and search terms.
</p>
<p className='text-gray-800 mb-2'>
Additionally, the map incorporates Project NOAH flood hazard data:
</p>
<ul className='list-disc list-inside text-gray-800 mb-4'>
<li>
<span className='font-medium'>5-Year Flood</span>
</li>
<li>
<span className='font-medium'>25-Year Flood</span>
</li>
<li>
<span className='font-medium'>100-Year Flood</span>
</li>
</ul>
<p className='text-gray-800'>
These layers visualize flood-prone areas based on historical and modeled
data, helping to understand potential flood risks in different regions.
Visit{' '}
<a
href='https://noah.up.edu.ph/'
target='_blank'
rel='noreferrer'
className='text-blue-600 hover:underline'
>
Project NOAH
</a>{' '}
to view high-resolution hazard maps on flooding, storm surge, and
landslides.
</p>
</div>
<p className='text-sm text-gray- mt-4'>
Source: Department of Public Works and Highways (DPWH) Flood Control
Information System, NOAH Studio
</p>
</div>
);

export default About;
52 changes: 52 additions & 0 deletions src/pages/flood-control-projects/components/MapControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { MapIcon, SatelliteIcon, ZoomInIcon, ZoomOutIcon } from 'lucide-react';
import Button from '../../../components/ui/Button';
import { IMapStyle } from '../types';

interface IMapControlsProps {
mapStyle: IMapStyle;
handleZoomIn: () => void;
handleZoomOut: () => void;
handleSwitchMapStyle: () => void;
}

const MapControls = ({
mapStyle,
handleZoomIn,
handleZoomOut,
handleSwitchMapStyle,
}: IMapControlsProps) => (
<div className='absolute top-4 right-4 z-10 flex flex-col gap-2'>
<Button
variant='primary'
size='sm'
onClick={handleZoomIn}
aria-label='Zoom in'
>
<ZoomInIcon className='h-4 w-4' />
</Button>
<Button
variant='primary'
size='sm'
onClick={handleZoomOut}
aria-label='Zoom out'
>
<ZoomOutIcon className='h-4 w-4' />
</Button>
<Button
variant='primary'
size='sm'
aria-label={
mapStyle.style === 'satellite' ? 'Standard View' : 'Satellite View'
}
onClick={handleSwitchMapStyle}
>
{mapStyle.style === 'satellite' ? (
<MapIcon className='h-4 w-4' />
) : (
<SatelliteIcon className='h-4 w-4' />
)}
</Button>
</div>
);

export default MapControls;
98 changes: 98 additions & 0 deletions src/pages/flood-control-projects/components/SimulationControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import SelectPicker from '@/components/ui/SelectPicker';
import { FloodYearEnum } from '@/enum/map.enum';
import Button from '../../../components/ui/Button';
import { HAZARD_LEVEL, MAX_SIMULATION_FLOOD_DEPTH } from '../constants';

interface ISimulationControlsProps {
simulation: {
simulating: boolean;
floodDepth: number;
};
selectedFloodYear: FloodYearEnum;
setSelectedFloodYear: (year: FloodYearEnum) => void;
toggleFloodSimulation: (simulate: boolean, reset?: boolean) => void;
handleStopSimulation: () => void;
}

const SimulationControls = ({
simulation,
selectedFloodYear,
setSelectedFloodYear,
toggleFloodSimulation,
handleStopSimulation,
}: ISimulationControlsProps) => (
<div className='absolute top-4 left-4 z-10 flex flex-col gap-2 rounded bg-white p-4 min-w-3xs'>
{simulation.simulating || simulation.floodDepth ? (
<>
<div>
<p>{selectedFloodYear}</p>
<p className='text-gray-600'>
Flood depth: ≈ {simulation.floodDepth.toFixed(1)} m
</p>
</div>
{simulation.simulating ? (
simulation.floodDepth >= MAX_SIMULATION_FLOOD_DEPTH ? (
<Button
variant='outline'
size='sm'
onClick={() => toggleFloodSimulation(true, true)}
>
Repeat
</Button>
) : (
<Button
variant='outline'
size='sm'
onClick={() => toggleFloodSimulation(false)}
>
Pause
</Button>
)
) : (
<Button
variant='outline'
size='sm'
onClick={() => toggleFloodSimulation(true)}
>
Resume
</Button>
)}
<Button variant='primary' size='sm' onClick={handleStopSimulation}>
Stop
</Button>
</>
) : (
<>
<SelectPicker
selectedValue={selectedFloodYear}
options={Object.values(FloodYearEnum).map(val => ({
label: val,
value: val,
}))}
onSelect={data => setSelectedFloodYear(data?.value as FloodYearEnum)}
clearable={false}
searchable={false}
/>
<div className='flex flex-col gap-2'>
{Object.values(HAZARD_LEVEL).map(({ color, label }, idx) => (
<div className='flex flex-row gap-2 items-center' key={idx}>
<div className={`h-4 w-4`} style={{ backgroundColor: color }} />
<p>{label}</p>
</div>
))}
</div>
<Button
className='mt-2'
variant='primary'
aria-label='Simulate flood'
size='sm'
onClick={() => toggleFloodSimulation(true, true)}
>
Run flood simulation
</Button>
</>
)}
</div>
);

export default SimulationControls;
Loading