Skip to content

Commit aa248ea

Browse files
authored
Merge pull request #299 from zigzagdev/chore/remove-duplicate-types
Chore: Centralize type definitions and lift search form logic
2 parents fa9f861 + f6f55d6 commit aa248ea

42 files changed

Lines changed: 363 additions & 537 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

client/src/app/features/breadcrumbs/BreadCrumbHooks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { useContext } from "react";
22
import BreadcrumbContext from "./BreadCrumbProvider.tsx";
33

4-
// label を登録する
4+
// register labeling
55
export const useSetBreadcrumbLabel = () => {
66
const context = useContext(BreadcrumbContext);
77
if (!context) throw new Error("useSetBreadcrumbLabel must be used within BreadcrumbProvider");
88
return context.setLabel;
99
};
1010

11-
// 全ラベルを取得する
11+
// fetching all labels
1212
export const useBreadcrumbLabels = () => {
1313
const context = useContext(BreadcrumbContext);
1414
if (!context) throw new Error("useBreadcrumbLabels must be used within BreadcrumbProvider");

client/src/app/features/heritages/mappers/to-world-heritage-detail-vm.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,15 @@ export function toWorldHeritageDetailVm(dto: ApiWorldHeritageDetailDto): WorldHe
2525
area_hectares: dto.area_hectares,
2626
buffer_zone_hectares: dto.buffer_zone_hectares,
2727
short_description: dto.short_description,
28-
short_description_jp: dto.short_description_jp,
2928
unesco_site_url: dto.unesco_site_url,
3029
state_party: dto.state_party,
3130
state_party_codes: dto.state_party_codes,
3231
state_parties_meta: dto.state_parties_meta,
3332
thumbnail: dto.thumbnail_url,
3433
} satisfies import("../../../../domain/types.ts").ApiWorldHeritageDto;
3534

35+
// Ensure the reshaped object satisfies ApiWorldHeritageDto at compile time
36+
3637
const base: WorldHeritageVm = toWorldHeritageVm(listDto);
3738

3839
const images: WorldHeritageImageVm[] = dto.images

client/src/app/features/map/hooks/region-count.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { RegionCount } from "../../../../domain/types.ts";
55
type State = {
66
data: RegionCount[];
77
isLoading: boolean;
8-
error: Error | null;
8+
error: unknown;
99
};
1010

