Skip to content

Commit 45dcd3e

Browse files
authored
Merge pull request #44 from ut42tech/refactor/checkin-data-layer
refactor(checkin): propagate useApiResource + add background reload
2 parents 39d969b + 412b605 commit 45dcd3e

12 files changed

Lines changed: 1192 additions & 392 deletions

File tree

apps/admin/src/app/(authed)/mentors/page.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export default function MentorsPage() {
7878
</Reveal>
7979

8080
<Reveal index={1}>
81-
<CreateMentorForm onCreated={reload} />
81+
<CreateMentorForm onCreated={() => reload({ background: true })} />
8282
</Reveal>
8383

8484
{/* データ領域。Reveal を常時マウントして入場は一度だけ(再フェッチで再生されない)。
@@ -110,7 +110,12 @@ export default function MentorsPage() {
110110
保存すれば再取得で両者が同期するため許容するトレードオフ。 */}
111111
<div className="flex flex-col gap-3 md:hidden">
112112
{state.data.mentors.map((m) => (
113-
<MentorRow key={m.id} mentor={m} onUpdated={reload} variant="card" />
113+
<MentorRow
114+
key={m.id}
115+
mentor={m}
116+
onUpdated={() => reload({ background: true })}
117+
variant="card"
118+
/>
114119
))}
115120
</div>
116121

@@ -130,7 +135,12 @@ export default function MentorsPage() {
130135
</TableHeader>
131136
<TableBody>
132137
{state.data.mentors.map((m) => (
133-
<MentorRow key={m.id} mentor={m} onUpdated={reload} variant="row" />
138+
<MentorRow
139+
key={m.id}
140+
mentor={m}
141+
onUpdated={() => reload({ background: true })}
142+
variant="row"
143+
/>
134144
))}
135145
</TableBody>
136146
</Table>

apps/admin/src/app/(authed)/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export default function DashboardPage() {
9696
type="button"
9797
variant="outline"
9898
size="sm"
99-
onClick={() => sessions.reload()}
99+
onClick={() => sessions.reload({ background: true })}
100100
disabled={sessions.state.kind === 'loading'}
101101
>
102102
<IconRefresh data-icon="inline-start" />

apps/admin/src/app/(authed)/pre-registrations/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export default function PreRegistrationsPage() {
101101
</Reveal>
102102

103103
<Reveal index={1}>
104-
<CreatePreRegistrationForm onCreated={reload} />
104+
<CreatePreRegistrationForm onCreated={() => reload({ background: true })} />
105105
</Reveal>
106106

107107
{/* データ領域。Reveal を常時マウントして入場は一度だけ(再フェッチで再生されない)。
@@ -133,7 +133,7 @@ export default function PreRegistrationsPage() {
133133
<PreRegistrationRow
134134
key={p.preRegistrationId}
135135
item={p}
136-
onDeleted={reload}
136+
onDeleted={() => reload({ background: true })}
137137
variant="card"
138138
/>
139139
))}
@@ -157,7 +157,7 @@ export default function PreRegistrationsPage() {
157157
<PreRegistrationRow
158158
key={p.preRegistrationId}
159159
item={p}
160-
onDeleted={reload}
160+
onDeleted={() => reload({ background: true })}
161161
variant="row"
162162
/>
163163
))}

apps/checkin/src/app/first-time/page.tsx

Lines changed: 30 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,18 @@ import { Card, CardContent, CardDescription, CardHeader } from '@tecnova/ui/comp
2828
import { Input } from '@tecnova/ui/components/input';
2929
import { Skeleton } from '@tecnova/ui/components/skeleton';
3030
import { Table, TableBody, TableCell, TableRow } from '@tecnova/ui/components/table';
31-
import { apiFetch, readErrorMessage } from '@tecnova/ui/lib/api-client';
31+
import { useApiResource } from '@tecnova/ui/hooks/use-api-resource';
3232
import { motion, useReducedMotion } from 'motion/react';
3333
import Link from 'next/link';
34-
import { useCallback, useEffect, useMemo, useState } from 'react';
34+
import { useMemo, useState } from 'react';
3535
import { AnimatedNumber } from '@/components/animated-number';
3636
import { PageShell } from '@/components/page-shell';
3737
import { PanelHeader } from '@/components/panel-header';
3838
import { Reveal } from '@/components/reveal';
39+
import { CheckinErrorScreen } from '@/components/screen-error';
3940
import { formatJapaneseDate } from '@/lib/format';
4041
import { listItemTransition } from '@/lib/motion';
4142

42-
type State =
43-
| { kind: 'loading' }
44-
| { kind: 'list'; items: PreRegisteredListResponse['participants'] }
45-
| { kind: 'error'; message: string };
46-
4743
type PreRegisteredParticipant = PreRegisteredListResponse['participants'][number];
4844

4945
function ParticipantDetails({ item }: { item: PreRegisteredParticipant }) {
@@ -124,33 +120,14 @@ function RegistrationSteps() {
124120

125121
export default function FirstTimePage() {
126122
const prefersReduced = useReducedMotion();
127-
const [state, setState] = useState<State>({ kind: 'loading' });
123+
const { state, reload } = useApiResource<PreRegisteredListResponse>('/checkin/pre-registered');
128124
const [query, setQuery] = useState('');
129125

130-
const loadParticipants = useCallback(async () => {
131-
setState({ kind: 'loading' });
132-
try {
133-
const r = await apiFetch('/checkin/pre-registered');
134-
if (!r.ok) throw new Error(await readErrorMessage(r));
135-
const data = (await r.json()) as PreRegisteredListResponse;
136-
setState({ kind: 'list', items: data.participants });
137-
} catch (e) {
138-
setState({
139-
kind: 'error',
140-
message: e instanceof Error ? e.message : String(e),
141-
});
142-
}
143-
}, []);
144-
145-
useEffect(() => {
146-
void loadParticipants();
147-
}, [loadParticipants]);
148-
149126
const filteredItems = useMemo(() => {
150-
if (state.kind !== 'list') return [];
127+
if (state.kind !== 'ok') return [];
151128
const normalizedQuery = query.trim().toLowerCase();
152-
if (!normalizedQuery) return state.items;
153-
return state.items.filter((item) => {
129+
if (!normalizedQuery) return state.data.participants;
130+
return state.data.participants.filter((item) => {
154131
const values = [
155132
item.fullName,
156133
item.nickname,
@@ -162,7 +139,7 @@ export default function FirstTimePage() {
162139
});
163140
}, [query, state]);
164141

165-
if (state.kind === 'loading') {
142+
if (state.kind === 'idle' || state.kind === 'loading') {
166143
return (
167144
<PageShell>
168145
<div className="mx-auto flex w-full max-w-6xl flex-1 flex-col gap-4">
@@ -189,27 +166,21 @@ export default function FirstTimePage() {
189166

190167
if (state.kind === 'error') {
191168
return (
192-
<main className="flex flex-1 flex-col items-center justify-center gap-6 bg-rose-50 p-6 text-center">
193-
<Alert variant="destructive" className="max-w-xl text-left text-lg">
194-
<IconAlertCircle className="size-6" aria-hidden="true" />
195-
<AlertTitle>一覧を表示できません</AlertTitle>
196-
<AlertDescription>{state.message}</AlertDescription>
197-
</Alert>
198-
<div className="grid w-full max-w-xl grid-cols-1 gap-3 sm:grid-cols-2">
199-
<Button
200-
type="button"
201-
size="lg"
202-
onClick={() => void loadParticipants()}
203-
className="h-16 text-xl"
204-
>
205-
<IconRefresh className="size-6" data-icon="inline-start" />
206-
再読み込み
207-
</Button>
208-
<Button asChild variant="secondary" size="lg" className="h-16 text-xl">
209-
<Link href="/">ホームに戻る</Link>
210-
</Button>
211-
</div>
212-
</main>
169+
<CheckinErrorScreen
170+
title="一覧を表示できません"
171+
message={state.message}
172+
actions={
173+
<>
174+
<Button type="button" size="lg" onClick={() => reload()} className="h-16 text-xl">
175+
<IconRefresh className="size-6" data-icon="inline-start" />
176+
再読み込み
177+
</Button>
178+
<Button asChild variant="secondary" size="lg" className="h-16 text-xl">
179+
<Link href="/">ホームに戻る</Link>
180+
</Button>
181+
</>
182+
}
183+
/>
213184
);
214185
}
215186

@@ -233,14 +204,18 @@ export default function FirstTimePage() {
233204
style={{ height: 'auto' }}
234205
className="w-fit px-4 py-2 text-base"
235206
>
236-
未登録 <AnimatedNumber value={state.items.length} className="mx-1 tabular-nums" />
207+
未登録{' '}
208+
<AnimatedNumber
209+
value={state.data.participants.length}
210+
className="mx-1 tabular-nums"
211+
/>
237212
238213
</Badge>
239214
</div>
240215

241216
<RegistrationSteps />
242217

243-
{state.items.length === 0 ? (
218+
{state.data.participants.length === 0 ? (
244219
<Alert className="border-sky-200 bg-white">
245220
<IconAlertCircle className="size-5" aria-hidden="true" />
246221
<AlertTitle>今、登録できる人はいません</AlertTitle>
@@ -282,7 +257,7 @@ export default function FirstTimePage() {
282257
type="button"
283258
variant="outline"
284259
size="lg"
285-
onClick={() => void loadParticipants()}
260+
onClick={() => reload()}
286261
className="h-14 text-lg"
287262
>
288263
<IconRefresh className="size-6" data-icon="inline-start" />

0 commit comments

Comments
 (0)