Skip to content

Commit 7dca9d2

Browse files
committed
fix(islands): load islands that do not belong to any regency
1 parent 88aa538 commit 7dca9d2

File tree

4 files changed

+76
-13
lines changed

4 files changed

+76
-13
lines changed

lib/data.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ export async function getData<A extends Area, P extends string | Query<A>>(
7272
)
7373
const parent = parentArea[area]
7474

75-
if (query?.parentCode && parent) {
75+
// Allow empty string on `parentCode` query
76+
if (query?.parentCode != null && parent) {
7677
url.searchParams.append(`${parent}Code`, query.parentCode)
7778
}
7879

modules/MapDashboard/IslandMarkers.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,8 @@ const MapMarker = dynamic(() => import('@/components/MapMarker'), {
1717
})
1818

1919
export default function IslandMarkers() {
20-
const { selectedArea, loading } = useMapDashboard()
21-
const regencyCode = selectedArea.regency?.code
22-
const { data: islands = [] } = useIslands(regencyCode)
20+
const { loading } = useMapDashboard()
21+
const { data: islands = [] } = useIslands()
2322

2423
return (
2524
<MarkerClusterGroup

modules/MapDashboard/IslandsInfo.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,11 @@ import { useIslands } from './hooks/useIslands'
66

77
export default function IslandsInfo() {
88
const { selectedArea } = useMapDashboard()
9-
const { isLoading, data: islands = [] } = useIslands(
10-
selectedArea.regency?.code,
11-
)
9+
const { isLoading, data: islands = [] } = useIslands()
1210

1311
return (
1412
<div className="w-full p-2 border rounded flex justify-center items-center text-center">
15-
{selectedArea.regency ? (
13+
{selectedArea.province || selectedArea.regency ? (
1614
<div className="flex flex-col w-full justify-center items-center">
1715
{isLoading ? (
1816
<div className="flex gap-2 justify-center items-center">
@@ -35,7 +33,7 @@ export default function IslandsInfo() {
3533
</div>
3634
) : (
3735
<span className="text-sm text-gray-500">
38-
Select a regency to see islands
36+
Select a province or regency to see islands
3937
</span>
4038
)}
4139
</div>

modules/MapDashboard/hooks/useIslands.ts

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { useQuery } from '@tanstack/react-query'
2+
import { useMemo } from 'react'
23
import { config } from '@/lib/config'
34
import { Area, type Island } from '@/lib/const'
45
import { getData } from '@/lib/data'
6+
import { useMapDashboard } from './useDashboard'
57

68
const MAX_PAGE_SIZE = config.dataSource.area.pagination.maxPageSize
79

@@ -29,10 +31,73 @@ async function fetchIslandsRecursively(
2931
return []
3032
}
3133

32-
export function useIslands(regencyCode?: string) {
33-
return useQuery({
34-
queryKey: ['islands', { parentCode: regencyCode }],
35-
queryFn: () => fetchIslandsRecursively(regencyCode as string),
34+
export function useIslands() {
35+
const { selectedArea } = useMapDashboard()
36+
const provinceCode = selectedArea.province?.code
37+
const regencyCode = selectedArea.regency?.code
38+
39+
// Load all islands in a province that does not belong to any regency
40+
// Fetch the full list of islands that don't belong to any regency once and
41+
// cache it. We only enable this when a province is selected to avoid
42+
// fetching unnecessarily on initial load. Different provinces will reuse
43+
// the same cached list and we filter it locally instead of refetching.
44+
const allNoRegencyQuery = useQuery<Island[], Error>({
45+
queryKey: ['islands', 'without-regency'],
46+
queryFn: () => fetchIslandsRecursively(''),
47+
enabled: !!provinceCode,
48+
// keep it fresh for a while so switching provinces won't trigger refetch
49+
staleTime: 1000 * 60 * 60, // 1 hour
50+
})
51+
52+
// derive province-level islands from the cached full list
53+
const provinceIslands = useMemo(() => {
54+
if (!provinceCode) return []
55+
const all: Island[] = (allNoRegencyQuery.data as Island[]) ?? []
56+
return all.filter((island: Island) =>
57+
island.code.startsWith(`${provinceCode}.00`),
58+
)
59+
}, [allNoRegencyQuery.data, provinceCode])
60+
61+
// Load islands for a specific regency
62+
const regencyQuery = useQuery<Island[], Error>({
63+
queryKey: ['islands', 'regency', regencyCode],
64+
queryFn: () =>
65+
regencyCode ? fetchIslandsRecursively(regencyCode) : Promise.resolve([]),
3666
enabled: !!regencyCode,
3767
})
68+
69+
// Compute aggregated islands from province + regency query results
70+
const islands = useMemo(() => {
71+
const prov = provinceIslands ?? []
72+
const reg = regencyQuery.data ?? []
73+
const map = new Map<string, Island>()
74+
75+
// add province islands first
76+
prov.forEach((i) => {
77+
map.set(i.code, i)
78+
})
79+
// overlay regency islands (will replace duplicates)
80+
reg.forEach((i) => {
81+
map.set(i.code, i)
82+
})
83+
84+
return Array.from(map.values())
85+
}, [provinceIslands, regencyQuery.data])
86+
87+
const isLoading = allNoRegencyQuery.isLoading || regencyQuery.isLoading
88+
const isFetching = allNoRegencyQuery.isFetching || regencyQuery.isFetching
89+
const aggregatedError = [regencyQuery.error, allNoRegencyQuery.error].filter(
90+
Boolean,
91+
)
92+
93+
return {
94+
data: islands,
95+
isLoading,
96+
isFetching,
97+
error: aggregatedError.length > 0 ? aggregatedError : null,
98+
refetch: () => {
99+
void allNoRegencyQuery.refetch()
100+
void regencyQuery.refetch()
101+
},
102+
}
38103
}

0 commit comments

Comments
 (0)