1111
export function useRegionCount() {

client/src/app/features/search/components/SearchResultMapComponent.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ import "leaflet/dist/leaflet.css";
55
import { useEffect } from "react";
66
import type { LatLngBoundsExpression } from "leaflet";
77

8-
type Props = {
9-
items: WorldHeritageVm[];
10-
};
11-
128
const CATEGORY_COLOR: Record<string, string> = {
139
Cultural: "#f59e0b",
1410
Natural: "#22c55e",
@@ -37,7 +33,7 @@ function FitBounds({ items }: { items: WorldHeritageVm[] }) {
3733
return null;
3834
}
3935

40-
export function SearchResultMapComponent({ items }: Props) {
36+
export function SearchResultMapComponent({ items }: { items: WorldHeritageVm[] }) {
4137
const navigate = useNavigate();
4238

4339
const validItems = items.filter((item) => isValidCoordinate(item.latitude, item.longitude));

client/src/app/features/search/components/SearchResultsPage.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
11
import type { ReactNode } from "react";
2-
import type { WorldHeritageVm } from "../../../../domain/types";
2+
import type {
3+
WorldHeritageVm,
4+
Pagination as SearchResultsPagination,
5+
} from "../../../../domain/types";
36
import { HeritageCard } from "@features/top/cards/HeritageCard";
47
import { Pagination } from "@features/top/components/Pagination.tsx";
58
import { BreadcrumbList } from "@shared/components/BreadcrumbList.tsx";
69
import { SearchResultMapComponent } from "@features/search/components/SearchResultMapComponent.tsx";
710

8-
type SearchResultsPagination = {
9-
current_page: number;
10-
per_page: number;
11-
total: number;
12-
last_page: number;
13-
};
14-
1511
export type SearchResultsPageProps = {
1612
header?: ReactNode;
1713
items: WorldHeritageVm[];
Lines changed: 53 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,26 @@
1-
import { useCallback, useMemo, useState, useEffect } from "react";
1+
import { useCallback, useEffect, useMemo, useState } from "react";
22
import { useLocation, useNavigate } from "react-router-dom";
3-
import type { HeritageSearchParams } from "../../../../domain/types";
3+
import type {
4+
Category,
5+
HeritageSearchParams,
6+
IdSortOption,
7+
SearchValues,
8+
StudyRegion,
9+
} from "../../../../domain/types";
410
import {
511
parseHeritageSearchParams,
612
serializeHeritageSearchParams,
7-
} from "../mapper/search-heritages.params.ts";
8-
import { HeritageSubHeader } from "@features/top/components/HeritageSubHeader.tsx";
9-
import type { SearchValues } from "@features/top/components/HeritageSearchForm.tsx";
13+
} from "../mapper/search-heritages.params";
14+
import { DEFAULT_HERITAGE_SEARCH_PARAMS as SEARCH_PARAMS } from "../mapper/search-heritage.types";
15+
import { HeritageSubHeader } from "@features/top/components/HeritageSubHeader";
16+
17+
const DEFAULT_TOP_PER_PAGE = 30;
18+
const DEFAULT_ORDER: IdSortOption = "asc";
19+
20+
const toStudyRegionOrNull = (value: StudyRegion | ""): StudyRegion | null =>
21+
value === "" ? null : value;
22+
23+
const toCategoryOrNull = (value: Category | ""): Category | null => (value === "" ? null : value);
1024

1125
const toSearchYearOrNull = (value: string): number | null => {
1226
const trimmed = value.trim();
@@ -16,71 +30,40 @@ const toSearchYearOrNull = (value: string): number | null => {
1630
return Math.floor(parsed);
1731
};
1832

19-
/** Determine whether any valid search condition exists */
20-
const hasSearchParams = (params: HeritageSearchParams): boolean =>
21-
params.search_query !== null ||
22-
params.region !== null ||
23-
params.category !== null ||
24-
params.year_inscribed_from !== null ||
25-
params.year_inscribed_to !== null;
33+
const toSearchValues = (params: HeritageSearchParams): SearchValues => ({
34+
region: params.region ?? "",
35+
category: params.category ?? "",
36+
keyword: params.search_query ?? "",
37+
yearInscribedFrom: params.year_inscribed_from !== null ? String(params.year_inscribed_from) : "",
38+
yearInscribedTo: params.year_inscribed_to !== null ? String(params.year_inscribed_to) : "",
39+
});
2640

27-
type Props = {
28-
/** Notify the parent which API should be used: list or search */
29-
onApiModeChange?: (isSearch: boolean) => void;
30-
};
31-
32-
export function SearchHeritageFormContainer({ onApiModeChange }: Props) {
41+
export function SearchHeritageFormContainer() {
3342
const location = useLocation();
3443
const navigate = useNavigate();
3544

36-
const params: HeritageSearchParams = useMemo(
37-
() => parseHeritageSearchParams(location.search),
38-
[location.search],
39-
);
40-
41-
// If any search parameter exists, we consider it as "search mode". Otherwise, it's "list mode".
42-
const isSearchMode = useMemo(() => hasSearchParams(params), [params]);
43-
44-
useEffect(() => {
45-
onApiModeChange?.(isSearchMode);
46-
}, [isSearchMode, onApiModeChange]);
47-
48-
// If no search condition exists on the results page, redirect to the list page.
49-
useEffect(() => {
50-
if (!isSearchMode && location.pathname === "/heritages/results") {
51-
navigate({ pathname: "/heritages", search: location.search }, { replace: true });
52-
}
53-
}, [isSearchMode, location.pathname, location.search, navigate]);
54-
55-
const valueFromUrl: SearchValues = useMemo(
56-
() => ({
57-
region: params.region ?? "",
58-
category: params.category ?? "",
59-
keyword: params.search_query ?? "",
60-
yearInscribedFrom:
61-
params.year_inscribed_from !== null ? String(params.year_inscribed_from) : "",
62-
yearInscribedTo: params.year_inscribed_to !== null ? String(params.year_inscribed_to) : "",
63-
}),
64-
[
65-
params.region,
66-
params.category,
67-
params.search_query,
68-
params.year_inscribed_from,
69-
params.year_inscribed_to,
70-
],
71-
);
45+
const params: HeritageSearchParams = useMemo(() => {
46+
const parsed = parseHeritageSearchParams(location.search);
47+
return {
48+
...SEARCH_PARAMS,
49+
...parsed,
50+
current_page: parsed.current_page ?? 1,
51+
per_page: parsed.per_page ?? DEFAULT_TOP_PER_PAGE,
52+
order: parsed.order ?? DEFAULT_ORDER,
53+
};
54+
}, [location.search]);
7255

73-
const [draft, setDraft] = useState<SearchValues>(valueFromUrl);
56+
const [draft, setDraft] = useState<SearchValues>(() => toSearchValues(params));
7457

7558
useEffect(() => {
76-
setDraft(valueFromUrl);
77-
}, [valueFromUrl]);
59+
setDraft(toSearchValues(params));
60+
}, [params]);
7861

79-
const onChange = useCallback((next: SearchValues) => {
62+
const handleChange = useCallback((next: SearchValues) => {
8063
setDraft(next);
8164
}, []);
8265

83-
const onSubmit = useCallback(
66+
const handleSubmit = useCallback(
8467
(query: Partial<SearchValues>) => {
8568
const merged: SearchValues = {
8669
region: query.region ?? draft.region,
@@ -91,20 +74,24 @@ export function SearchHeritageFormContainer({ onApiModeChange }: Props) {
9174
};
9275

9376
const nextParams: HeritageSearchParams = {
94-
...params,
95-
region: (merged.region.trim() || null) as HeritageSearchParams["region"],
96-
category: (merged.category.trim() || null) as HeritageSearchParams["category"],
97-
search_query: merged.keyword.trim() || null,
77+
...SEARCH_PARAMS,
78+
search_query: merged.keyword.trim() === "" ? null : merged.keyword.trim(),
79+
region: toStudyRegionOrNull(merged.region),
80+
category: toCategoryOrNull(merged.category),
9881
year_inscribed_from: toSearchYearOrNull(merged.yearInscribedFrom),
9982
year_inscribed_to: toSearchYearOrNull(merged.yearInscribedTo),
10083
current_page: 1,
84+
per_page: params.per_page ?? DEFAULT_TOP_PER_PAGE,
85+
order: params.order ?? DEFAULT_ORDER,
86+
country: null,
10187
};
10288

10389
const search = serializeHeritageSearchParams(nextParams);
104-
navigate({ pathname: location.pathname, search }, { replace: false });
90+
navigate({ pathname: "/heritages/results", search }, { replace: false });
91+
setDraft(merged);
10592
},
106-
[navigate, location.pathname, params, draft],
93+
[draft, navigate, params.per_page, params.order],
10794
);
10895

109-
return <HeritageSubHeader value={draft} onChange={onChange} onSubmit={onSubmit} />;
96+
return <HeritageSubHeader value={draft} onChange={handleChange} onSubmit={handleSubmit} />;
11097
}

client/src/app/features/search/containers/search-heritage-result-container.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ import {
99

1010
import { useHeritageSearchQuery } from "../../search/hooks/use-search-heritage-query";
1111
import SearchResultsPage from "../components/SearchResultsPage";
12-
import type { WorldHeritageVm } from "../../../../domain/types";
12+
import type {
13+
ApiWorldHeritageDto,
14+
Pagination,
15+
SearchValues,
16+
WorldHeritageVm,
17+
} from "../../../../domain/types";
1318
import { toWorldHeritageListVm } from "@features/heritages/mappers/to-world-heritage-vm";
14-
import type { Pagination } from "../types";
1519
import { HeritageSubHeader } from "@features/top/components/HeritageSubHeader";
16-
import type { SearchValues } from "@features/top/components/HeritageSearchForm";
1720
import { DEFAULT_HERITAGE_SEARCH_PARAMS as SEARCH_PARAMS } from "../mapper/search-heritage.types";
18-
import type { ApiSearchResponse } from "@features/search/apis/search-api";
1921

2022
const fmtRangeText = (pagination: Pagination, count: number): string => {
2123
if (count === 0) {
@@ -33,7 +35,7 @@ const isObject = (value: unknown): value is Record<string, unknown> =>
3335

3436
const isValidListResult = (
3537
value: unknown,
36-
): value is { items: ApiSearchResponse[]; pagination: Pagination } => {
38+
): value is { items: ApiWorldHeritageDto[]; pagination: Pagination } => {
3739
if (!isObject(value)) {
3840
return false;
3941
}

client/src/app/features/search/hooks/use-search-heritage-query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const toSearchParams = (params: HeritageSearchParams): SearchParams => ({
1919
});
2020

2121
type Options = {
22-
/** If false, the API call is skipped. Defaults to true. */
22+
/** If false, the API call is skipped. Default is true. */
2323
enabled?: boolean;
2424
};
2525

0 commit comments

Comments
 (0)