Skip to content

Commit 39f7194

Browse files
yn1323claude
andauthored
シフト一時保存 (#322)
* docs: SubAgentのモデル指定をCLAUDE.mdに追加 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: シフト編集画面に一時保存ボタンと警告モーダルを追加 - saveShiftAssignmentsを単独呼び出しして通知せず割当を保存 - 初回保存時のみ「自動配置が止まる」警告モーダルを表示 - 確定後は警告なしで即保存 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: スタッフ15人と希望シフトを投入するseed mutationを追加 - seedRealisticStaffRequestsで学生/フリーター/主婦など現実的なパターンで投入 - 未提出1人/全休み提出1人など提出状況のバリエーションも含める Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: biome.jsonのスキーマバージョンを2.4.11に更新 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: ダッシュボード警告の文言を明確化 「編集中にも変更される」から「編集中にも希望シフトが追加される」に変更し 何が変わるかを具体的に示す Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent eb186b1 commit 39f7194

File tree

14 files changed

+249
-89
lines changed

14 files changed

+249
-89
lines changed

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ pnpm e2e e2e/path/to/file.spec.ts # 特定E2Eファイル
4646
- `lint`はwarningでも修正すること
4747
- 次に`/review`を実行して、レビュー結果を要修正、不要で分けて提示してください。
4848
- 上記完了後、SubAgentで`/simplify`を実行してリファクタを行うこと(Context消費したくない)
49+
- SubAgentのモデルはOpus4.6にしてください
4950

5051
### フロントエンド(UIあり)
5152

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/2.4.10/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.4.11/schema.json",
33
"vcs": {
44
"enabled": true,
55
"clientKind": "git",

convex-seeds/seeds/db.zip

6.26 KB
Binary file not shown.

convex/_generated/api.d.ts

Lines changed: 1 addition & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -105,91 +105,5 @@ export declare const internal: FilterApi<
105105
>;
106106

107107
export declare const components: {
108-
migrations: {
109-
lib: {
110-
cancel: FunctionReference<
111-
"mutation",
112-
"internal",
113-
{ name: string },
114-
{
115-
batchSize?: number;
116-
cursor?: string | null;
117-
error?: string;
118-
isDone: boolean;
119-
latestEnd?: number;
120-
latestStart: number;
121-
name: string;
122-
next?: Array<string>;
123-
processed: number;
124-
state: "inProgress" | "success" | "failed" | "canceled" | "unknown";
125-
}
126-
>;
127-
cancelAll: FunctionReference<
128-
"mutation",
129-
"internal",
130-
{ sinceTs?: number },
131-
Array<{
132-
batchSize?: number;
133-
cursor?: string | null;
134-
error?: string;
135-
isDone: boolean;
136-
latestEnd?: number;
137-
latestStart: number;
138-
name: string;
139-
next?: Array<string>;
140-
processed: number;
141-
state: "inProgress" | "success" | "failed" | "canceled" | "unknown";
142-
}>
143-
>;
144-
clearAll: FunctionReference<
145-
"mutation",
146-
"internal",
147-
{ before?: number },
148-
null
149-
>;
150-
getStatus: FunctionReference<
151-
"query",
152-
"internal",
153-
{ limit?: number; names?: Array<string> },
154-
Array<{
155-
batchSize?: number;
156-
cursor?: string | null;
157-
error?: string;
158-
isDone: boolean;
159-
latestEnd?: number;
160-
latestStart: number;
161-
name: string;
162-
next?: Array<string>;
163-
processed: number;
164-
state: "inProgress" | "success" | "failed" | "canceled" | "unknown";
165-
}>
166-
>;
167-
migrate: FunctionReference<
168-
"mutation",
169-
"internal",
170-
{
171-
batchSize?: number;
172-
cursor?: string | null;
173-
dryRun: boolean;
174-
fnHandle: string;
175-
name: string;
176-
next?: Array<{ fnHandle: string; name: string }>;
177-
oneBatchOnly?: boolean;
178-
reset?: boolean;
179-
},
180-
{
181-
batchSize?: number;
182-
cursor?: string | null;
183-
error?: string;
184-
isDone: boolean;
185-
latestEnd?: number;
186-
latestStart: number;
187-
name: string;
188-
next?: Array<string>;
189-
processed: number;
190-
state: "inProgress" | "success" | "failed" | "canceled" | "unknown";
191-
}
192-
>;
193-
};
194-
};
108+
migrations: import("@convex-dev/migrations/_generated/component.js").ComponentApi<"migrations">;
195109
};

convex/testing.ts

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,174 @@ export const seedPaginationTestData = internalMutation({
122122
},
123123
});
124124

125+
/**
126+
* 探索的テスト用:最新shop/recruitmentにスタッフ15人と現実的な希望シフトを投入
127+
*/
128+
export const seedRealisticStaffRequests = internalMutation({
129+
args: {},
130+
handler: async (ctx) => {
131+
const shop = await ctx.db.query("shops").order("desc").first();
132+
if (!shop) throw new Error("No shop found");
133+
134+
const recruitment = await ctx.db
135+
.query("recruitments")
136+
.withIndex("by_shopId", (q) => q.eq("shopId", shop._id))
137+
.order("desc")
138+
.first();
139+
if (!recruitment) throw new Error("No recruitment found");
140+
141+
const start = new Date(recruitment.periodStart);
142+
const dates: string[] = [];
143+
for (let i = 0; i < 7; i++) {
144+
const d = new Date(start);
145+
d.setDate(d.getDate() + i);
146+
dates.push(d.toISOString().slice(0, 10));
147+
}
148+
const isWeekend = (i: number) => {
149+
const dow = new Date(dates[i]).getDay();
150+
return dow === 0 || dow === 6;
151+
};
152+
153+
type Pattern = {
154+
name: string;
155+
email: string;
156+
mode: "requests" | "allOff" | "unsubmitted";
157+
pick: (i: number) => { startTime: string; endTime: string } | null;
158+
};
159+
const patterns: Pattern[] = [
160+
{
161+
name: "田中 健太",
162+
email: "tanaka.kenta@example.com",
163+
mode: "requests",
164+
pick: (i) =>
165+
isWeekend(i) ? { startTime: "17:00", endTime: "25:00" } : { startTime: "19:00", endTime: "23:00" },
166+
},
167+
{
168+
name: "佐藤 美咲",
169+
email: "sato.misaki@example.com",
170+
mode: "requests",
171+
pick: (i) => (i === 3 ? null : { startTime: "17:00", endTime: "24:00" }),
172+
},
173+
{
174+
name: "鈴木 翔太",
175+
email: "suzuki.shota@example.com",
176+
mode: "requests",
177+
pick: (i) => ([1, 3, 4].includes(i) ? { startTime: "18:00", endTime: "23:00" } : null),
178+
},
179+
{
180+
name: "高橋 由美",
181+
email: "takahashi.yumi@example.com",
182+
mode: "requests",
183+
pick: (i) => ([4, 5].includes(i) ? { startTime: "17:00", endTime: "22:00" } : null),
184+
},
185+
{
186+
name: "伊藤 直樹",
187+
email: "ito.naoki@example.com",
188+
mode: "requests",
189+
pick: (i) => (isWeekend(i) ? { startTime: "17:00", endTime: "21:00" } : null),
190+
},
191+
{
192+
name: "渡辺 彩香",
193+
email: "watanabe.ayaka@example.com",
194+
mode: "requests",
195+
pick: (i) => ([0, 2, 5].includes(i) ? { startTime: "18:30", endTime: "23:00" } : null),
196+
},
197+
{
198+
name: "山本 隆",
199+
email: "yamamoto.takashi@example.com",
200+
mode: "requests",
201+
pick: (i) => (i === 2 ? null : { startTime: "17:00", endTime: "25:00" }),
202+
},
203+
{
204+
name: "中村 愛",
205+
email: "nakamura.ai@example.com",
206+
mode: "unsubmitted",
207+
pick: () => null,
208+
},
209+
{
210+
name: "小林 陽介",
211+
email: "kobayashi.yosuke@example.com",
212+
mode: "requests",
213+
pick: (i) => (isWeekend(i) ? { startTime: "17:00", endTime: "24:00" } : null),
214+
},
215+
{
216+
name: "加藤 真理",
217+
email: "kato.mari@example.com",
218+
mode: "requests",
219+
pick: (i) => (!isWeekend(i) ? { startTime: "18:00", endTime: "22:00" } : null),
220+
},
221+
{
222+
name: "吉田 亮",
223+
email: "yoshida.ryo@example.com",
224+
mode: "requests",
225+
pick: (i) => (i === 6 ? null : { startTime: "17:00", endTime: "24:30" }),
226+
},
227+
{
228+
name: "山田 美穂",
229+
email: "yamada.miho@example.com",
230+
mode: "requests",
231+
pick: (i) => ([0, 2, 4].includes(i) ? { startTime: "18:00", endTime: "23:00" } : null),
232+
},
233+
{
234+
name: "佐々木 翔",
235+
email: "sasaki.sho@example.com",
236+
mode: "requests",
237+
pick: (i) => ([1, 4, 5, 6].includes(i) ? { startTime: "17:30", endTime: "22:30" } : null),
238+
},
239+
{
240+
name: "松本 涼子",
241+
email: "matsumoto.ryoko@example.com",
242+
mode: "allOff",
243+
pick: () => null,
244+
},
245+
{
246+
name: "井上 大樹",
247+
email: "inoue.daiki@example.com",
248+
mode: "requests",
249+
pick: (i) => ([2, 5].includes(i) ? { startTime: "19:00", endTime: "23:00" } : null),
250+
},
251+
];
252+
253+
let staffInserted = 0;
254+
let requestsInserted = 0;
255+
let submissionsInserted = 0;
256+
257+
for (const p of patterns) {
258+
const staffId = await ctx.db.insert("staffs", {
259+
shopId: shop._id,
260+
name: p.name,
261+
email: p.email,
262+
isDeleted: false,
263+
});
264+
staffInserted++;
265+
266+
if (p.mode === "unsubmitted") continue;
267+
268+
for (let i = 0; i < 7; i++) {
269+
const slot = p.pick(i);
270+
if (!slot) continue;
271+
await ctx.db.insert("shiftRequests", {
272+
recruitmentId: recruitment._id,
273+
staffId,
274+
date: dates[i],
275+
startTime: slot.startTime,
276+
endTime: slot.endTime,
277+
});
278+
requestsInserted++;
279+
}
280+
281+
await ctx.db.insert("shiftSubmissions", {
282+
recruitmentId: recruitment._id,
283+
staffId,
284+
submittedAt: Date.now(),
285+
});
286+
submissionsInserted++;
287+
}
288+
289+
return { staffInserted, requestsInserted, submissionsInserted, dates };
290+
},
291+
});
292+
125293
/**
126294
* 探索的テスト用:シフト提出画面のテストデータを一括セットアップ
127295
* shop + staff + recruitment + magicLink + session を作成し、sessionTokenを返す

src/components/features/Dashboard/DashboardContent/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ export const DashboardContent = ({
269269
>
270270
<Stack gap={1}>
271271
<Text>全員分の希望がそろっていません</Text>
272-
<Text>編集中にも変更される場合があります</Text>
272+
<Text>編集中にも希望シフトが追加される場合があります</Text>
273273
</Stack>
274274
</Dialog>
275275

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { Meta, StoryObj } from "@storybook/react-vite";
2+
import { SaveDraftWarningContent } from "./index";
3+
4+
const meta = {
5+
title: "Features/ShiftBoard/SaveDraftWarningContent",
6+
component: SaveDraftWarningContent,
7+
parameters: {
8+
layout: "padded",
9+
},
10+
} satisfies Meta<typeof SaveDraftWarningContent>;
11+
12+
export default meta;
13+
type Story = StoryObj<typeof meta>;
14+
15+
export const Default: Story = {};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Stack, Text } from "@chakra-ui/react";
2+
3+
export const SaveDraftWarningContent = () => {
4+
return (
5+
<Stack gap={3} fontSize="sm" lineHeight="tall">
6+
<Text>一時保存後に提出されたシフトは、シフト予定(青いバー)が自動で設定されません。</Text>
7+
<Text>以降提出されたシフトは手動でシフト予定時間を設定してください。</Text>
8+
<Text>※スタッフの希望シフト(黒い点線)は、引き続き設定されます。</Text>
9+
</Stack>
10+
);
11+
};

src/components/features/ShiftBoard/ShiftBoardHeader/index.stories.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const AllVariants = () => (
2222
periodLabel="1/20(月)〜1/26(日) のシフト"
2323
confirmedAt={null}
2424
onConfirm={() => {}}
25+
onSaveDraft={() => {}}
2526
viewMode="daily"
2627
onViewModeChange={() => {}}
2728
/>
@@ -33,6 +34,7 @@ const AllVariants = () => (
3334
periodLabel="1/20(月)〜1/26(日) のシフト"
3435
confirmedAt={new Date("2026-03-28T23:15:00")}
3536
onConfirm={() => {}}
37+
onSaveDraft={() => {}}
3638
viewMode="overview"
3739
onViewModeChange={() => {}}
3840
/>
@@ -45,6 +47,7 @@ export const Variants: Story = {
4547
periodLabel: "1/20(月)〜1/26(日) のシフト",
4648
confirmedAt: null,
4749
onConfirm: () => {},
50+
onSaveDraft: () => {},
4851
viewMode: "daily",
4952
onViewModeChange: () => {},
5053
},

src/components/features/ShiftBoard/ShiftBoardHeader/index.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export const ShiftBoardHeader = ({
1010
periodLabel,
1111
confirmedAt,
1212
onConfirm,
13+
onSaveDraft,
1314
viewMode,
1415
onViewModeChange,
1516
}: ShiftBoardHeaderProps) => {
@@ -37,6 +38,10 @@ export const ShiftBoardHeader = ({
3738
<SegmentGroup.Items items={VIEW_OPTIONS} cursor="pointer" />
3839
</SegmentGroup.Root>
3940

41+
<Button variant="outline" size="sm" onClick={onSaveDraft}>
42+
一時保存
43+
</Button>
44+
4045
{isConfirmed ? (
4146
<Button variant="outline" size="sm" borderColor="teal.600" color="teal.600" onClick={onConfirm}>
4247
再通知する

0 commit comments

Comments
 (0)