Skip to content

Commit 531bb78

Browse files
authored
Merge pull request #310 from zigzagdev/chore/add-criteria_endangered-search-form
Chore: Add criteria & endangered selector into search form
2 parents af4023d + 2c3e2e0 commit 531bb78

16 files changed

Lines changed: 309 additions & 15 deletions

client/src/app/features/search/apis/search-api.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {
22
ApiWorldHeritageDto,
3+
CriteriaCode,
34
ListResult,
45
Pagination,
56
StudyRegion,
@@ -16,6 +17,8 @@ export type SearchParams = {
1617
category?: string;
1718
yearInscribedFrom?: number;
1819
yearInscribedTo?: number;
20+
isEndangered?: boolean;
21+
criteria?: readonly CriteriaCode[];
1922
currentPage?: number;
2023
perPage?: number;
2124
};
@@ -72,6 +75,10 @@ export const createSearchApi = ({ apiBase, fetchImpl = fetch }: SearchApiDeps) =
7275
if (category) queryParams.set("category", category);
7376
if (yearInscribedFrom) queryParams.set("year_inscribed_from", yearInscribedFrom);
7477
if (yearInscribedTo) queryParams.set("year_inscribed_to", yearInscribedTo);
78+
if (params.isEndangered === true) queryParams.set("is_endangered", "true");
79+
if (params.criteria && params.criteria.length > 0) {
80+
queryParams.set("criteria", params.criteria.join(","));
81+
}
7582
if (params.currentPage != null) queryParams.set("current_page", String(params.currentPage));
7683
if (params.perPage != null) queryParams.set("per_page", String(params.perPage));
7784

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { HeritageCard } from "@features/top/cards/HeritageCard";
77
import { Pagination } from "@features/top/components/Pagination.tsx";
88
import { BreadcrumbList } from "@shared/components/BreadcrumbList.tsx";
99
import { SearchResultMapComponent } from "@features/search/components/SearchResultMapComponent.tsx";
10+
import { LocaleToggle } from "@shared/locale/LocaleToggle.tsx";
11+
import { useText } from "@shared/locale/ui-text.ts";
1012

1113
type Props = {
1214
header?: ReactNode;
@@ -31,6 +33,7 @@ export default function SearchResultsPage({
3133
onPageChange,
3234
onBackToAllSites,
3335
}: Props) {
36+
const text = useText();
3437
return (
3538
<main className="mx-auto max-w-7xl px-4 py-12">
3639
<div className="sticky top-0 z-20 -mx-4 border-b border-zinc-200 bg-white/95 px-4 py-3 backdrop-blur">
@@ -69,11 +72,12 @@ export default function SearchResultsPage({
6972
h-9 rounded-xl border border-zinc-200 bg-white px-3 text-xs font-semibold text-zinc-700
7073
shadow-sm transition hover:bg-zinc-50
7174
"
72-
aria-label="Back to all sites"
75+
aria-label={text.backToAllSites}
7376
>
74-
Back to all sites
77+
{text.backToAllSites}
7578
</button>
7679
) : null}
80+
<LocaleToggle />
7781
</div>
7882
</div>
7983
</div>
@@ -87,7 +91,7 @@ export default function SearchResultsPage({
8791

8892
{items.length === 0 ? (
8993
<div className="py-20 text-center">
90-
<p className="text-sm text-zinc-600">No sites found.</p>
94+
<p className="text-sm text-zinc-600">{text.noSitesFound}</p>
9195
</div>
9296
) : (
9397
<ul className="grid list-none grid-cols-1 gap-6 p-0 md:grid-cols-2 lg:grid-cols-3">

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ type SearchValues = {
3939
keyword: string;
4040
yearInscribedFrom: string;
4141
yearInscribedTo: string;
42+
isEndangered: boolean;
43+
criteria: string[];
4244
};
4345

4446
type HeritageSubHeaderProps = {
@@ -144,6 +146,8 @@ const makeParsedParams = (overrides: Partial<HeritageSearchParams> = {}): Herita
144146
category: null,
145147
year_inscribed_from: null,
146148
year_inscribed_to: null,
149+
is_endangered: null,
150+
criteria: [],
147151
current_page: 1,
148152
per_page: 30,
149153
order: "asc",
@@ -203,6 +207,8 @@ describe("TopPageContainer", () => {
203207
keyword: "Kyoto",
204208
yearInscribedFrom: "",
205209
yearInscribedTo: "",
210+
isEndangered: false,
211+
criteria: [],
206212
});
207213
});
208214

@@ -252,6 +258,8 @@ describe("TopPageContainer", () => {
252258
category: "Cultural",
253259
year_inscribed_from: 1990,
254260
year_inscribed_to: null,
261+
is_endangered: null,
262+
criteria: [],
255263
current_page: 1,
256264
per_page: 30,
257265
order: "asc",

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

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ const toSearchValues = (params: HeritageSearchParams): SearchValues => ({
3636
keyword: params.search_query ?? "",
3737
yearInscribedFrom: params.year_inscribed_from !== null ? String(params.year_inscribed_from) : "",
3838
yearInscribedTo: params.year_inscribed_to !== null ? String(params.year_inscribed_to) : "",
39+
isEndangered: params.is_endangered === true,
40+
criteria: params.criteria,
3941
});
4042

4143
export function SearchHeritageFormContainer() {
@@ -71,6 +73,8 @@ export function SearchHeritageFormContainer() {
7173
keyword: query.keyword ?? draft.keyword,
7274
yearInscribedFrom: query.yearInscribedFrom ?? draft.yearInscribedFrom,
7375
yearInscribedTo: query.yearInscribedTo ?? draft.yearInscribedTo,
76+
isEndangered: query.isEndangered ?? draft.isEndangered,
77+
criteria: query.criteria ?? draft.criteria,
7478
};
7579

7680
const nextParams: HeritageSearchParams = {
@@ -80,17 +84,27 @@ export function SearchHeritageFormContainer() {
8084
category: toCategoryOrNull(merged.category),
8185
year_inscribed_from: toSearchYearOrNull(merged.yearInscribedFrom),
8286
year_inscribed_to: toSearchYearOrNull(merged.yearInscribedTo),
87+
is_endangered: merged.isEndangered ? true : null,
88+
criteria: merged.criteria,
8389
current_page: 1,
8490
per_page: params.per_page ?? DEFAULT_TOP_PER_PAGE,
8591
order: params.order ?? DEFAULT_ORDER,
8692
country: null,
8793
};
8894

89-
const search = serializeHeritageSearchParams(nextParams);
95+
const baseSearch = serializeHeritageSearchParams(nextParams);
96+
const currentLang = new URLSearchParams(location.search).get("lang");
97+
const finalParams = new URLSearchParams(
98+
baseSearch.startsWith("?") ? baseSearch.slice(1) : baseSearch,
99+
);
100+
if (currentLang === "ja") finalParams.set("lang", "ja");
101+
const finalQueryString = finalParams.toString();
102+
const search = finalQueryString ? `?${finalQueryString}` : "";
103+
90104
navigate({ pathname: "/heritages/results", search }, { replace: false });
91105
setDraft(merged);
92106
},
93-
[draft, navigate, params.per_page, params.order],
107+
[draft, navigate, params.per_page, params.order, location.search],
94108
);
95109

96110
return <HeritageSubHeader value={draft} onChange={handleChange} onSubmit={handleSubmit} />;

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

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,18 @@ const hasSearchParams = (params: HeritageSearchParams): boolean =>
5050
params.region !== null ||
5151
params.category !== null ||
5252
params.year_inscribed_from !== null ||
53-
params.year_inscribed_to !== null;
53+
params.year_inscribed_to !== null ||
54+
params.is_endangered === true ||
55+
params.criteria.length > 0;
5456

5557
const toDraftValues = (params: HeritageSearchParams): SearchValues => ({
5658
region: params.region ?? "",
5759
category: params.category ?? "",
5860
keyword: params.search_query ?? "",
5961
yearInscribedFrom: params.year_inscribed_from !== null ? String(params.year_inscribed_from) : "",
6062
yearInscribedTo: params.year_inscribed_to !== null ? String(params.year_inscribed_to) : "",
63+
isEndangered: params.is_endangered === true,
64+
criteria: params.criteria,
6165
});
6266

6367
const toSearchParams = (draft: SearchValues): HeritageSearchParams => ({
@@ -67,6 +71,8 @@ const toSearchParams = (draft: SearchValues): HeritageSearchParams => ({
6771
category: draft.category || null,
6872
year_inscribed_from: draft.yearInscribedFrom ? Number(draft.yearInscribedFrom) : null,
6973
year_inscribed_to: draft.yearInscribedTo ? Number(draft.yearInscribedTo) : null,
74+
is_endangered: draft.isEndangered ? true : null,
75+
criteria: draft.criteria,
7076
current_page: 1,
7177
});
7278

@@ -76,8 +82,19 @@ const mergeDraft = (currentDraft: SearchValues, partial: Partial<SearchValues>):
7682
keyword: partial.keyword ?? currentDraft.keyword,
7783
yearInscribedFrom: partial.yearInscribedFrom ?? currentDraft.yearInscribedFrom,
7884
yearInscribedTo: partial.yearInscribedTo ?? currentDraft.yearInscribedTo,
85+
isEndangered: partial.isEndangered ?? currentDraft.isEndangered,
86+
criteria: partial.criteria ?? currentDraft.criteria,
7987
});
8088

89+
// 現在の URL に lang=ja があれば、遷移先 search にも持たせる (en はデフォルトなので付けない)
90+
const preserveLang = (nextSearch: string, currentSearch: string): string => {
91+
const currentLang = new URLSearchParams(currentSearch).get("lang");
92+
if (currentLang !== "ja") return nextSearch;
93+
const params = new URLSearchParams(nextSearch.startsWith("?") ? nextSearch.slice(1) : nextSearch);
94+
params.set("lang", "ja");
95+
return `?${params.toString()}`;
96+
};
97+
8198
function useHeritageSearchDraft(params: HeritageSearchParams) {
8299
const [draft, setDraft] = React.useState<SearchValues>(() => toDraftValues(params));
83100

@@ -121,9 +138,10 @@ export function SearchHeritageResultsContainer(): React.ReactElement {
121138

122139
const handleClickItem = React.useCallback(
123140
(id: number) => {
124-
navigate(`/heritages/${id}`);
141+
const search = preserveLang("", location.search);
142+
navigate(`/heritages/${id}${search}`);
125143
},
126-
[navigate],
144+
[navigate, location.search],
127145
);
128146

129147
const handlePageChange = React.useCallback(
@@ -133,7 +151,7 @@ export function SearchHeritageResultsContainer(): React.ReactElement {
133151
current_page: page,
134152
};
135153

136-
const search = serializeHeritageSearchParams(nextParams);
154+
const search = preserveLang(serializeHeritageSearchParams(nextParams), location.search);
137155

138156
navigate(
139157
{
@@ -143,14 +161,14 @@ export function SearchHeritageResultsContainer(): React.ReactElement {
143161
{ replace: false },
144162
);
145163
},
146-
[navigate, location.pathname, params],
164+
[navigate, location.pathname, location.search, params],
147165
);
148166

149167
const handleSubmit = React.useCallback(
150168
(partial: Partial<SearchValues>) => {
151169
const nextDraft = mergeDraft(draft, partial);
152170
const nextParams = toSearchParams(nextDraft);
153-
const search = serializeHeritageSearchParams(nextParams);
171+
const search = preserveLang(serializeHeritageSearchParams(nextParams), location.search);
154172

155173
navigate(
156174
{
@@ -162,13 +180,14 @@ export function SearchHeritageResultsContainer(): React.ReactElement {
162180

163181
setDraft(nextDraft);
164182
},
165-
[draft, navigate, setDraft],
183+
[draft, navigate, setDraft, location.search],
166184
);
167185

168186
// Hooks must be called at the top level before any early returns.
169187
const handleBackToAllSites = React.useCallback(() => {
170-
navigate("/heritages", { replace: true });
171-
}, [navigate]);
188+
const search = preserveLang("", location.search);
189+
navigate(`/heritages${search}`, { replace: true });
190+
}, [navigate, location.search]);
172191

173192
const header = (
174193
<HeritageSubHeader value={draft} onChange={handleChange} onSubmit={handleSubmit} />

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ const makeParams = (overrides: Partial<HeritageSearchParams> = {}): HeritageSear
101101
category: null,
102102
year_inscribed_from: null,
103103
year_inscribed_to: null,
104+
is_endangered: null,
105+
criteria: [],
104106
current_page: 1,
105107
per_page: 30,
106108
order: null,

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ const toSearchParams = (params: HeritageSearchParams): SearchParams => ({
1414
category: params.category ?? undefined,
1515
yearInscribedFrom: params.year_inscribed_from ?? undefined,
1616
yearInscribedTo: params.year_inscribed_to ?? undefined,
17+
isEndangered: params.is_endangered === true ? true : undefined,
18+
criteria: params.criteria.length > 0 ? params.criteria : undefined,
1719
currentPage: params.current_page,
1820
perPage: params.per_page,
1921
});

0 commit comments

Comments
 (0)