@aiquants/daily-report 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +150 -0
  3. package/dist/client.d.mts +449 -0
  4. package/dist/client.d.ts +449 -0
  5. package/dist/client.js +7 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/client.mjs +7 -0
  8. package/dist/client.mjs.map +1 -0
  9. package/dist/index.d.mts +42 -0
  10. package/dist/index.d.ts +42 -0
  11. package/dist/index.js +2 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/index.mjs +2 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/dist/logger-D3krZrNK.d.mts +29 -0
  16. package/dist/logger-D3krZrNK.d.ts +29 -0
  17. package/dist/server.d.mts +1515 -0
  18. package/dist/server.d.ts +1515 -0
  19. package/dist/server.js +10 -0
  20. package/dist/server.js.map +1 -0
  21. package/dist/server.mjs +10 -0
  22. package/dist/server.mjs.map +1 -0
  23. package/dist/sse-schema-CK7cUnEo.d.ts +1986 -0
  24. package/dist/sse-schema-yl5AaSsj.d.mts +1986 -0
  25. package/dist/types-CVhwLhSN.d.mts +76 -0
  26. package/dist/types-CVhwLhSN.d.ts +76 -0
  27. package/package.json +108 -0
  28. package/src/client/components/business-day-thumb-overlay.tsx +19 -0
  29. package/src/client/components/daily-report-comment-item.tsx +81 -0
  30. package/src/client/components/daily-report-comment-section.tsx +166 -0
  31. package/src/client/components/daily-report-detail-list.tsx +676 -0
  32. package/src/client/components/daily-report-edit-form.tsx +81 -0
  33. package/src/client/components/daily-report-list.tsx +1024 -0
  34. package/src/client/components/daily-report-page.tsx +147 -0
  35. package/src/client/components/daily-report-resolved-content.tsx +139 -0
  36. package/src/client/components/unread-indicator.tsx +13 -0
  37. package/src/client/config-context.tsx +129 -0
  38. package/src/client/contexts/daily-report-action-context.tsx +910 -0
  39. package/src/client/hooks/use-daily-report-comments.ts +73 -0
  40. package/src/client/hooks/use-daily-report-sse-connection.ts +86 -0
  41. package/src/client/hooks/use-daily-report.spec.ts +155 -0
  42. package/src/client/hooks/use-daily-report.ts +426 -0
  43. package/src/client/hooks/use-dynamic-viewport-height.ts +127 -0
  44. package/src/client/route-helpers.ts +76 -0
  45. package/src/client/ui/button.tsx +42 -0
  46. package/src/client/ui/cn.ts +14 -0
  47. package/src/client/ui/input.tsx +21 -0
  48. package/src/client/ui/label.tsx +16 -0
  49. package/src/client/ui/switch.tsx +19 -0
  50. package/src/client/ui/tabs.tsx +40 -0
  51. package/src/client/ui/textarea.tsx +19 -0
  52. package/src/client/utils/constants.ts +29 -0
  53. package/src/client.ts +21 -0
  54. package/src/index.ts +10 -0
  55. package/src/server/cache.ts +165 -0
  56. package/src/server/etag.ts +18 -0
  57. package/src/server/external-source.ts +60 -0
  58. package/src/server/handlers.ts +543 -0
  59. package/src/server/ports.ts +68 -0
  60. package/src/server/response.ts +37 -0
  61. package/src/server/schema.ts +266 -0
  62. package/src/server/service.spec.ts +97 -0
  63. package/src/server/service.ts +1308 -0
  64. package/src/server/sse-reader.spec.ts +55 -0
  65. package/src/server/sse-reader.ts +223 -0
  66. package/src/server.ts +83 -0
  67. package/src/shared/business-date.spec.ts +61 -0
  68. package/src/shared/business-date.ts +84 -0
  69. package/src/shared/comment-adapter.ts +78 -0
  70. package/src/shared/logger.ts +47 -0
  71. package/src/shared/sse-schema.ts +147 -0
  72. package/src/shared/text-utils.ts +57 -0
  73. package/src/shared/types.ts +76 -0
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Top-level daily-report page content (chrome-agnostic).
3
+ * アプリのレイアウトに依存しない日報ページ本体コンポーネント。
4
+ *
5
+ * アプリ側のルートは本コンポーネントを自前のレイアウト (Main 等) で包み、
6
+ * `config` でヘッダー描画・パス・ラベル名を注入する。
7
+ */
8
+ import { Suspense, useCallback, useEffect, useState } from "react"
9
+ import { Await, useRevalidator } from "react-router"
10
+ import type { DailyReportItem, DailyReportUser } from "../../shared/types"
11
+ import { type DailyReportClientConfigInput, DailyReportConfigProvider, useDailyReportConfig } from "../config-context"
12
+ import { DailyReportActionProvider } from "../contexts/daily-report-action-context"
13
+ import { DAILY_REPORT_FETCH_DEBOUNCE_MS } from "../utils/constants"
14
+ import { DailyReportResolvedContent } from "./daily-report-resolved-content"
15
+
16
+ /**
17
+ * Synchronizes resolved daily report ids into the action context provider state.
18
+ * Also renders the resolved content after synchronization trigger is set.
19
+ *
20
+ * 解決済みの日報 ID を Action Context 側へ同期し、解決済みコンテンツを描画するコンポーネント。
21
+ */
22
+ const DailyReportResolvedSync = ({ dailyReportItems, userId, onResolved }: { dailyReportItems: DailyReportItem[]; userId?: string | null; onResolved: (items: DailyReportItem[]) => void }) => {
23
+ /**
24
+ * Notifies parent when resolved items change.
25
+ * Dependencies:
26
+ * - dailyReportItems: latest resolved list from Await.
27
+ * - onResolved: parent synchronization callback.
28
+ * Cleanup:
29
+ * - None (pure synchronization side effect).
30
+ *
31
+ * 解決済み items が変化したときに親へ通知します。
32
+ * 依存配列:
33
+ * - dailyReportItems: Await で解決された最新リスト
34
+ * - onResolved: 親への同期コールバック
35
+ * クリーンアップ:
36
+ * - なし(同期のみの副作用)
37
+ */
38
+ useEffect(() => {
39
+ onResolved(dailyReportItems)
40
+ }, [dailyReportItems, onResolved])
41
+
42
+ return <DailyReportResolvedContent dailyReportItems={dailyReportItems} userId={userId} />
43
+ }
44
+
45
+ /**
46
+ * Error display component for daily report data fetch failures.
47
+ * 日報データの取得失敗時に表示するエラーコンポーネント。
48
+ */
49
+ export const DailyReportLoadError = () => {
50
+ const { title, renderHeader } = useDailyReportConfig()
51
+ const { revalidate } = useRevalidator()
52
+ return (
53
+ <>
54
+ {renderHeader({ title, annotation: <span className="font-bold text-red-500">Error</span> })}
55
+ <div className="h-full bg-slate-50 p-2">
56
+ <div className="mx-auto flex h-full w-full max-w-6xl flex-1 flex-col gap-6">
57
+ <div className="flex h-full flex-1 flex-col items-center justify-center gap-4 rounded-lg border border-red-200 bg-white p-4 text-slate-500 shadow-sm dark:border-red-900 dark:bg-slate-900/70 dark:text-slate-400">
58
+ <p className="font-medium text-red-600">日報データの読み込みに失敗しました。</p>
59
+ <button type="button" onClick={() => revalidate()} className="rounded bg-slate-100 px-4 py-2 text-slate-700 text-sm hover:bg-slate-200 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700">
60
+ 再読み込み
61
+ </button>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </>
66
+ )
67
+ }
68
+
69
+ /**
70
+ * Suspense fallback while daily report identifiers resolve.
71
+ * 日報 ID 解決中のサスペンスフォールバック。
72
+ */
73
+ export const DailyReportLoading = () => {
74
+ const { title, renderHeader } = useDailyReportConfig()
75
+ return (
76
+ <>
77
+ {renderHeader({
78
+ title,
79
+ annotation: (
80
+ <div className="flex flex-wrap gap-2 leading-none">
81
+ <span>総件数: 読み込み中...</span>
82
+ <span>デバウンス: {DAILY_REPORT_FETCH_DEBOUNCE_MS} ms</span>
83
+ </div>
84
+ ),
85
+ })}
86
+ <div className="h-full bg-slate-50 p-2">
87
+ <div className="mx-auto flex h-full w-full max-w-6xl flex-1 flex-col gap-6">
88
+ <div className="flex h-full flex-1 items-center justify-center rounded-lg border border-slate-200 bg-white text-slate-500 shadow-sm dark:border-slate-700 dark:bg-slate-900/70 dark:text-slate-300">日報 ID を読み込み中です...</div>
89
+ </div>
90
+ </div>
91
+ </>
92
+ )
93
+ }
94
+
95
+ export type DailyReportPageProps = {
96
+ /** 表示中ユーザー (楽観的コメントの作者名等に使用)。 */
97
+ user?: DailyReportUser
98
+ /** 難読化済みの内部ユーザー ID (未登録ユーザーは null)。 */
99
+ userId?: string | null
100
+ /** clientLoader で defer した日報 ID 一覧 (Promise) または解決済み配列。 */
101
+ dailyReportIds?: Promise<DailyReportItem[]> | DailyReportItem[] | null
102
+ /** クライアント設定の部分上書き (ヘッダー描画・パス・ラベル名・フィールドラベルなど)。 */
103
+ config?: DailyReportClientConfigInput
104
+ }
105
+
106
+ /**
107
+ * Daily report page content rendering provider, suspense boundary, and resolved views.
108
+ * プロバイダー・サスペンス境界・解決済みビューを描画する日報ページ本体。
109
+ */
110
+ const DailyReportPageInner = ({ user, userId, dailyReportIds }: Omit<DailyReportPageProps, "config">) => {
111
+ const [resolvedInitialItems, setResolvedInitialItems] = useState<DailyReportItem[]>([])
112
+
113
+ const handleResolvedItems = useCallback((items: DailyReportItem[]) => {
114
+ setResolvedInitialItems(items)
115
+ }, [])
116
+
117
+ return (
118
+ <DailyReportActionProvider user={user} initialItems={resolvedInitialItems} userId={userId}>
119
+ <Suspense fallback={<DailyReportLoading />}>
120
+ {/* Resolve で非同期取得済み ID を確定表示 */}
121
+ {dailyReportIds ? (
122
+ <Await resolve={dailyReportIds} errorElement={<DailyReportLoadError />}>
123
+ {(resolvedItems) => {
124
+ if (!resolvedItems) {
125
+ throw new Error("Daily report ids resolve must not be null")
126
+ }
127
+ // 非 null を保証して以降の処理で利用
128
+ return <DailyReportResolvedSync dailyReportItems={resolvedItems as DailyReportItem[]} userId={userId} onResolved={handleResolvedItems} />
129
+ }}
130
+ </Await>
131
+ ) : (
132
+ <DailyReportLoading />
133
+ )}
134
+ </Suspense>
135
+ </DailyReportActionProvider>
136
+ )
137
+ }
138
+
139
+ /**
140
+ * Chrome-agnostic daily report page with client configuration provider.
141
+ * 設定プロバイダー込みの日報ページコンポーネント。
142
+ */
143
+ export const DailyReportPage = ({ config, ...rest }: DailyReportPageProps) => (
144
+ <DailyReportConfigProvider config={config}>
145
+ <DailyReportPageInner {...rest} />
146
+ </DailyReportConfigProvider>
147
+ )
@@ -0,0 +1,139 @@
1
+ import { Plus } from "lucide-react"
2
+ import type { ReactNode } from "react"
3
+ import { useEffect, useMemo, useState } from "react"
4
+ import { useSearchParams } from "react-router"
5
+ import type { DailyReportItem } from "../../shared/types"
6
+ import { useDailyReportConfig } from "../config-context"
7
+ import { useDailyReportActionContext } from "../contexts/daily-report-action-context"
8
+ import { Button } from "../ui/button"
9
+ import { Label } from "../ui/label"
10
+ import { Switch } from "../ui/switch"
11
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"
12
+ import { DAILY_REPORT_FETCH_DEBOUNCE_MS } from "../utils/constants"
13
+ import { DailyReportDetailList } from "./daily-report-detail-list"
14
+ import { DailyReportList } from "./daily-report-list"
15
+
16
+ const tabTriggerClass = "min-w-24 p-2 font-medium text-slate-600 text-xs transition-colors data-[state=active]:bg-white data-[state=active]:text-slate-900 sm:min-w-36 sm:text-sm dark:text-slate-300 dark:data-[state=active]:bg-slate-800 dark:data-[state=active]:text-slate-100"
17
+
18
+ type DailyReportResolvedContentProps = {
19
+ dailyReportItems: DailyReportItem[]
20
+ userId?: string | null
21
+ }
22
+
23
+ /**
24
+ * Displays fetched daily reports with layout and tabbed virtual scroll views.
25
+ * レイアウトとタブ付き仮想スクロールで取得済み日報を表示するコンポーネント。
26
+ */
27
+ export const DailyReportResolvedContent = ({ dailyReportItems: _initialItems, userId }: DailyReportResolvedContentProps) => {
28
+ // 自動既読設定
29
+ const [autoMarkRead, setAutoMarkRead] = useState(false)
30
+ const { title, renderHeader, showDevControls } = useDailyReportConfig()
31
+ const { isSseEnabled, toggleSse, createReport, items: dailyReportItems } = useDailyReportActionContext()
32
+ const [searchParams] = useSearchParams()
33
+
34
+ // 選択状態の管理 (タブ間で共有)
35
+ const [selectedItemId, setSelectedItemId] = useState<number | null>(() => {
36
+ if (dailyReportItems.length === 0) return null
37
+ return dailyReportItems[0].reportHubId
38
+ })
39
+
40
+ const handleCreateReport = async () => {
41
+ const businessDateParam = searchParams.get("businessDate")
42
+ const today = new Date().toISOString().split("T")[0]
43
+ const dateToUse = businessDateParam || today
44
+ const newId = await createReport(dateToUse)
45
+ setSelectedItemId(newId)
46
+ }
47
+
48
+ /**
49
+ * Effect to load the auto-read setting from local storage on mount.
50
+ * マウント時にローカルストレージから自動既読設定を読み込む副作用。
51
+ *
52
+ * Purpose: To persist user preference for auto-read behavior.
53
+ * Dependencies: [] - Runs only on mount.
54
+ * Cleanup: None.
55
+ */
56
+ useEffect(() => {
57
+ const stored = localStorage.getItem("daily-report-auto-read")
58
+ if (stored === "true") {
59
+ setAutoMarkRead(true)
60
+ }
61
+ }, [])
62
+
63
+ const handleAutoMarkReadChange = (checked: boolean) => {
64
+ setAutoMarkRead(checked)
65
+ localStorage.setItem("daily-report-auto-read", String(checked))
66
+ }
67
+
68
+ const tabItems = useMemo<{ value: string; label: string; content: ReactNode }[]>(
69
+ () => [
70
+ {
71
+ value: "list",
72
+ label: "📄 List",
73
+ content: <DailyReportList dailyReportItems={dailyReportItems} autoMarkRead={autoMarkRead} selectedReportHubId={selectedItemId} onSelectItem={setSelectedItemId} userId={userId} />,
74
+ },
75
+ {
76
+ value: "tree",
77
+ label: "🌳 Tree",
78
+ content: <div className="flex flex-1 items-center justify-center rounded-lg border border-slate-300 border-dashed bg-white p-6 text-slate-500 transition-colors dark:border-slate-700 dark:bg-slate-900/70 dark:text-slate-300">Tree ビューは現在準備中です</div>,
79
+ },
80
+ {
81
+ value: "detailList",
82
+ label: "📋 DetailList",
83
+ content: <DailyReportDetailList dailyReportItems={dailyReportItems} userId={userId} selectedItemId={selectedItemId} onSelectItem={setSelectedItemId} />,
84
+ },
85
+ ],
86
+ [dailyReportItems, autoMarkRead, selectedItemId, userId],
87
+ )
88
+
89
+ return (
90
+ <>
91
+ {renderHeader({
92
+ title,
93
+ annotation: (
94
+ <div className="flex flex-wrap gap-2 leading-1">
95
+ <span>総件数: {dailyReportItems.length.toLocaleString()} 件</span>
96
+ <span>デバウンス: {DAILY_REPORT_FETCH_DEBOUNCE_MS} ms</span>
97
+ </div>
98
+ ),
99
+ })}
100
+ <div className="h-full bg-slate-50 dark:bg-slate-500">
101
+ <div className="flex h-full w-full max-w-6xl flex-1 flex-col p-1">
102
+ <Tabs defaultValue="list" className="flex h-full flex-1 flex-col">
103
+ <div className="flex items-center justify-between gap-2 rounded-md bg-slate-50 p-1 transition-colors dark:bg-slate-900/70 dark:text-slate-200">
104
+ <TabsList className="scrollbar-none flex w-full touch-pan-x flex-nowrap overflow-x-auto overflow-y-hidden bg-transparent p-0">
105
+ {tabItems.map(({ value, label }) => (
106
+ <TabsTrigger key={value} value={value} className={tabTriggerClass}>
107
+ {label}
108
+ </TabsTrigger>
109
+ ))}
110
+ </TabsList>
111
+ <div className="flex shrink-0 items-center space-x-1 px-2">
112
+ <Button variant="ghost" size="icon" onClick={handleCreateReport} title="日報を作成">
113
+ <Plus className="h-4 w-4" />
114
+ </Button>
115
+ {showDevControls && (
116
+ <div className="hidden items-center space-x-1 sm:flex">
117
+ <Switch id="sse-mode" checked={isSseEnabled} onCheckedChange={toggleSse} className="scale-75" />
118
+ <Label htmlFor="sse-mode" className="cursor-pointer text-slate-500 text-xs">
119
+ 購読
120
+ </Label>
121
+ </div>
122
+ )}
123
+ <Switch id="auto-read-mode" checked={autoMarkRead} onCheckedChange={handleAutoMarkReadChange} className="scale-75" />
124
+ <Label htmlFor="auto-read-mode" className="cursor-pointer text-slate-500 text-xs">
125
+ 自動既読
126
+ </Label>
127
+ </div>
128
+ </div>
129
+ {tabItems.map(({ value, content }) => (
130
+ <TabsContent key={value} value={value} className="mt-0 flex h-full min-h-0 flex-1 flex-col">
131
+ {content}
132
+ </TabsContent>
133
+ ))}
134
+ </Tabs>
135
+ </div>
136
+ </div>
137
+ </>
138
+ )
139
+ }
@@ -0,0 +1,13 @@
1
+ import { cn } from "../ui/cn"
2
+
3
+ type UnreadIndicatorProps = {
4
+ className?: string
5
+ }
6
+
7
+ /**
8
+ * A simple blue dot indicator used to represent unread status.
9
+ * 未読状態を表すためのシンプルな青いドットインジケーター。
10
+ */
11
+ export const UnreadIndicator = ({ className }: UnreadIndicatorProps) => {
12
+ return <span className={cn("text-blue-500", className)}>●</span>
13
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Client-side DI configuration for the daily-report UI (paths, chrome slots, labels).
3
+ * 日報 UI のクライアント側 DI 設定 (パス・クローム差し込み・ラベル名) を提供するコンテキスト。
4
+ */
5
+ import { createContext, type ReactNode, useContext, useMemo } from "react"
6
+
7
+ /** ヘッダー描画スロットに渡される引数。 */
8
+ export type DailyReportHeaderProps = {
9
+ title: string
10
+ annotation?: ReactNode
11
+ }
12
+
13
+ /**
14
+ * Display labels for the report detail/list fields, sections, and side-pane tabs.
15
+ * 日報の詳細/一覧カードのフィールド・セクション・タブ見出しの表示ラベル群。
16
+ *
17
+ * パッケージは中立な英語ラベルのみを既定として同梱し、ドメイン固有の表現 (言語・業務語彙) は
18
+ * 消費アプリが `fieldLabels` で注入する。これによりパッケージ本体が業務ドメイン知識を持たない。
19
+ */
20
+ export type DailyReportFieldLabels = {
21
+ id: string
22
+ businessDate: string
23
+ author: string
24
+ visitTime: string
25
+ createdAt: string
26
+ updatedAt: string
27
+ updatedBy: string
28
+ category: string
29
+ categoryInfo: string
30
+ creationCategory: string
31
+ customer: string
32
+ subject: string
33
+ content: string
34
+ interviewers: string
35
+ comments: string
36
+ tabArticle: string
37
+ tabRelations: string
38
+ relationsEmpty: string
39
+ }
40
+
41
+ /** クライアント層の設定一式。 */
42
+ export type DailyReportClientConfig = {
43
+ /** 画面タイトル (ヘッダーへ渡す)。 */
44
+ title: string
45
+ /** API ベースパス (例 `/daily_report/api`)。末尾スラッシュ無し。 */
46
+ apiBasePath: string
47
+ /** SSE エンドポイントパス (例 `/sse/daily_report/updates`)。 */
48
+ ssePath: string
49
+ /** 下書き状態を表すラベル名。UI の編集モード判定に使う。既定はプレースホルダーなので消費側で自アプリのラベル名へ上書きする。 */
50
+ draftLabelName: string
51
+ /** 詳細/一覧カードのフィールド見出しラベル。既定は中立英語。アプリが自ドメインの表記を注入する。 */
52
+ fieldLabels: DailyReportFieldLabels
53
+ /** ヘッダー描画スロット。アプリのヘッダーコンポーネントを差し込む。 */
54
+ renderHeader: (props: DailyReportHeaderProps) => ReactNode
55
+ /** 開発向けコントロール (SSE 購読トグル等) を表示するか。 */
56
+ showDevControls: boolean
57
+ }
58
+
59
+ /**
60
+ * Partial client-config override. `fieldLabels` may itself be partially overridden.
61
+ * クライアント設定の部分上書き型。`fieldLabels` も部分上書き可 (プロバイダーが深くマージする)。
62
+ */
63
+ export type DailyReportClientConfigInput = Partial<Omit<DailyReportClientConfig, "fieldLabels">> & {
64
+ fieldLabels?: Partial<DailyReportFieldLabels>
65
+ }
66
+
67
+ /** 中立英語のフィールドラベル既定 (ドメイン非依存)。 */
68
+ export const defaultDailyReportFieldLabels: DailyReportFieldLabels = {
69
+ id: "ID",
70
+ businessDate: "Business date",
71
+ author: "Author",
72
+ visitTime: "Visit time",
73
+ createdAt: "Created at",
74
+ updatedAt: "Updated at",
75
+ updatedBy: "Updated by",
76
+ category: "Category",
77
+ categoryInfo: "Category",
78
+ creationCategory: "Creation category",
79
+ customer: "Customer",
80
+ subject: "Subject",
81
+ content: "Content",
82
+ interviewers: "Attendees",
83
+ comments: "Comments",
84
+ tabArticle: "Overview",
85
+ tabRelations: "Related",
86
+ relationsEmpty: "No related view configured yet.",
87
+ }
88
+
89
+ /** 既定設定 (消費側は必要なフィールドのみ部分上書きする)。 */
90
+ export const defaultDailyReportClientConfig: DailyReportClientConfig = {
91
+ title: "Daily Reports",
92
+ apiBasePath: "/daily_report/api",
93
+ ssePath: "/sse/daily_report/updates",
94
+ // 中立プレースホルダー。実運用のラベル名 (アプリ固有) は Provider / config prop で上書きする。
95
+ draftLabelName: "draft",
96
+ fieldLabels: defaultDailyReportFieldLabels,
97
+ renderHeader: ({ title, annotation }) => (
98
+ <header className="flex flex-wrap items-baseline justify-between gap-2 px-2 py-1">
99
+ <span className="text-nowrap text-slate-700 text-sm dark:text-slate-200">{title}</span>
100
+ {annotation ? <span className="text-slate-600 text-xs dark:text-slate-300">{annotation}</span> : null}
101
+ </header>
102
+ ),
103
+ showDevControls: false,
104
+ }
105
+
106
+ const DailyReportConfigContext = createContext<DailyReportClientConfig>(defaultDailyReportClientConfig)
107
+
108
+ /**
109
+ * Provides a partial override of the daily-report client configuration.
110
+ * 日報クライアント設定の部分上書きを提供するプロバイダー。
111
+ */
112
+ export const DailyReportConfigProvider = ({ config, children }: { config?: DailyReportClientConfigInput; children: ReactNode }) => {
113
+ // 部分設定を既定値へ浅くマージする (fieldLabels のみ 1 段深くマージして部分上書きを許す)
114
+ const merged = useMemo<DailyReportClientConfig>(
115
+ () => ({
116
+ ...defaultDailyReportClientConfig,
117
+ ...config,
118
+ fieldLabels: { ...defaultDailyReportFieldLabels, ...(config?.fieldLabels ?? {}) },
119
+ }),
120
+ [config],
121
+ )
122
+ return <DailyReportConfigContext.Provider value={merged}>{children}</DailyReportConfigContext.Provider>
123
+ }
124
+
125
+ /**
126
+ * Reads the daily-report client configuration (defaults when no provider).
127
+ * 日報クライアント設定を読む (プロバイダー未設置時は既定値)。
128
+ */
129
+ export const useDailyReportConfig = (): DailyReportClientConfig => useContext(DailyReportConfigContext)