From eda29a3a17a564e8625b89644ccbefe3b36b81cb Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Sun, 28 Dec 2025 21:08:48 +0900 Subject: [PATCH 01/47] [add] Add buy reports summary endpoint and related types --- openapi/openapi.yaml | 73 ++++++++++++++++++ view/next-project/src/generated/hooks.ts | 75 +++++++++++++++++++ .../src/generated/model/buyReport.ts | 1 + .../src/generated/model/buyReportSummary.ts | 17 +++++ .../model/getBuyReportsDetailsParams.ts | 12 +++ .../model/getBuyReportsSummaryParams.ts | 26 +++++++ .../next-project/src/generated/model/index.ts | 2 + 7 files changed, 206 insertions(+) create mode 100644 view/next-project/src/generated/model/buyReportSummary.ts create mode 100644 view/next-project/src/generated/model/getBuyReportsSummaryParams.ts diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index ae7a4be52..4a32f60dc 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -773,6 +773,24 @@ paths: description: year schema: type: integer + - name: financial_record_id + in: query + description: financial_records.id での完全一致フィルタ + required: false + schema: + type: integer + - name: paid_by + in: query + description: buy_reports.paid_by での完全一致フィルタ + required: false + schema: + type: string + - name: paid_by_user_id + in: query + description: buy_reports.paid_by_user_id での完全一致フィルタ + required: false + schema: + type: integer responses: "200": description: buy_reportの一覧を取得 @@ -782,6 +800,43 @@ paths: type: array items: $ref: "#/components/schemas/buyReportDetail" + /buy_reports/summary: + get: + tags: + - buy_report + description: 購入報告一覧の未精算/未封詰め合計を返す + parameters: + - name: year + in: query + description: year + required: true + schema: + type: integer + - name: financial_record_id + in: query + description: financial_records.id での完全一致フィルタ + required: false + schema: + type: integer + - name: paid_by + in: query + description: buy_reports.paid_by での完全一致フィルタ + required: false + schema: + type: string + - name: paid_by_user_id + in: query + description: buy_reports.paid_by_user_id での完全一致フィルタ + required: false + schema: + type: integer + responses: + "200": + description: 条件に一致する購入報告のサマリ + content: + application/json: + schema: + $ref: "#/components/schemas/buyReportSummary" /buy_report/status/{buy_report_id}: put: tags: @@ -2789,6 +2844,9 @@ components: paidBy: type: string example: 企画局_技大太郎 + paidByUserId: + type: integer + example: 1 required: - festivalItemID - amount @@ -2885,6 +2943,21 @@ components: - paidBy - reportDate - filePath + buyReportSummary: + description: 購入報告の未精算/未封詰め合計 + type: object + properties: + unsettledAmount: + type: integer + example: 12000 + description: isSettled=falseのamount合計 + unpackedAmount: + type: integer + example: 8000 + description: isPacked=falseのamount合計 + required: + - unsettledAmount + - unpackedAmount division: required: - name diff --git a/view/next-project/src/generated/hooks.ts b/view/next-project/src/generated/hooks.ts index 901af7a75..2f91dbb63 100644 --- a/view/next-project/src/generated/hooks.ts +++ b/view/next-project/src/generated/hooks.ts @@ -17,6 +17,7 @@ import type { ActivityStyle, BuyReport, BuyReportDetail, + BuyReportSummary, BuyReportWithDivisionId, DeleteActivitiesId200, DeleteActivityInformationsId200, @@ -75,6 +76,7 @@ import type { GetBureausId200, GetBuyReportsCsvDownloadParams, GetBuyReportsDetailsParams, + GetBuyReportsSummaryParams, GetDepartments200, GetDepartmentsId200, GetDivisionsParams, @@ -2612,6 +2614,79 @@ export const useGetBuyReportsDetails = ( }; }; +/** + * 購入報告一覧の未精算/未封詰め合計を返す + */ +export type getBuyReportsSummaryResponse200 = { + data: BuyReportSummary; + status: 200; +}; + +export type getBuyReportsSummaryResponseComposite = getBuyReportsSummaryResponse200; + +export type getBuyReportsSummaryResponse = getBuyReportsSummaryResponseComposite & { + headers: Headers; +}; + +export const getGetBuyReportsSummaryUrl = (params: GetBuyReportsSummaryParams) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/buy_reports/summary?${stringifiedParams}` + : `/buy_reports/summary`; +}; + +export const getBuyReportsSummary = async ( + params: GetBuyReportsSummaryParams, + options?: RequestInit, +): Promise => { + return customFetch(getGetBuyReportsSummaryUrl(params), { + ...options, + method: 'GET', + }); +}; + +export const getGetBuyReportsSummaryKey = (params: GetBuyReportsSummaryParams) => + [`/buy_reports/summary`, ...(params ? [params] : [])] as const; + +export type GetBuyReportsSummaryQueryResult = NonNullable< + Awaited> +>; +export type GetBuyReportsSummaryQueryError = unknown; + +export const useGetBuyReportsSummary = ( + params: GetBuyReportsSummaryParams, + options?: { + swr?: SWRConfiguration>, TError> & { + swrKey?: Key; + enabled?: boolean; + }; + request?: SecondParameter; + }, +) => { + const { swr: swrOptions, request: requestOptions } = options ?? {}; + + const isEnabled = swrOptions?.enabled !== false; + const swrKey = + swrOptions?.swrKey ?? (() => (isEnabled ? getGetBuyReportsSummaryKey(params) : null)); + const swrFn = () => getBuyReportsSummary(params, requestOptions); + + const query = useSwr>, TError>(swrKey, swrFn, swrOptions); + + return { + swrKey, + ...query, + }; +}; + /** * buy_reportのステータス更新、財務が封詰め、精算済みにするAPI */ diff --git a/view/next-project/src/generated/model/buyReport.ts b/view/next-project/src/generated/model/buyReport.ts index ed9254e11..c8430d00b 100644 --- a/view/next-project/src/generated/model/buyReport.ts +++ b/view/next-project/src/generated/model/buyReport.ts @@ -14,4 +14,5 @@ export interface BuyReport { festivalItemID: number; amount: number; paidBy: string; + paidByUserId?: number; } diff --git a/view/next-project/src/generated/model/buyReportSummary.ts b/view/next-project/src/generated/model/buyReportSummary.ts new file mode 100644 index 000000000..12243f36a --- /dev/null +++ b/view/next-project/src/generated/model/buyReportSummary.ts @@ -0,0 +1,17 @@ +/** + * Generated by orval v7.6.0 🍺 + * Do not edit manually. + * NUTFes FinanSu API + * FinanSu APIドキュメント + * OpenAPI spec version: 2.0.0 + */ + +/** + * 購入報告の未精算/未封詰め合計 + */ +export interface BuyReportSummary { + /** isSettled=falseのamount合計 */ + unsettledAmount: number; + /** isPacked=falseのamount合計 */ + unpackedAmount: number; +} diff --git a/view/next-project/src/generated/model/getBuyReportsDetailsParams.ts b/view/next-project/src/generated/model/getBuyReportsDetailsParams.ts index 493673557..f357c0e95 100644 --- a/view/next-project/src/generated/model/getBuyReportsDetailsParams.ts +++ b/view/next-project/src/generated/model/getBuyReportsDetailsParams.ts @@ -11,4 +11,16 @@ export type GetBuyReportsDetailsParams = { * year */ year?: number; + /** + * financial_records.id での完全一致フィルタ + */ + financial_record_id?: number; + /** + * buy_reports.paid_by での完全一致フィルタ + */ + paid_by?: string; + /** + * buy_reports.paid_by_user_id での完全一致フィルタ + */ + paid_by_user_id?: number; }; diff --git a/view/next-project/src/generated/model/getBuyReportsSummaryParams.ts b/view/next-project/src/generated/model/getBuyReportsSummaryParams.ts new file mode 100644 index 000000000..f1bcdedca --- /dev/null +++ b/view/next-project/src/generated/model/getBuyReportsSummaryParams.ts @@ -0,0 +1,26 @@ +/** + * Generated by orval v7.6.0 🍺 + * Do not edit manually. + * NUTFes FinanSu API + * FinanSu APIドキュメント + * OpenAPI spec version: 2.0.0 + */ + +export type GetBuyReportsSummaryParams = { + /** + * year + */ + year: number; + /** + * financial_records.id での完全一致フィルタ + */ + financial_record_id?: number; + /** + * buy_reports.paid_by での完全一致フィルタ + */ + paid_by?: string; + /** + * buy_reports.paid_by_user_id での完全一致フィルタ + */ + paid_by_user_id?: number; +}; diff --git a/view/next-project/src/generated/model/index.ts b/view/next-project/src/generated/model/index.ts index 868c4b0dd..3a5a2db90 100644 --- a/view/next-project/src/generated/model/index.ts +++ b/view/next-project/src/generated/model/index.ts @@ -13,6 +13,7 @@ export * from './buyReport'; export * from './buyReportDetail'; export * from './buyReportInformation'; export * from './buyReportInformationStatus'; +export * from './buyReportSummary'; export * from './buyReportWithDivisionId'; export * from './deleteActivitiesId200'; export * from './deleteActivityInformationsId200'; @@ -77,6 +78,7 @@ export * from './getBureausId200'; export * from './getBuyReportsCsvDownloadParams'; export * from './getBuyReportsDetailsParams'; export * from './getBuyReportsListParams'; +export * from './getBuyReportsSummaryParams'; export * from './getDepartments200'; export * from './getDepartmentsId200'; export * from './getDivisionsParams'; From 3fa14ff502c3dbac5ae7fe81147382b414d6feab Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Mon, 29 Dec 2025 00:13:27 +0900 Subject: [PATCH 02/47] [add] Implement PurchaseReportSummaryAmounts component and integrate it into the purchase reports page --- .../PurchaseReportSummaryAmounts.tsx | 30 +++++++++++++++++++ .../src/components/purchasereports/index.ts | 1 + .../src/pages/purchase_report_list/index.tsx | 24 +++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx diff --git a/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx new file mode 100644 index 000000000..1e4bcddef --- /dev/null +++ b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +interface PurchaseReportSummaryAmountsProps { + unsettledAmountText: string; + unpackedAmountText: string; + className?: string; +} + +export default function PurchaseReportSummaryAmounts({ + unsettledAmountText, + unpackedAmountText, + className = 'text-sm text-black-600 md:ml-auto', +}: PurchaseReportSummaryAmountsProps) { + return ( +
+
+ 未清算金額 + + + {unsettledAmountText} 円 + + 未封詰め金額 + + + {unpackedAmountText} 円 + +
+
+ ); +} diff --git a/view/next-project/src/components/purchasereports/index.ts b/view/next-project/src/components/purchasereports/index.ts index eca7bd61e..ecd14bde4 100644 --- a/view/next-project/src/components/purchasereports/index.ts +++ b/view/next-project/src/components/purchasereports/index.ts @@ -11,5 +11,6 @@ export { default as PurchaseOrderListModal } from './PurchaseOrderListModal'; export { default as PurchaseReportAddModal } from './PurchaseReportAddModal'; export { default as PurchaseReportConfirmModal } from './PurchaseReportConfirmModal'; export { default as PurchaseReportItemNumModal } from './PurchaseReportItemNumModal'; // "PurchaseReport|temNumModal"を修正しました。 +export { default as PurchaseReportSummaryAmounts } from './PurchaseReportSummaryAmounts'; export { default as ReceiptModal } from './ReceiptModal'; export { default as CheckSettlementConfirmModal } from './CheckSettlementConfirmModal'; diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index b789d18de..8aceebb99 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -6,13 +6,16 @@ import { useRecoilValue } from 'recoil'; import DownloadButton from '@/components/common/DownloadButton'; import PrimaryButton from '@/components/common/OutlinePrimaryButton/OutlinePrimaryButton'; import { OpenCheckSettlementModalButton } from '@/components/purchasereports'; +import PurchaseReportSummaryAmounts from '@/components/purchasereports/PurchaseReportSummaryAmounts'; import { useGetBuyReportsDetails, + useGetBuyReportsSummary, useGetYearsPeriods, usePutBuyReportStatusBuyReportId, } from '@/generated/hooks'; import type { GetBuyReportsDetailsParams, + GetBuyReportsSummaryParams, BuyReportDetail, PutBuyReportStatusBuyReportIdBody, } from '@/generated/model'; @@ -52,6 +55,14 @@ export default function PurchaseReports() { mutate: mutateBuyReportData, } = useGetBuyReportsDetails(getBuyReportsDetailsParams); const buyReports = useMemo(() => buyReportsData?.data ?? [], [buyReportsData]); + const getBuyReportsSummaryParams: GetBuyReportsSummaryParams = { year: selectedYear }; + const { + data: buyReportsSummaryData, + isLoading: isBuyReportsSummaryLoading, + error: buyReportsSummaryError, + } = useGetBuyReportsSummary(getBuyReportsSummaryParams, { + swr: { enabled: selectedYear > 0 }, + }); const [sealChecks, setSealChecks] = useState>({}); const [settlementChecks, setSettlementChecks] = useState>({}); @@ -106,6 +117,15 @@ export default function PurchaseReports() { const formatAmount = useCallback((amount: number) => { return amount.toLocaleString(); }, []); + const buyReportsSummary = buyReportsSummaryData?.data; + const summaryUnsettledAmount = + isBuyReportsSummaryLoading || buyReportsSummaryError || buyReportsSummary == null + ? '-' + : formatAmount(buyReportsSummary.unsettledAmount ?? 0); + const summaryUnpackedAmount = + isBuyReportsSummaryLoading || buyReportsSummaryError || buyReportsSummary == null + ? '-' + : formatAmount(buyReportsSummary.unpackedAmount ?? 0); const download = async (url: string, fileName: string) => { const downloadPath = `${process.env.NEXT_PUBLIC_MINIO_ENDPONT}/finansu/${url}`; @@ -181,6 +201,10 @@ export default function PurchaseReports() { CSVダウンロード +
From 9d47aebc7c66fce96d1f75893c26c13d874d2004 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Mon, 29 Dec 2025 00:18:31 +0900 Subject: [PATCH 03/47] [otr] Simplify JSX formatting in PurchaseReportSummaryAmounts component --- .../purchasereports/PurchaseReportSummaryAmounts.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx index 1e4bcddef..9c1475e9a 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx @@ -16,14 +16,10 @@ export default function PurchaseReportSummaryAmounts({
未清算金額 - - {unsettledAmountText} 円 - + {unsettledAmountText} 円 未封詰め金額 - - {unpackedAmountText} 円 - + {unpackedAmountText} 円
); From 44167c1109c4ce7e02dc13c5872b5b9d7d45b1c4 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Wed, 31 Dec 2025 16:32:35 +0900 Subject: [PATCH 04/47] [add] PurchaseReportPaidByFilterModal component and integrate it into the purchase reports page --- .../PurchaseReportPaidByFilterModal.tsx | 116 ++++++++++++++++++ .../src/pages/purchase_report_list/index.tsx | 53 +++++++- 2 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx new file mode 100644 index 000000000..a7a4f7ec8 --- /dev/null +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -0,0 +1,116 @@ +import React, { FC, useEffect, useMemo, useState } from 'react'; + +import { CloseButton, Modal, OutlinePrimaryButton, Select } from '@components/common'; +import { Bureau, User } from '@type/common'; + +interface PurchaseReportPaidByFilterModalProps { + isOpen: boolean; + onClose: () => void; + onApply: (selection: { + bureauId: number | null; + paidByUserId: number | null | undefined; + }) => void; + bureaus: Bureau[]; + users: User[]; + selectedBureauId: number | null; + selectedPaidByUserId: number | null | undefined; +} + +const PurchaseReportPaidByFilterModal: FC = (props) => { + const { isOpen, onClose, onApply, bureaus, users, selectedBureauId, selectedPaidByUserId } = + props; + + const [draftBureauId, setDraftBureauId] = useState(selectedBureauId); + const [draftPaidByUserId, setDraftPaidByUserId] = useState( + selectedPaidByUserId, + ); + + useEffect(() => { + if (!isOpen) return; + setDraftBureauId(selectedBureauId); + setDraftPaidByUserId(selectedPaidByUserId); + }, [isOpen, selectedBureauId, selectedPaidByUserId]); + + const bureauNameMap = useMemo( + () => + new Map( + bureaus.map((bureau) => [bureau.id ?? 0, bureau.name] as const).filter(([id]) => id > 0), + ), + [bureaus], + ); + + const filteredUsers = useMemo(() => { + if (!draftBureauId) return users; + return users.filter((user) => user.bureauID === draftBureauId); + }, [draftBureauId, users]); + + const paidBySelectValue = draftPaidByUserId === null ? 'none' : draftPaidByUserId ?? ''; + + const handleBureauChange = (event: React.ChangeEvent) => { + const value = event.target.value; + const nextBureauId = value === '' ? null : Number(value); + setDraftBureauId(nextBureauId); + setDraftPaidByUserId(undefined); + }; + + const handlePaidByChange = (event: React.ChangeEvent) => { + const value = event.target.value; + if (value === '') { + setDraftPaidByUserId(undefined); + return; + } + if (value === 'none') { + setDraftPaidByUserId(null); + return; + } + setDraftPaidByUserId(Number(value)); + }; + + const handleApply = () => { + onApply({ + bureauId: draftBureauId ?? null, + paidByUserId: draftPaidByUserId, + }); + }; + + return ( + +
+ +
+
+
+

局名

+ +
+
+

氏名

+ + {/* NOTE: paid_by_user_id の NULL を絞り込む仕様が未定義のため「立替者なし」は未実装。 */} +
+
+
+ 設定 +
+
+ ); +}; + +export default PurchaseReportPaidByFilterModal; diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index 8aceebb99..b90a80ca1 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -1,15 +1,19 @@ import { saveAs } from 'file-saver'; import { useRouter } from 'next/router'; import { useCallback, useState, useEffect, useMemo } from 'react'; +import { RiArrowDropDownLine } from 'react-icons/ri'; import { TbDownload } from 'react-icons/tb'; import { useRecoilValue } from 'recoil'; import DownloadButton from '@/components/common/DownloadButton'; import PrimaryButton from '@/components/common/OutlinePrimaryButton/OutlinePrimaryButton'; import { OpenCheckSettlementModalButton } from '@/components/purchasereports'; +import PurchaseReportPaidByFilterModal from '@/components/purchasereports/PurchaseReportPaidByFilterModal'; import PurchaseReportSummaryAmounts from '@/components/purchasereports/PurchaseReportSummaryAmounts'; +import { BUREAUS } from '@/constants/bureaus'; import { useGetBuyReportsDetails, useGetBuyReportsSummary, + useGetUsers, useGetYearsPeriods, usePutBuyReportStatusBuyReportId, } from '@/generated/hooks'; @@ -23,6 +27,7 @@ import { userAtom } from '@/store/atoms'; import { Card, Checkbox, EditButton, Loading, Title } from '@components/common'; import MainLayout from '@components/layout/MainLayout'; import OpenDeleteModalButton from '@components/purchasereports/OpenDeleteModalButton'; +import type { User } from '@type/common'; export default function PurchaseReports() { const router = useRouter(); @@ -33,6 +38,12 @@ export default function PurchaseReports() { } = useGetYearsPeriods(); const yearPeriods = yearPeriodsData?.data; const user = useRecoilValue(userAtom); + const { data: usersResponse } = useGetUsers(); + const users = useMemo(() => { + const responseData = usersResponse?.data as User[] | { data?: User[] } | undefined; + if (Array.isArray(responseData)) return responseData; + return responseData?.data ?? []; + }, [usersResponse]); user?.roleID === 1 && router.push('/my_page'); @@ -46,7 +57,15 @@ export default function PurchaseReports() { const [selectedYear, setSelectedYear] = useState( yearPeriods && yearPeriods.length > 0 ? yearPeriods[yearPeriods.length - 1].year : 0, ); - const getBuyReportsDetailsParams: GetBuyReportsDetailsParams = { year: selectedYear }; + const [isPaidByFilterOpen, setIsPaidByFilterOpen] = useState(false); + const [selectedBureauId, setSelectedBureauId] = useState(null); + const [selectedPaidByUserId, setSelectedPaidByUserId] = useState( + undefined, + ); + const getBuyReportsDetailsParams: GetBuyReportsDetailsParams = { + year: selectedYear, + ...(selectedPaidByUserId != null ? { paid_by_user_id: selectedPaidByUserId } : {}), + }; const { data: buyReportsData, @@ -55,7 +74,10 @@ export default function PurchaseReports() { mutate: mutateBuyReportData, } = useGetBuyReportsDetails(getBuyReportsDetailsParams); const buyReports = useMemo(() => buyReportsData?.data ?? [], [buyReportsData]); - const getBuyReportsSummaryParams: GetBuyReportsSummaryParams = { year: selectedYear }; + const getBuyReportsSummaryParams: GetBuyReportsSummaryParams = { + year: selectedYear, + ...(selectedPaidByUserId != null ? { paid_by_user_id: selectedPaidByUserId } : {}), + }; const { data: buyReportsSummaryData, isLoading: isBuyReportsSummaryLoading, @@ -207,6 +229,21 @@ export default function PurchaseReports() { /> + {isPaidByFilterOpen && ( + setIsPaidByFilterOpen(false)} + onApply={({ bureauId, paidByUserId }) => { + setSelectedBureauId(bureauId); + setSelectedPaidByUserId(paidByUserId); + setIsPaidByFilterOpen(false); + }} + bureaus={BUREAUS} + users={users} + selectedBureauId={selectedBureauId} + selectedPaidByUserId={selectedPaidByUserId} + /> + )}
@@ -225,7 +262,17 @@ export default function PurchaseReports() { 物品
- 立替者 +
+ 立替者 + +
金額 From 3b6ec0a093f2c61c8c91e94b268a007e46ff9b7b Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Wed, 31 Dec 2025 16:52:07 +0900 Subject: [PATCH 05/47] [fix] Update button text in PurchaseReportPaidByFilterModal --- .../purchasereports/PurchaseReportPaidByFilterModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index a7a4f7ec8..e23ddc61c 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -107,7 +107,7 @@ const PurchaseReportPaidByFilterModal: FC
- 設定 + 絞り込み
); From 3c61495e3b8bc45f1c712c8a3d91060f50eb657b Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Wed, 31 Dec 2025 20:36:20 +0900 Subject: [PATCH 06/47] [fix] Update button text in PurchaseReportPaidByFilterModal to improve clarity --- .../purchasereports/PurchaseReportPaidByFilterModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index e23ddc61c..5149a5994 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -107,7 +107,7 @@ const PurchaseReportPaidByFilterModal: FC
- 絞り込み + 絞り込む
); From dff5edd9ad679d495997382e95fc1562e1ee6dee Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Mon, 29 Dec 2025 00:13:27 +0900 Subject: [PATCH 07/47] [add] Implement PurchaseReportSummaryAmounts component and integrate it into the purchase reports page --- .../PurchaseReportSummaryAmounts.tsx | 30 +++++++++++++++++++ .../src/components/purchasereports/index.ts | 1 + .../src/pages/purchase_report_list/index.tsx | 24 +++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx diff --git a/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx new file mode 100644 index 000000000..1e4bcddef --- /dev/null +++ b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +interface PurchaseReportSummaryAmountsProps { + unsettledAmountText: string; + unpackedAmountText: string; + className?: string; +} + +export default function PurchaseReportSummaryAmounts({ + unsettledAmountText, + unpackedAmountText, + className = 'text-sm text-black-600 md:ml-auto', +}: PurchaseReportSummaryAmountsProps) { + return ( +
+
+ 未清算金額 + + + {unsettledAmountText} 円 + + 未封詰め金額 + + + {unpackedAmountText} 円 + +
+
+ ); +} diff --git a/view/next-project/src/components/purchasereports/index.ts b/view/next-project/src/components/purchasereports/index.ts index eca7bd61e..ecd14bde4 100644 --- a/view/next-project/src/components/purchasereports/index.ts +++ b/view/next-project/src/components/purchasereports/index.ts @@ -11,5 +11,6 @@ export { default as PurchaseOrderListModal } from './PurchaseOrderListModal'; export { default as PurchaseReportAddModal } from './PurchaseReportAddModal'; export { default as PurchaseReportConfirmModal } from './PurchaseReportConfirmModal'; export { default as PurchaseReportItemNumModal } from './PurchaseReportItemNumModal'; // "PurchaseReport|temNumModal"を修正しました。 +export { default as PurchaseReportSummaryAmounts } from './PurchaseReportSummaryAmounts'; export { default as ReceiptModal } from './ReceiptModal'; export { default as CheckSettlementConfirmModal } from './CheckSettlementConfirmModal'; diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index e583e3e1c..6041293e0 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -7,8 +7,10 @@ import { useRecoilValue } from 'recoil'; import DownloadButton from '@/components/common/DownloadButton'; import PrimaryButton from '@/components/common/OutlinePrimaryButton/OutlinePrimaryButton'; import { OpenCheckSettlementModalButton } from '@/components/purchasereports'; +import PurchaseReportSummaryAmounts from '@/components/purchasereports/PurchaseReportSummaryAmounts'; import { useGetBuyReportsDetails, + useGetBuyReportsSummary, useGetYearsPeriods, usePutBuyReportStatusBuyReportId, } from '@/generated/hooks'; @@ -19,6 +21,7 @@ import OpenDeleteModalButton from '@components/purchasereports/OpenDeleteModalBu import type { GetBuyReportsDetailsParams, + GetBuyReportsSummaryParams, BuyReportDetail, PutBuyReportStatusBuyReportIdBody, } from '@/generated/model'; @@ -54,6 +57,14 @@ export default function PurchaseReports() { mutate: mutateBuyReportData, } = useGetBuyReportsDetails(getBuyReportsDetailsParams); const buyReports = useMemo(() => buyReportsData?.data ?? [], [buyReportsData]); + const getBuyReportsSummaryParams: GetBuyReportsSummaryParams = { year: selectedYear }; + const { + data: buyReportsSummaryData, + isLoading: isBuyReportsSummaryLoading, + error: buyReportsSummaryError, + } = useGetBuyReportsSummary(getBuyReportsSummaryParams, { + swr: { enabled: selectedYear > 0 }, + }); const [sealChecks, setSealChecks] = useState>({}); const [settlementChecks, setSettlementChecks] = useState>({}); @@ -108,6 +119,15 @@ export default function PurchaseReports() { const formatAmount = useCallback((amount: number) => { return amount.toLocaleString(); }, []); + const buyReportsSummary = buyReportsSummaryData?.data; + const summaryUnsettledAmount = + isBuyReportsSummaryLoading || buyReportsSummaryError || buyReportsSummary == null + ? '-' + : formatAmount(buyReportsSummary.unsettledAmount ?? 0); + const summaryUnpackedAmount = + isBuyReportsSummaryLoading || buyReportsSummaryError || buyReportsSummary == null + ? '-' + : formatAmount(buyReportsSummary.unpackedAmount ?? 0); const download = async (url: string, fileName: string) => { const downloadPath = `${process.env.NEXT_PUBLIC_MINIO_ENDPONT}/finansu/${url}`; @@ -183,6 +203,10 @@ export default function PurchaseReports() { CSVダウンロード +
From 72f01f11b811283a2fcf2cd276c79aa2e34a19e8 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Mon, 29 Dec 2025 00:18:31 +0900 Subject: [PATCH 08/47] [otr] Simplify JSX formatting in PurchaseReportSummaryAmounts component --- .../purchasereports/PurchaseReportSummaryAmounts.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx index 1e4bcddef..9c1475e9a 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx @@ -16,14 +16,10 @@ export default function PurchaseReportSummaryAmounts({
未清算金額 - - {unsettledAmountText} 円 - + {unsettledAmountText} 円 未封詰め金額 - - {unpackedAmountText} 円 - + {unpackedAmountText} 円
); From 95d654658a5401256db7532626d0f7acfad6d100 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Wed, 31 Dec 2025 16:32:35 +0900 Subject: [PATCH 09/47] [add] PurchaseReportPaidByFilterModal component and integrate it into the purchase reports page --- .../PurchaseReportPaidByFilterModal.tsx | 116 ++++++++++++++++++ .../src/pages/purchase_report_list/index.tsx | 56 ++++++++- 2 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx new file mode 100644 index 000000000..a7a4f7ec8 --- /dev/null +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -0,0 +1,116 @@ +import React, { FC, useEffect, useMemo, useState } from 'react'; + +import { CloseButton, Modal, OutlinePrimaryButton, Select } from '@components/common'; +import { Bureau, User } from '@type/common'; + +interface PurchaseReportPaidByFilterModalProps { + isOpen: boolean; + onClose: () => void; + onApply: (selection: { + bureauId: number | null; + paidByUserId: number | null | undefined; + }) => void; + bureaus: Bureau[]; + users: User[]; + selectedBureauId: number | null; + selectedPaidByUserId: number | null | undefined; +} + +const PurchaseReportPaidByFilterModal: FC = (props) => { + const { isOpen, onClose, onApply, bureaus, users, selectedBureauId, selectedPaidByUserId } = + props; + + const [draftBureauId, setDraftBureauId] = useState(selectedBureauId); + const [draftPaidByUserId, setDraftPaidByUserId] = useState( + selectedPaidByUserId, + ); + + useEffect(() => { + if (!isOpen) return; + setDraftBureauId(selectedBureauId); + setDraftPaidByUserId(selectedPaidByUserId); + }, [isOpen, selectedBureauId, selectedPaidByUserId]); + + const bureauNameMap = useMemo( + () => + new Map( + bureaus.map((bureau) => [bureau.id ?? 0, bureau.name] as const).filter(([id]) => id > 0), + ), + [bureaus], + ); + + const filteredUsers = useMemo(() => { + if (!draftBureauId) return users; + return users.filter((user) => user.bureauID === draftBureauId); + }, [draftBureauId, users]); + + const paidBySelectValue = draftPaidByUserId === null ? 'none' : draftPaidByUserId ?? ''; + + const handleBureauChange = (event: React.ChangeEvent) => { + const value = event.target.value; + const nextBureauId = value === '' ? null : Number(value); + setDraftBureauId(nextBureauId); + setDraftPaidByUserId(undefined); + }; + + const handlePaidByChange = (event: React.ChangeEvent) => { + const value = event.target.value; + if (value === '') { + setDraftPaidByUserId(undefined); + return; + } + if (value === 'none') { + setDraftPaidByUserId(null); + return; + } + setDraftPaidByUserId(Number(value)); + }; + + const handleApply = () => { + onApply({ + bureauId: draftBureauId ?? null, + paidByUserId: draftPaidByUserId, + }); + }; + + return ( + +
+ +
+
+
+

局名

+ +
+
+

氏名

+ + {/* NOTE: paid_by_user_id の NULL を絞り込む仕様が未定義のため「立替者なし」は未実装。 */} +
+
+
+ 設定 +
+
+ ); +}; + +export default PurchaseReportPaidByFilterModal; diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index 6041293e0..5123beaf2 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -1,16 +1,20 @@ import { saveAs } from 'file-saver'; import { useRouter } from 'next/router'; -import { useCallback, useState, useEffect, useMemo } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { RiArrowDropDownLine } from 'react-icons/ri'; import { TbDownload } from 'react-icons/tb'; import { useRecoilValue } from 'recoil'; import DownloadButton from '@/components/common/DownloadButton'; import PrimaryButton from '@/components/common/OutlinePrimaryButton/OutlinePrimaryButton'; import { OpenCheckSettlementModalButton } from '@/components/purchasereports'; +import PurchaseReportPaidByFilterModal from '@/components/purchasereports/PurchaseReportPaidByFilterModal'; import PurchaseReportSummaryAmounts from '@/components/purchasereports/PurchaseReportSummaryAmounts'; +import { BUREAUS } from '@/constants/bureaus'; import { useGetBuyReportsDetails, useGetBuyReportsSummary, + useGetUsers, useGetYearsPeriods, usePutBuyReportStatusBuyReportId, } from '@/generated/hooks'; @@ -20,9 +24,9 @@ import MainLayout from '@components/layout/MainLayout'; import OpenDeleteModalButton from '@components/purchasereports/OpenDeleteModalButton'; import type { + BuyReportDetail, GetBuyReportsDetailsParams, GetBuyReportsSummaryParams, - BuyReportDetail, PutBuyReportStatusBuyReportIdBody, } from '@/generated/model'; @@ -35,6 +39,12 @@ export default function PurchaseReports() { } = useGetYearsPeriods(); const yearPeriods = yearPeriodsData?.data; const user = useRecoilValue(userAtom); + const { data: usersResponse } = useGetUsers(); + const users = useMemo(() => { + const responseData = usersResponse?.data as User[] | { data?: User[] } | undefined; + if (Array.isArray(responseData)) return responseData; + return responseData?.data ?? []; + }, [usersResponse]); user?.roleID === 1 && router.push('/my_page'); @@ -48,7 +58,15 @@ export default function PurchaseReports() { const [selectedYear, setSelectedYear] = useState( yearPeriods && yearPeriods.length > 0 ? yearPeriods[yearPeriods.length - 1].year : 0, ); - const getBuyReportsDetailsParams: GetBuyReportsDetailsParams = { year: selectedYear }; + const [isPaidByFilterOpen, setIsPaidByFilterOpen] = useState(false); + const [selectedBureauId, setSelectedBureauId] = useState(null); + const [selectedPaidByUserId, setSelectedPaidByUserId] = useState( + undefined, + ); + const getBuyReportsDetailsParams: GetBuyReportsDetailsParams = { + year: selectedYear, + ...(selectedPaidByUserId != null ? { paid_by_user_id: selectedPaidByUserId } : {}), + }; const { data: buyReportsData, @@ -57,7 +75,10 @@ export default function PurchaseReports() { mutate: mutateBuyReportData, } = useGetBuyReportsDetails(getBuyReportsDetailsParams); const buyReports = useMemo(() => buyReportsData?.data ?? [], [buyReportsData]); - const getBuyReportsSummaryParams: GetBuyReportsSummaryParams = { year: selectedYear }; + const getBuyReportsSummaryParams: GetBuyReportsSummaryParams = { + year: selectedYear, + ...(selectedPaidByUserId != null ? { paid_by_user_id: selectedPaidByUserId } : {}), + }; const { data: buyReportsSummaryData, isLoading: isBuyReportsSummaryLoading, @@ -209,6 +230,21 @@ export default function PurchaseReports() { /> + {isPaidByFilterOpen && ( + setIsPaidByFilterOpen(false)} + onApply={({ bureauId, paidByUserId }) => { + setSelectedBureauId(bureauId); + setSelectedPaidByUserId(paidByUserId); + setIsPaidByFilterOpen(false); + }} + bureaus={BUREAUS} + users={users} + selectedBureauId={selectedBureauId} + selectedPaidByUserId={selectedPaidByUserId} + /> + )}
@@ -227,7 +263,17 @@ export default function PurchaseReports() { 物品 -
- 立替者 +
+ 立替者 + +
金額 From 2c7c7b76ccf9a0121c97a6dec2dd4fc9ab3c12a1 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Wed, 31 Dec 2025 16:52:07 +0900 Subject: [PATCH 10/47] [fix] Update button text in PurchaseReportPaidByFilterModal --- .../purchasereports/PurchaseReportPaidByFilterModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index a7a4f7ec8..e23ddc61c 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -107,7 +107,7 @@ const PurchaseReportPaidByFilterModal: FC
- 設定 + 絞り込み
); From 2894b013844c384d1cdf2785408f3cc52128044d Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Wed, 31 Dec 2025 20:36:20 +0900 Subject: [PATCH 11/47] [fix] Update button text in PurchaseReportPaidByFilterModal to improve clarity --- .../purchasereports/PurchaseReportPaidByFilterModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index e23ddc61c..5149a5994 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -107,7 +107,7 @@ const PurchaseReportPaidByFilterModal: FC
- 絞り込み + 絞り込む
); From d88ee44bfd86319e07a083787471346637e22a2a Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Sat, 24 Jan 2026 22:14:19 +0900 Subject: [PATCH 12/47] =?UTF-8?q?[add]=20Add=20"=E7=B5=9E=E3=82=8A?= =?UTF-8?q?=E8=BE=BC=E3=81=BF=E3=81=AA=E3=81=97"=20option=20to=20bureau=20?= =?UTF-8?q?selection=20in=20PurchaseReportPaidByFilterModal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../purchasereports/PurchaseReportPaidByFilterModal.tsx | 1 + view/next-project/src/pages/purchase_report_list/index.tsx | 3 +++ 2 files changed, 4 insertions(+) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index 5149a5994..be734583c 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -82,6 +82,7 @@ const PurchaseReportPaidByFilterModal: FC

局名

- +

局名

+
-

氏名

- + {filteredUsers.map((user) => { const bureauName = bureauNameMap.get(user.bureauID); const label = draftBureauId || !bureauName ? user.name : `${bureauName} ${user.name}`; return ( - ); diff --git a/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx index b6e4dbe41..c807d76ff 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx @@ -1,5 +1,3 @@ -import React from 'react'; - interface PurchaseReportSummaryAmountsProps { unsettledAmountText: string; unpackedAmountText: string; @@ -9,17 +7,24 @@ interface PurchaseReportSummaryAmountsProps { export default function PurchaseReportSummaryAmounts({ unsettledAmountText, unpackedAmountText, - className = 'text-sm text-black-600 md:ml-auto', + className = '', }: PurchaseReportSummaryAmountsProps) { return (
-
+
未清算金額 - {unsettledAmountText} 円 + + {unsettledAmountText} + {'\u00A0\u00A0'}円 + + 未封詰め金額 - {unpackedAmountText} 円 + + {unpackedAmountText} + {'\u00A0\u00A0'}円 +
); From b7cda1c9b1461ab80f5f674c3081d3d41113ece8 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Sat, 31 Jan 2026 22:00:29 +0900 Subject: [PATCH 16/47] [fix] Simplify user filtering logic and improve type checking in PurchaseReports component --- .../PurchaseReportPaidByFilterModal.tsx | 22 ++++++------------- .../src/pages/purchase_report_list/index.tsx | 19 +++++++++++----- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index 3fb06609f..d57dfa9c8 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -21,14 +21,14 @@ const PurchaseReportPaidByFilterModal: FC props; const [draftBureauId, setDraftBureauId] = useState(selectedBureauId); - const [draftPaidByUserId, setDraftPaidByUserId] = useState( - selectedPaidByUserId, + const [draftPaidByUserId, setDraftPaidByUserId] = useState( + selectedPaidByUserId ?? null, ); useEffect(() => { if (!isOpen) return; setDraftBureauId(selectedBureauId); - setDraftPaidByUserId(selectedPaidByUserId); + setDraftPaidByUserId(selectedPaidByUserId ?? null); }, [isOpen, selectedBureauId, selectedPaidByUserId]); const labelClassName = 'mb-2 text-sm text-black-600 [font-family:"Noto_Sans_JP"]'; @@ -48,26 +48,18 @@ const PurchaseReportPaidByFilterModal: FC return users.filter((user) => user.bureauID === draftBureauId); }, [draftBureauId, users]); - const paidBySelectValue = draftPaidByUserId === null ? 'none' : draftPaidByUserId ?? ''; + const paidBySelectValue = draftPaidByUserId ?? ''; const handleBureauChange = (event: React.ChangeEvent) => { const value = event.target.value; const nextBureauId = value === '' ? null : Number(value); setDraftBureauId(nextBureauId); - setDraftPaidByUserId(undefined); + setDraftPaidByUserId(null); }; const handlePaidByChange = (event: React.ChangeEvent) => { const value = event.target.value; - if (value === '') { - setDraftPaidByUserId(undefined); - return; - } - if (value === 'none') { - setDraftPaidByUserId(null); - return; - } - setDraftPaidByUserId(Number(value)); + setDraftPaidByUserId(value === '' ? null : Number(value)); }; const handleApply = () => { @@ -107,7 +99,7 @@ const PurchaseReportPaidByFilterModal: FC value={paidBySelectValue} onChange={handlePaidByChange} > - {filteredUsers.map((user) => { diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index 535b8796c..e6b62e561 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -43,17 +43,24 @@ export default function PurchaseReports() { const user = useRecoilValue(userAtom); const { data: usersResponse } = useGetUsers(); + const isUser = (value: unknown): value is User => { + if (!value || typeof value !== 'object') return false; + const candidate = value as Partial; + return typeof candidate.id === 'number' && typeof candidate.name === 'string'; + }; const users = useMemo(() => { - const responseData = usersResponse?.data as User[] | { data?: User[] } | undefined; - if (Array.isArray(responseData)) return responseData; - return responseData?.data ?? []; + const responseData: unknown = usersResponse?.data; + if (Array.isArray(responseData)) return responseData.filter(isUser); + if (responseData && typeof responseData === 'object') { + const nested = (responseData as { data?: unknown }).data; + if (Array.isArray(nested)) return nested.filter(isUser); + } + return []; }, [usersResponse]); user?.roleID === 1 && router.push('/my_page'); - const [selectedYear, setSelectedYear] = useState( - yearPeriods && yearPeriods.length > 0 ? yearPeriods[yearPeriods.length - 1].year : 0, - ); + const [selectedYear, setSelectedYear] = useState(0); useEffect(() => { if (yearPeriods && yearPeriods.length > 0) { From 9dbe13bb306838c294c568396f792376d3fa6912 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Thu, 5 Feb 2026 02:21:21 +0900 Subject: [PATCH 17/47] [fix] Update PurchaseReportPaidByFilterModal to use paidBy instead of paidByUserId --- .../PurchaseReportPaidByFilterModal.tsx | 26 ++++++++----------- .../src/pages/purchase_report_list/index.tsx | 14 +++++----- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index d57dfa9c8..71f74cf6e 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -8,28 +8,25 @@ interface PurchaseReportPaidByFilterModalProps { onClose: () => void; onApply: (selection: { bureauId: number | null; - paidByUserId: number | null | undefined; + paidBy: string | null | undefined; }) => void; bureaus: Bureau[]; users: User[]; selectedBureauId: number | null; - selectedPaidByUserId: number | null | undefined; + selectedPaidBy: string | null | undefined; } const PurchaseReportPaidByFilterModal: FC = (props) => { - const { isOpen, onClose, onApply, bureaus, users, selectedBureauId, selectedPaidByUserId } = - props; + const { isOpen, onClose, onApply, bureaus, users, selectedBureauId, selectedPaidBy } = props; const [draftBureauId, setDraftBureauId] = useState(selectedBureauId); - const [draftPaidByUserId, setDraftPaidByUserId] = useState( - selectedPaidByUserId ?? null, - ); + const [draftPaidBy, setDraftPaidBy] = useState(selectedPaidBy ?? null); useEffect(() => { if (!isOpen) return; setDraftBureauId(selectedBureauId); - setDraftPaidByUserId(selectedPaidByUserId ?? null); - }, [isOpen, selectedBureauId, selectedPaidByUserId]); + setDraftPaidBy(selectedPaidBy ?? null); + }, [isOpen, selectedBureauId, selectedPaidBy]); const labelClassName = 'mb-2 text-sm text-black-600 [font-family:"Noto_Sans_JP"]'; const selectTextClassName = 'text-black-600 [font-family:"Noto_Sans_JP"]'; @@ -48,24 +45,24 @@ const PurchaseReportPaidByFilterModal: FC return users.filter((user) => user.bureauID === draftBureauId); }, [draftBureauId, users]); - const paidBySelectValue = draftPaidByUserId ?? ''; + const paidBySelectValue = draftPaidBy ?? ''; const handleBureauChange = (event: React.ChangeEvent) => { const value = event.target.value; const nextBureauId = value === '' ? null : Number(value); setDraftBureauId(nextBureauId); - setDraftPaidByUserId(null); + setDraftPaidBy(null); }; const handlePaidByChange = (event: React.ChangeEvent) => { const value = event.target.value; - setDraftPaidByUserId(value === '' ? null : Number(value)); + setDraftPaidBy(value === '' ? null : value); }; const handleApply = () => { onApply({ bureauId: draftBureauId ?? null, - paidByUserId: draftPaidByUserId, + paidBy: draftPaidBy, }); }; @@ -106,13 +103,12 @@ const PurchaseReportPaidByFilterModal: FC const bureauName = bureauNameMap.get(user.bureauID); const label = draftBureauId || !bureauName ? user.name : `${bureauName} ${user.name}`; return ( - ); })} - {/* NOTE: paid_by_user_id の NULL を絞り込む仕様が未定義のため「立替者なし」は未実装。 */}
diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index e6b62e561..2c39c4d65 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -71,14 +71,12 @@ export default function PurchaseReports() { const [isPaidByFilterOpen, setIsPaidByFilterOpen] = useState(false); const [selectedBureauId, setSelectedBureauId] = useState(null); - const [selectedPaidByUserId, setSelectedPaidByUserId] = useState( - undefined, - ); + const [selectedPaidBy, setSelectedPaidBy] = useState(undefined); const getBuyReportsDetailsParams: GetBuyReportsDetailsParams = { year: selectedYear, ...(selectedBureauId != null ? { financial_record_id: selectedBureauId } : {}), - ...(selectedPaidByUserId != null ? { paid_by_user_id: selectedPaidByUserId } : {}), + ...(selectedPaidBy != null ? { paid_by: selectedPaidBy } : {}), }; const { @@ -93,7 +91,7 @@ export default function PurchaseReports() { const getBuyReportsSummaryParams: GetBuyReportsSummaryParams = { year: selectedYear, ...(selectedBureauId != null ? { financial_record_id: selectedBureauId } : {}), - ...(selectedPaidByUserId != null ? { paid_by_user_id: selectedPaidByUserId } : {}), + ...(selectedPaidBy != null ? { paid_by: selectedPaidBy } : {}), }; const { @@ -255,15 +253,15 @@ export default function PurchaseReports() { setIsPaidByFilterOpen(false)} - onApply={({ bureauId, paidByUserId }) => { + onApply={({ bureauId, paidBy }) => { setSelectedBureauId(bureauId); - setSelectedPaidByUserId(paidByUserId); + setSelectedPaidBy(paidBy); setIsPaidByFilterOpen(false); }} bureaus={BUREAUS} users={users} selectedBureauId={selectedBureauId} - selectedPaidByUserId={selectedPaidByUserId} + selectedPaidBy={selectedPaidBy} /> )} From 566c3a9d2efe5dcdc607d659e3244e63e5157265 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Fri, 13 Feb 2026 02:46:26 +0900 Subject: [PATCH 18/47] [fix] Enhance PurchaseReportPaidByFilterModal and PurchaseReports to support paidByUserId filtering --- .../PurchaseReportPaidByFilterModal.tsx | 63 ++++++++++++++++--- .../src/pages/purchase_report_list/index.tsx | 15 ++++- .../src/utils/purchaseReportFilters.ts | 34 ++++++++++ 3 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 view/next-project/src/utils/purchaseReportFilters.ts diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index 71f74cf6e..5367ec749 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -2,31 +2,47 @@ import React, { FC, useEffect, useMemo, useState } from 'react'; import { CloseButton, Modal, OutlinePrimaryButton, Select } from '@components/common'; import { Bureau, User } from '@type/common'; +import { normalizePaidBy } from '@/utils/purchaseReportFilters'; interface PurchaseReportPaidByFilterModalProps { isOpen: boolean; onClose: () => void; onApply: (selection: { bureauId: number | null; + paidByUserId: number | null; paidBy: string | null | undefined; }) => void; bureaus: Bureau[]; users: User[]; selectedBureauId: number | null; + selectedPaidByUserId: number | null; selectedPaidBy: string | null | undefined; } const PurchaseReportPaidByFilterModal: FC = (props) => { - const { isOpen, onClose, onApply, bureaus, users, selectedBureauId, selectedPaidBy } = props; + const { + isOpen, + onClose, + onApply, + bureaus, + users, + selectedBureauId, + selectedPaidByUserId, + selectedPaidBy, + } = props; const [draftBureauId, setDraftBureauId] = useState(selectedBureauId); - const [draftPaidBy, setDraftPaidBy] = useState(selectedPaidBy ?? null); + const [draftPaidByUserId, setDraftPaidByUserId] = useState( + selectedPaidByUserId ?? null, + ); + const [draftPaidBy, setDraftPaidBy] = useState(normalizePaidBy(selectedPaidBy)); useEffect(() => { if (!isOpen) return; setDraftBureauId(selectedBureauId); - setDraftPaidBy(selectedPaidBy ?? null); - }, [isOpen, selectedBureauId, selectedPaidBy]); + setDraftPaidByUserId(selectedPaidByUserId ?? null); + setDraftPaidBy(normalizePaidBy(selectedPaidBy)); + }, [isOpen, selectedBureauId, selectedPaidBy, selectedPaidByUserId]); const labelClassName = 'mb-2 text-sm text-black-600 [font-family:"Noto_Sans_JP"]'; const selectTextClassName = 'text-black-600 [font-family:"Noto_Sans_JP"]'; @@ -45,24 +61,50 @@ const PurchaseReportPaidByFilterModal: FC return users.filter((user) => user.bureauID === draftBureauId); }, [draftBureauId, users]); - const paidBySelectValue = draftPaidBy ?? ''; + const legacyPaidBy = draftPaidByUserId == null ? draftPaidBy : null; + const legacyPaidByValue = legacyPaidBy ? `legacy:${legacyPaidBy}` : ''; + const paidBySelectValue = + draftPaidByUserId != null ? String(draftPaidByUserId) : legacyPaidByValue; const handleBureauChange = (event: React.ChangeEvent) => { const value = event.target.value; const nextBureauId = value === '' ? null : Number(value); setDraftBureauId(nextBureauId); + setDraftPaidByUserId(null); setDraftPaidBy(null); }; const handlePaidByChange = (event: React.ChangeEvent) => { const value = event.target.value; - setDraftPaidBy(value === '' ? null : value); + if (value === '') { + setDraftPaidByUserId(null); + setDraftPaidBy(null); + return; + } + + if (value.startsWith('legacy:')) { + setDraftPaidByUserId(null); + setDraftPaidBy(normalizePaidBy(value.replace('legacy:', ''))); + return; + } + + const nextUserId = Number(value); + if (!Number.isFinite(nextUserId) || nextUserId <= 0) { + setDraftPaidByUserId(null); + setDraftPaidBy(null); + return; + } + + const selectedUser = users.find((user) => user.id === nextUserId); + setDraftPaidByUserId(nextUserId); + setDraftPaidBy(normalizePaidBy(selectedUser?.name ?? null)); }; const handleApply = () => { onApply({ bureauId: draftBureauId ?? null, - paidBy: draftPaidBy, + paidByUserId: draftPaidByUserId ?? null, + paidBy: normalizePaidBy(draftPaidBy), }); }; @@ -99,11 +141,16 @@ const PurchaseReportPaidByFilterModal: FC + {legacyPaidBy && ( + + )} {filteredUsers.map((user) => { const bureauName = bureauNameMap.get(user.bureauID); const label = draftBureauId || !bureauName ? user.name : `${bureauName} ${user.name}`; return ( - ); diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index 2c39c4d65..bf3426639 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -19,6 +19,7 @@ import { usePutBuyReportStatusBuyReportId, } from '@/generated/hooks'; import { userAtom } from '@/store/atoms'; +import { buildPaidByFilterParams } from '@/utils/purchaseReportFilters'; import { Card, Checkbox, EditButton, Loading, Title } from '@components/common'; import MainLayout from '@components/layout/MainLayout'; import OpenDeleteModalButton from '@components/purchasereports/OpenDeleteModalButton'; @@ -72,11 +73,17 @@ export default function PurchaseReports() { const [isPaidByFilterOpen, setIsPaidByFilterOpen] = useState(false); const [selectedBureauId, setSelectedBureauId] = useState(null); const [selectedPaidBy, setSelectedPaidBy] = useState(undefined); + const [selectedPaidByUserId, setSelectedPaidByUserId] = useState(null); + + const paidByFilterParams = buildPaidByFilterParams({ + paidByUserId: selectedPaidByUserId, + paidBy: selectedPaidBy, + }); const getBuyReportsDetailsParams: GetBuyReportsDetailsParams = { year: selectedYear, ...(selectedBureauId != null ? { financial_record_id: selectedBureauId } : {}), - ...(selectedPaidBy != null ? { paid_by: selectedPaidBy } : {}), + ...paidByFilterParams, }; const { @@ -91,7 +98,7 @@ export default function PurchaseReports() { const getBuyReportsSummaryParams: GetBuyReportsSummaryParams = { year: selectedYear, ...(selectedBureauId != null ? { financial_record_id: selectedBureauId } : {}), - ...(selectedPaidBy != null ? { paid_by: selectedPaidBy } : {}), + ...paidByFilterParams, }; const { @@ -253,14 +260,16 @@ export default function PurchaseReports() { setIsPaidByFilterOpen(false)} - onApply={({ bureauId, paidBy }) => { + onApply={({ bureauId, paidByUserId, paidBy }) => { setSelectedBureauId(bureauId); + setSelectedPaidByUserId(paidByUserId ?? null); setSelectedPaidBy(paidBy); setIsPaidByFilterOpen(false); }} bureaus={BUREAUS} users={users} selectedBureauId={selectedBureauId} + selectedPaidByUserId={selectedPaidByUserId} selectedPaidBy={selectedPaidBy} /> )} diff --git a/view/next-project/src/utils/purchaseReportFilters.ts b/view/next-project/src/utils/purchaseReportFilters.ts new file mode 100644 index 000000000..f17286dd3 --- /dev/null +++ b/view/next-project/src/utils/purchaseReportFilters.ts @@ -0,0 +1,34 @@ +export type PaidByFilterInput = { + paidByUserId?: number | null; + paidBy?: string | null | undefined; +}; + +export type PaidByFilterParams = { + paid_by_user_id?: number; + paid_by?: string; +}; + +export const normalizePaidBy = (value: string | null | undefined): string | null => { + if (typeof value !== 'string') return null; + const trimmed = value.trim(); + return trimmed.length > 0 ? trimmed : null; +}; + +const normalizePaidByUserId = (value: number | null | undefined): number | null => { + if (typeof value !== 'number' || Number.isNaN(value) || value <= 0) return null; + return value; +}; + +export const buildPaidByFilterParams = (input: PaidByFilterInput): PaidByFilterParams => { + const paidByUserId = normalizePaidByUserId(input.paidByUserId); + if (paidByUserId != null) { + return { paid_by_user_id: paidByUserId }; + } + + const paidBy = normalizePaidBy(input.paidBy); + if (paidBy != null) { + return { paid_by: paidBy }; + } + + return {}; +}; From bf65e4fa8ff2e465fa2f47c8b6e0f4a1d4575bb1 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Tue, 12 May 2026 19:10:23 +0900 Subject: [PATCH 19/47] [add] Implement search functionality and dropdown for paidBy selection in PurchaseReportPaidByFilterModal --- .../PurchaseReportPaidByFilterModal.tsx | 156 +++++++++++------- .../src/pages/purchase_report_list/index.tsx | 14 +- 2 files changed, 106 insertions(+), 64 deletions(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index 5367ec749..f15df15d7 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -1,11 +1,12 @@ -import React, { FC, useEffect, useMemo, useState } from 'react'; +import React, { FC, useMemo, useState } from 'react'; +import { RiSearchLine } from 'react-icons/ri'; +import { normalizePaidBy } from '@/utils/purchaseReportFilters'; import { CloseButton, Modal, OutlinePrimaryButton, Select } from '@components/common'; +import s from '@components/common/Select/Select.module.css'; import { Bureau, User } from '@type/common'; -import { normalizePaidBy } from '@/utils/purchaseReportFilters'; interface PurchaseReportPaidByFilterModalProps { - isOpen: boolean; onClose: () => void; onApply: (selection: { bureauId: number | null; @@ -14,6 +15,7 @@ interface PurchaseReportPaidByFilterModalProps { }) => void; bureaus: Bureau[]; users: User[]; + legacyPaidByOptions: string[]; selectedBureauId: number | null; selectedPaidByUserId: number | null; selectedPaidBy: string | null | undefined; @@ -21,11 +23,11 @@ interface PurchaseReportPaidByFilterModalProps { const PurchaseReportPaidByFilterModal: FC = (props) => { const { - isOpen, onClose, onApply, bureaus, users, + legacyPaidByOptions, selectedBureauId, selectedPaidByUserId, selectedPaidBy, @@ -36,13 +38,8 @@ const PurchaseReportPaidByFilterModal: FC selectedPaidByUserId ?? null, ); const [draftPaidBy, setDraftPaidBy] = useState(normalizePaidBy(selectedPaidBy)); - - useEffect(() => { - if (!isOpen) return; - setDraftBureauId(selectedBureauId); - setDraftPaidByUserId(selectedPaidByUserId ?? null); - setDraftPaidBy(normalizePaidBy(selectedPaidBy)); - }, [isOpen, selectedBureauId, selectedPaidBy, selectedPaidByUserId]); + const [searchQuery, setSearchQuery] = useState(''); + const [isNameDropdownOpen, setIsNameDropdownOpen] = useState(false); const labelClassName = 'mb-2 text-sm text-black-600 [font-family:"Noto_Sans_JP"]'; const selectTextClassName = 'text-black-600 [font-family:"Noto_Sans_JP"]'; @@ -61,10 +58,26 @@ const PurchaseReportPaidByFilterModal: FC return users.filter((user) => user.bureauID === draftBureauId); }, [draftBureauId, users]); - const legacyPaidBy = draftPaidByUserId == null ? draftPaidBy : null; - const legacyPaidByValue = legacyPaidBy ? `legacy:${legacyPaidBy}` : ''; - const paidBySelectValue = - draftPaidByUserId != null ? String(draftPaidByUserId) : legacyPaidByValue; + const filteredOptions = useMemo(() => { + const q = searchQuery.toLowerCase(); + const legacy = legacyPaidByOptions + .filter((name) => name.toLowerCase().includes(q)) + .map((name) => ({ label: name, userId: null as number | null, paidBy: name })); + const userOpts = filteredUsers + .filter((u) => { + const bureauName = bureauNameMap.get(u.bureauID) ?? ''; + return `${bureauName} ${u.name}`.toLowerCase().includes(q); + }) + .map((u) => { + const bureauName = bureauNameMap.get(u.bureauID); + return { + label: draftBureauId || !bureauName ? u.name : `${bureauName} ${u.name}`, + userId: u.id, + paidBy: u.name, + }; + }); + return [...legacy, ...userOpts]; + }, [searchQuery, filteredUsers, legacyPaidByOptions, bureauNameMap, draftBureauId]); const handleBureauChange = (event: React.ChangeEvent) => { const value = event.target.value; @@ -72,32 +85,13 @@ const PurchaseReportPaidByFilterModal: FC setDraftBureauId(nextBureauId); setDraftPaidByUserId(null); setDraftPaidBy(null); + setSearchQuery(''); }; - const handlePaidByChange = (event: React.ChangeEvent) => { - const value = event.target.value; - if (value === '') { - setDraftPaidByUserId(null); - setDraftPaidBy(null); - return; - } - - if (value.startsWith('legacy:')) { - setDraftPaidByUserId(null); - setDraftPaidBy(normalizePaidBy(value.replace('legacy:', ''))); - return; - } - - const nextUserId = Number(value); - if (!Number.isFinite(nextUserId) || nextUserId <= 0) { - setDraftPaidByUserId(null); - setDraftPaidBy(null); - return; - } - - const selectedUser = users.find((user) => user.id === nextUserId); - setDraftPaidByUserId(nextUserId); - setDraftPaidBy(normalizePaidBy(selectedUser?.name ?? null)); + const handleSelectOption = (userId: number | null, paidBy: string | null) => { + setDraftPaidByUserId(userId); + setDraftPaidBy(normalizePaidBy(paidBy)); + setIsNameDropdownOpen(false); }; const handleApply = () => { @@ -108,6 +102,9 @@ const PurchaseReportPaidByFilterModal: FC }); }; + const isSelected = (userId: number | null, paidBy: string) => + userId != null ? draftPaidByUserId === userId : draftPaidBy === paidBy; + return (
@@ -133,29 +130,64 @@ const PurchaseReportPaidByFilterModal: FC

氏名

- +
+ +
+ {isNameDropdownOpen &&
+
+
+ + setSearchQuery(e.target.value)} + className='w-full bg-black-300 text-sm text-white-0 outline-none placeholder:text-black-900 [font-family:"Noto_Sans_JP"]' + /> +
+
+
    +
  • + +
  • + {filteredOptions.map((opt) => ( +
  • + +
  • + ))} + {filteredOptions.length === 0 && ( +
  • + 該当なし +
  • + )} +
+
}
diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index bf3426639..e82311ca4 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -95,6 +95,14 @@ export default function PurchaseReports() { const buyReports = useMemo(() => buyReportsData?.data ?? [], [buyReportsData]); + const legacyPaidByOptions = useMemo(() => { + const userNames = new Set(users.map((u) => u.name)); + const seen = new Set(); + return buyReports + .map((r) => r.paidBy) + .filter((name): name is string => !!name && !userNames.has(name) && !seen.has(name) && !!seen.add(name)); + }, [buyReports, users]); + const getBuyReportsSummaryParams: GetBuyReportsSummaryParams = { year: selectedYear, ...(selectedBureauId != null ? { financial_record_id: selectedBureauId } : {}), @@ -105,6 +113,7 @@ export default function PurchaseReports() { data: buyReportsSummaryData, isLoading: isBuyReportsSummaryLoading, error: buyReportsSummaryError, + mutate: mutateBuyReportsSummary, } = useGetBuyReportsSummary(getBuyReportsSummaryParams, { swr: { enabled: selectedYear > 0 }, }); @@ -206,7 +215,8 @@ export default function PurchaseReports() { const onSuccess = useCallback(() => { mutateBuyReportData(); - }, [mutateBuyReportData]); + mutateBuyReportsSummary(); + }, [mutateBuyReportData, mutateBuyReportsSummary]); const downloadCSV = async () => { const url = `${process.env.CSR_API_URI}/buy_reports/csv/download?year=${selectedYear}`; @@ -258,7 +268,6 @@ export default function PurchaseReports() { {isPaidByFilterOpen && ( setIsPaidByFilterOpen(false)} onApply={({ bureauId, paidByUserId, paidBy }) => { setSelectedBureauId(bureauId); @@ -268,6 +277,7 @@ export default function PurchaseReports() { }} bureaus={BUREAUS} users={users} + legacyPaidByOptions={legacyPaidByOptions} selectedBureauId={selectedBureauId} selectedPaidByUserId={selectedPaidByUserId} selectedPaidBy={selectedPaidBy} From 11ed2266ea5370db76f876f809116533ca530b22 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Tue, 12 May 2026 19:11:26 +0900 Subject: [PATCH 20/47] [fix] Refactor filtering logic in PurchaseReports for improved readability --- .../PurchaseReportPaidByFilterModal.tsx | 80 ++++++++++--------- .../src/pages/purchase_report_list/index.tsx | 5 +- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index f15df15d7..7fdc2882b 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -139,55 +139,57 @@ const PurchaseReportPaidByFilterModal: FC {draftPaidBy ?? '絞り込みなし'}
- {isNameDropdownOpen &&
-
-
- - setSearchQuery(e.target.value)} - className='w-full bg-black-300 text-sm text-white-0 outline-none placeholder:text-black-900 [font-family:"Noto_Sans_JP"]' - /> + {isNameDropdownOpen && ( +
+
+
+ + setSearchQuery(e.target.value)} + className='w-full bg-black-300 text-sm text-white-0 outline-none placeholder:text-black-900 [font-family:"Noto_Sans_JP"]' + /> +
-
-
    -
  • - -
  • - {filteredOptions.map((opt) => ( -
  • +
      +
    • - ))} - {filteredOptions.length === 0 && ( -
    • - 該当なし -
    • - )} -
    -
} + {filteredOptions.map((opt) => ( +
  • + +
  • + ))} + {filteredOptions.length === 0 && ( +
  • + 該当なし +
  • + )} + +
    + )}
    diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index e82311ca4..34d0bd87d 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -100,7 +100,10 @@ export default function PurchaseReports() { const seen = new Set(); return buyReports .map((r) => r.paidBy) - .filter((name): name is string => !!name && !userNames.has(name) && !seen.has(name) && !!seen.add(name)); + .filter( + (name): name is string => + !!name && !userNames.has(name) && !seen.has(name) && !!seen.add(name), + ); }, [buyReports, users]); const getBuyReportsSummaryParams: GetBuyReportsSummaryParams = { From d632646d24b1d870ba627fee04862d0aa8f5c081 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Tue, 12 May 2026 22:11:38 +0900 Subject: [PATCH 21/47] [add] Create styles for name selection in PurchaseReportPaidByFilterModal and update component to use new styles --- ...PurchaseReportPaidByFilterModal.module.css | 24 +++++++++++++++++++ .../PurchaseReportPaidByFilterModal.tsx | 4 ++-- .../PurchaseReportSummaryAmounts.tsx | 6 ++--- .../src/pages/purchase_report_list/index.tsx | 11 +++++---- 4 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.module.css diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.module.css b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.module.css new file mode 100644 index 000000000..43952c8e6 --- /dev/null +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.module.css @@ -0,0 +1,24 @@ +.nameSelect { + position: relative; + width: 100%; +} + +.nameSelect button { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.nameSelect::after { + content: ''; + background: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/1697571/chevron-down.svg) + no-repeat center; + display: block; + height: 1rem; + position: absolute; + right: 1rem; + top: 50%; + transform: translateY(-50%); + width: 1rem; + pointer-events: none; +} diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index 7fdc2882b..1707e17e9 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -3,7 +3,7 @@ import { RiSearchLine } from 'react-icons/ri'; import { normalizePaidBy } from '@/utils/purchaseReportFilters'; import { CloseButton, Modal, OutlinePrimaryButton, Select } from '@components/common'; -import s from '@components/common/Select/Select.module.css'; +import s from './PurchaseReportPaidByFilterModal.module.css'; import { Bureau, User } from '@type/common'; interface PurchaseReportPaidByFilterModalProps { @@ -130,7 +130,7 @@ const PurchaseReportPaidByFilterModal: FC

    氏名

    -
    +
    -
    - {isNameDropdownOpen && ( -
    -
    -
    - - setSearchQuery(e.target.value)} - className='w-full bg-black-300 text-sm text-white-0 outline-none placeholder:text-black-900 [font-family:"Noto_Sans_JP"]' - /> -
    -
    -
      -
    • - -
    • - {filteredOptions.map((opt) => ( -
    • - -
    • - ))} - {filteredOptions.length === 0 && ( -
    • - 該当なし -
    • - )} -
    -
    - )} + + instanceId='paid-by-name-select' + isSearchable + options={nameOptions} + value={nameSelectValue} + onChange={handleNameChange} + noOptionsMessage={() => '該当なし'} + menuPortalTarget={typeof document !== 'undefined' ? document.body : null} + styles={{ + control: (base, state) => ({ + ...base, + borderRadius: '9999px', + borderColor: state.isFocused ? '#48b2cf' : '#56DAFF', + outline: state.isFocused ? '1.5px #48b2cf solid' : 'none', + boxShadow: 'none', + paddingTop: '0.25rem', + paddingBottom: '0.25rem', + paddingLeft: '0.75rem', + paddingRight: '0.25rem', + '&:hover': { + borderColor: state.isFocused ? '#48b2cf' : '#56DAFF', + }, + }), + indicatorSeparator: () => ({ display: 'none' }), + menuPortal: (base) => ({ ...base, zIndex: 9999 }), + }} + />
    From db78d6f5fc44b4424731e404c7fd9077a090ce3c Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Wed, 13 May 2026 00:43:26 +0900 Subject: [PATCH 24/47] [refactor] Clean up formatting and improve readability in PurchaseReportPaidByFilterModal --- .../PurchaseReportPaidByFilterModal.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index 28956f00b..d18b93429 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -2,7 +2,12 @@ import React, { FC, useMemo, useState } from 'react'; import Select, { SingleValue } from 'react-select'; import { normalizePaidBy } from '@/utils/purchaseReportFilters'; -import { CloseButton, Modal, OutlinePrimaryButton, Select as CommonSelect } from '@components/common'; +import { + CloseButton, + Modal, + OutlinePrimaryButton, + Select as CommonSelect, +} from '@components/common'; import { Bureau, User } from '@type/common'; type NameOption = { value: string; label: string }; @@ -72,10 +77,10 @@ const PurchaseReportPaidByFilterModal: FC const nameSelectValue = draftPaidByUserId != null - ? (nameOptions.find((o) => o.value === `user:${draftPaidByUserId}`) ?? nameOptions[0]) + ? nameOptions.find((o) => o.value === `user:${draftPaidByUserId}`) ?? nameOptions[0] : draftPaidBy != null - ? (nameOptions.find((o) => o.value === `legacy:${draftPaidBy}`) ?? nameOptions[0]) - : nameOptions[0]; + ? nameOptions.find((o) => o.value === `legacy:${draftPaidBy}`) ?? nameOptions[0] + : nameOptions[0]; const handleBureauChange = (event: React.ChangeEvent) => { const value = event.target.value; From 906cf6af9baf431d5cf7594e791ddba0539da8ab Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Wed, 13 May 2026 01:03:40 +0900 Subject: [PATCH 25/47] [refactor] Replace userAtom with useCurrentUser hook in PurchaseReports component --- view/next-project/src/pages/purchase_report_list/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index 82e52c3ca..e947a8871 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -17,7 +17,7 @@ import { useGetYearsPeriods, usePutBuyReportStatusBuyReportId, } from '@/generated/hooks'; -import { userAtom } from '@/store/atoms'; +import { useCurrentUser } from '@/store'; import { buildPaidByFilterParams } from '@/utils/purchaseReportFilters'; import { Card, Checkbox, EditButton, Loading, Title } from '@components/common'; import MainLayout from '@components/layout/MainLayout'; @@ -46,7 +46,7 @@ export default function PurchaseReports() { } = useGetYearsPeriods(); const yearPeriods = yearPeriodsData?.data; - const user = useRecoilValue(userAtom); + const user = useCurrentUser(); const { data: usersResponse } = useGetUsers(); const users = useMemo(() => { From b8d26b8fbd0e4ce677262fef322b057853867b5b Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Wed, 13 May 2026 01:26:22 +0900 Subject: [PATCH 26/47] [fix] Ensure buy report data is updated after status change in PurchaseReports component --- view/next-project/src/pages/purchase_report_list/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index e947a8871..36ba8b7d1 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -207,10 +207,12 @@ export default function PurchaseReports() { try { await trigger(putBuyReportStatusBuyReportIdBody); + mutateBuyReportData(); + mutateBuyReportsSummary(); } catch { console.error('Failed to update buy_reports:', statusError); } - }, [buyReportId, sealChecks, settlementChecks, trigger, statusError]); + }, [buyReportId, sealChecks, settlementChecks, trigger, statusError, mutateBuyReportData, mutateBuyReportsSummary]); useEffect(() => { updateStatus(); From 1c1db0a90fb3ef453c2d757de5b1ab8019691be0 Mon Sep 17 00:00:00 2001 From: nakatashingo <235711nakatashingo@gmail.com> Date: Wed, 13 May 2026 01:27:02 +0900 Subject: [PATCH 27/47] [refactor] Improve formatting and readability in PurchaseReportPaidByFilterModal and PurchaseReportSummaryAmounts components --- .../PurchaseReportPaidByFilterModal.tsx | 6 +++--- .../PurchaseReportSummaryAmounts.tsx | 6 +++--- .../src/pages/purchase_report_list/index.tsx | 14 +++++++++++--- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx index d18b93429..86c6e769e 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportPaidByFilterModal.tsx @@ -77,10 +77,10 @@ const PurchaseReportPaidByFilterModal: FC const nameSelectValue = draftPaidByUserId != null - ? nameOptions.find((o) => o.value === `user:${draftPaidByUserId}`) ?? nameOptions[0] + ? (nameOptions.find((o) => o.value === `user:${draftPaidByUserId}`) ?? nameOptions[0]) : draftPaidBy != null - ? nameOptions.find((o) => o.value === `legacy:${draftPaidBy}`) ?? nameOptions[0] - : nameOptions[0]; + ? (nameOptions.find((o) => o.value === `legacy:${draftPaidBy}`) ?? nameOptions[0]) + : nameOptions[0]; const handleBureauChange = (event: React.ChangeEvent) => { const value = event.target.value; diff --git a/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx index ab679273f..2f90ee9a4 100644 --- a/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx +++ b/view/next-project/src/components/purchasereports/PurchaseReportSummaryAmounts.tsx @@ -11,17 +11,17 @@ export default function PurchaseReportSummaryAmounts({ }: PurchaseReportSummaryAmountsProps) { return (
    -
    +
    未清算金額 - + {unsettledAmountText} 未封詰め金額 - + {unpackedAmountText} diff --git a/view/next-project/src/pages/purchase_report_list/index.tsx b/view/next-project/src/pages/purchase_report_list/index.tsx index 36ba8b7d1..07b3a9cdb 100644 --- a/view/next-project/src/pages/purchase_report_list/index.tsx +++ b/view/next-project/src/pages/purchase_report_list/index.tsx @@ -212,7 +212,15 @@ export default function PurchaseReports() { } catch { console.error('Failed to update buy_reports:', statusError); } - }, [buyReportId, sealChecks, settlementChecks, trigger, statusError, mutateBuyReportData, mutateBuyReportsSummary]); + }, [ + buyReportId, + sealChecks, + settlementChecks, + trigger, + statusError, + mutateBuyReportData, + mutateBuyReportsSummary, + ]); useEffect(() => { updateStatus(); @@ -306,12 +314,12 @@ export default function PurchaseReports() {
    物品 +
    立替者