From 43fe3a1a99bc65fdc55c618c4e08bb145ebcc02c Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 13 Apr 2026 15:27:31 +0900 Subject: [PATCH] =?UTF-8?q?[cs]=20WIP:=20=EC=95=8C=EB=A6=BC=ED=86=A1?= =?UTF-8?q?=EB=B0=9C=EC=86=A1=EC=9D=B4=EB=A0=A5=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ntftalk-history/page.tsx | 3 + apps/cs/docs/spec/ntftalk/history/SPEC.md | 164 ++++++++++++ apps/cs/docs/spec/ntftalk/{ => temp}/SPEC.md | 0 apps/cs/eslint.config.mjs | 2 + .../api/ntftalk-mmt-log-history-service.ts | 58 ++++ apps/cs/src/entities/ntftalk/index.ts | 8 + .../ntftalk-mmt-log-history-interface.ts | 84 ++++++ apps/cs/src/pages/ntftalk-history/index.ts | 1 + .../model/ntftalk-history-table-columns.tsx | 64 +++++ .../ui/ntftalk-history-page.tsx | 247 ++++++++++++++++++ .../ui/ntftalk-temp-detail-dialog.tsx | 7 +- .../ntftalk-temp/ui/ntftalk-temp-page.tsx | 54 ++-- .../ui/ntftalk-temp-regist-dialog.tsx | 45 ++-- apps/cs/src/shared/config/api.ts | 1 + .../src/shared/ui/input-date-range-picker.tsx | 12 +- .../src/ui/input-date-picker/index.tsx | 8 +- 16 files changed, 702 insertions(+), 56 deletions(-) create mode 100644 apps/cs/app/(with-base-layout)/ntftalk-history/page.tsx create mode 100644 apps/cs/docs/spec/ntftalk/history/SPEC.md rename apps/cs/docs/spec/ntftalk/{ => temp}/SPEC.md (100%) create mode 100644 apps/cs/src/entities/ntftalk/api/ntftalk-mmt-log-history-service.ts create mode 100644 apps/cs/src/entities/ntftalk/model/ntftalk-mmt-log-history-interface.ts create mode 100644 apps/cs/src/pages/ntftalk-history/index.ts create mode 100644 apps/cs/src/pages/ntftalk-history/model/ntftalk-history-table-columns.tsx create mode 100644 apps/cs/src/pages/ntftalk-history/ui/ntftalk-history-page.tsx diff --git a/apps/cs/app/(with-base-layout)/ntftalk-history/page.tsx b/apps/cs/app/(with-base-layout)/ntftalk-history/page.tsx new file mode 100644 index 000000000..3e2afeb35 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/ntftalk-history/page.tsx @@ -0,0 +1,3 @@ +import { NtftalkHistoryPage } from "@/pages/ntftalk-history"; + +export default NtftalkHistoryPage; diff --git a/apps/cs/docs/spec/ntftalk/history/SPEC.md b/apps/cs/docs/spec/ntftalk/history/SPEC.md new file mode 100644 index 000000000..51740470a --- /dev/null +++ b/apps/cs/docs/spec/ntftalk/history/SPEC.md @@ -0,0 +1,164 @@ +# 화면설계서: 알림톡발송이력관리 + +## 1. 기본 정보 + +| 항목 | 내용 | +| ---------- | -------------------------------------------- | +| 버전 | 1.0 | +| 프로세스명 | UI설계 | +| 화면명 | 알림톡발송이력관리 | +| 화면 유형 | [x] 조회 [ ] 입력 [ ] 삭제 [ ] 수정 [ ] 출력 | +| 메뉴 경로 | home > 시스템관리 > 알림톡발송이력관리 | + +--- + +## 2. 화면 목적 + +알림톡 발송 이력을 **기간·내용·수신자·결과·시스템** 조건으로 검색하고, **발송 시스템·알림 내용·수신자·수신 시각·성공 여부**를 목록으로 확인한다. + +--- + +## 3. 화면 구성 + +### 3-1. 전체 레이아웃 + +``` +┌────────────────────────────────────────────────────────────────────┐ +│ 페이지 제목: 알림톡발송이력관리 브레드크럼 (우측 상단) │ +├────────────────────────────────────────────────────────────────────┤ +│ [영역 A] 검색 필터 │ +│ · 조회기간, 알림내용, 사용자, 결과, 시스템 │ +│ · [조회], [초기화] │ +├────────────────────────────────────────────────────────────────────┤ +│ [영역 B] 알림톡 발송 이력 그리드 │ +│ · 순번, 시스템, 알림내용, 수신자, 수신일, 결과 │ +│ · 페이지네이션 │ +└────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 4. 영역 A: 검색 필터 + +### 4-1. 필드 정의 + +| 표시명 | 입력 유형 | 필수 | 기본값 | API 파라미터 매핑 | 비고 | +| -------- | -------------------- | ---- | ------------- | ----------------------------------- | ------------------------------------------- | +| 조회기간 | InputDateRangePicker | N | 1개월 전~오늘 | `dateFrom`, `dateTo` (`yyyy-MM-dd`) | `src/shared/ui/input-date-range-picker.tsx` | +| 알림내용 | 텍스트 | N | 공백 | `content` | | +| 사용자 | 텍스트 | N | 공백 | `recipientNum` | | +| 결과 | 드롭다운 | N | 전체 | `successYn` | `SUCCESS` \| `FAIL` \| `ALL` | +| 시스템 | SiteSelect | N | 전체 | `system` | `src/entities/site/ui/site-select.tsx` | + +### 4-2. 버튼 + +| 버튼명 | 동작 | +| ------ | ---------------------------------------------------------------- | +| 조회 | 현재 필터로 영역 B 목록을 재조회한다. 페이지는 1페이지로 초기화. | +| 초기화 | 검색 필드를 기본값으로 되돌린다. | + +--- + +## 5. 영역 B: 알림톡 발송 이력 그리드 + +### 5-1. 그리드 컬럼 + +| 표시명 | Field | 비고 | +| -------- | -------------- | ------------------------------------------------------------------------- | +| 순번 | — | 클라이언트 표시용 인덱스(서버 필드 없음). 페이지네이션 시 오프셋 반영 | +| 시스템 | `siteSystem` | 응답 시 `templateCode` 앞 4자 기반 파생(행정지원·하수도통계·포털·기타) | +| 알림내용 | `content` | 긴 텍스트 말줄임 처리. 툴팁 적용하여 전체 텍스트 확인 가능. | +| 수신자 | `recipientNum` | 실데이터·마스킹 규칙 **TBD** | +| 수신일 | `dateRslt` | `YYYY-MM-DD HH:mm:ss` 형식 표시. | +| 결과 | `successYn` | UI 표시: `SUCCESS` → 성공, `FAIL` → 실패(백엔드는 `reportCode` 기반 파생) | + +- 행 클릭 UX 없음. + +--- + +## 6. API/DTO 명세 + +### 6-1. 공통 + +- 목록 응답: Spring `Page` 직렬화 형태 — 프론트 타입 `PageableList` (`content`, `totalElements`, `totalPages`, `number`, `size` 등). +- 목록 요청 쿼리: `Pageable` 표준 파라미터 `page`, `size`, `sort` (`PageableParams`). + +### 6-2. 알림톡 발송 이력 API + +Base: `/ntftalk-service/api/v1/ata/mmt-log-history` + +| 기능 | Method | URL | Request DTO | Response DTO | API(클라이언트 함수명) | +| ----------------- | ------ | ----------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ----------------------------- | +| 목록 조회(페이징) | GET | `/ata/mmt-log-history` | Query: `AtaMmtLogHistorySearchDto` 필드 + `Pageable`(`page`,`size`,`sort`) | `Page` → `NtftalkMmtLogHistoryPageList` | `getNtftalkMmtLogHistoryPage` | +| 상세 조회 | GET | `/ata/mmt-log-history/{mtPr}` | Path: `mtPr` | `AtaMmtLogHistoryResponseDto` → `NtftalkMmtLogHistory` | `getNtftalkMmtLogHistory` | +| 등록 | POST | `/ata/mmt-log-history` | Body: `AtaMmtLogHistorySaveRequestDto` → `NtftalkMmtLogHistorySavePayload` | `AtaMmtLogHistoryResponseDto` | `saveNtftalkMmtLogHistory` | +| 수정 | PUT | `/ata/mmt-log-history/{mtPr}` | Path: `mtPr`, Body: `AtaMmtLogHistoryUpdateRequestDto` → `NtftalkMmtLogHistoryUpdatePayload` | `AtaMmtLogHistoryResponseDto` | `updateNtftalkMmtLogHistory` | +| 삭제 | DELETE | `/ata/mmt-log-history/{mtPr}` | Path: `mtPr` | 없음(204) | `deleteNtftalkMmtLogHistory` | + +#### 검색 Query ↔ TypeScript (`NtftalkMmtLogHistorySearchParams`) + +| Query (Java 필드) | TS 필드 | 설명 | +| ----------------- | --------------- | ------------------------------------ | +| `subject` | `subject?` | 메시지 제목 | +| `content` | `content?` | 전송 메시지 내용 | +| `recipientNum` | `recipientNum?` | 수신자 전화번호 | +| `msgStatus` | `msgStatus?` | `1` 전송대기, `2` 결과대기, `3` 완료 | +| `reportCode` | `reportCode?` | 전송 결과 코드(`1000`: 성공) | +| `dateFrom` | `dateFrom?` | `yyyy-MM-dd` | +| `dateTo` | `dateTo?` | `yyyy-MM-dd` | +| `system` | `system?` | `ADSS` \| `CWMS` \| `PORT` \| `ALL` | +| `successYn` | `successYn?` | `SUCCESS` \| `FAIL` \| `ALL` | + +#### 응답 행 ↔ TypeScript (`NtftalkMmtLogHistory`) + +백엔드 `AtaMmtLogHistoryResponseDto`와 동일 필드명: `mtPr`, `mtRefkey`, `priority`, `dateClientReq`, `subject`, `content`, `callback`, `msgStatus`, `recipientNum`, `dateMtSent`, `dateRslt`, `dateMtReport`, `reportCode`, `countryCode`, `msgType`, `regDate`, `senderKey`, `templateCode`, `siteSystem`, `successYn`. + +--- + +## 7. UI 구현 가이드 + +### 7-1. 기술 스택·원칙 + +- **UI**: `@common/components` 공통 컴포넌트 +- **테이블**: `@tanstack/react-table` + `useDataTable`(`src/shared/lib/use-data-table.tsx`) +- **데이터**: `@tanstack/react-query` — 목록은 `getNtftalkMmtLogHistoryPage`, 무한 스크롤 vs 페이지 UI는 **TBD** +- **레이아웃**: App Router `app/(with-base-layout)/ntftalk-history/page.tsx`에서 페이지 UI 마운트 + +### 7-2. 모듈·파일 배치 + +| 구분 | 경로 | +| --------------- | ------------------------------------------------------------------------------------------------------------------- | +| 라우트 엔트리 | `apps/cs/app/(with-base-layout)/ntftalk-history/page.tsx` | +| 페이지 셸 | `apps/cs/src/pages/ntftalk-history/ui/ntftalk-history-page.tsx` | +| 컬럼 정의 | `apps/cs/src/pages/ntftalk-history/model/*-table-columns.tsx` | +| API·타입 | `apps/cs/src/entities/ntftalk/model/ntftalk-mmt-log-history-interface.ts`, `api/ntftalk-mmt-log-history-service.ts` | +| 엔드포인트 상수 | `apps/cs/src/shared/config/api.ts` — `ENDPOINT.NTFTALK_SERVICE.MMT_LOG_HISTORY` | + +### 7-3. 영역별 UI 매핑 + +| 영역 | 화면 요소 | 컴포넌트·훅 | 구현 비고 | +| ---- | ------------ | ------------------------------------------------ | ---------------------------- | +| A | 검색 폼 | `Input`, `Label`, `Select`, `Button` | | +| B | 이력 그리드 | `useDataTable` + `ColumnDef`, `manualPagination` | | +| 공통 | 로딩·빈 목록 | `LoadingWithAnimation`, 빈 데이터 안내 | 기타 목록 페이지와 동일 수준 | + +### 7-4. 데이터 흐름 + +``` +BEGIN + + // 최초 진입 + CALL 알림톡발송이력목록조회(기본 검색 조건, page=0, size=10) + DISPLAY 알림톡 발송 이력 그리드 (영역 B) + + ON [조회] 버튼 CLICK DO + CALL 알림톡발송이력목록조회(조회기간→dateFrom/dateTo, content, recipientNum, successYn, system, page=0) + DISPLAY 알림톡 발송 이력 그리드 (영역 B) + END ON + + ON [초기화] 버튼 CLICK DO + RESET 검색 조건 → 기본값 + END ON + +END +``` diff --git a/apps/cs/docs/spec/ntftalk/SPEC.md b/apps/cs/docs/spec/ntftalk/temp/SPEC.md similarity index 100% rename from apps/cs/docs/spec/ntftalk/SPEC.md rename to apps/cs/docs/spec/ntftalk/temp/SPEC.md diff --git a/apps/cs/eslint.config.mjs b/apps/cs/eslint.config.mjs index e11052b52..c2643e6aa 100644 --- a/apps/cs/eslint.config.mjs +++ b/apps/cs/eslint.config.mjs @@ -1,6 +1,7 @@ import { dirname } from "path"; import { fileURLToPath } from "url"; import { FlatCompat } from "@eslint/eslintrc"; +import baseEsLintConfig from "../../eslint.config.mjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -11,6 +12,7 @@ const compat = new FlatCompat({ const eslintConfig = [ ...compat.extends("next/core-web-vitals", "next/typescript"), + ...baseEsLintConfig, { rules: { "react-hooks/exhaustive-deps": "off", diff --git a/apps/cs/src/entities/ntftalk/api/ntftalk-mmt-log-history-service.ts b/apps/cs/src/entities/ntftalk/api/ntftalk-mmt-log-history-service.ts new file mode 100644 index 000000000..ebfd90bdf --- /dev/null +++ b/apps/cs/src/entities/ntftalk/api/ntftalk-mmt-log-history-service.ts @@ -0,0 +1,58 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import { + type NtftalkMmtLogHistory, + type NtftalkMmtLogHistoryPageList, + type NtftalkMmtLogHistorySavePayload, + type NtftalkMmtLogHistorySearchParams, + type NtftalkMmtLogHistoryService, + type NtftalkMmtLogHistoryUpdatePayload, +} from "../model/ntftalk-mmt-log-history-interface"; + +class NtftalkMmtLogHistoryApiService implements NtftalkMmtLogHistoryService { + async getNtftalkMmtLogHistoryPage( + params: NtftalkMmtLogHistorySearchParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.NTFTALK_SERVICE.MMT_LOG_HISTORY}`, + { params }, + ); + return response.data; + } + + async getNtftalkMmtLogHistory(mtPr: number): Promise { + const response = await client.get( + `${ENDPOINT.NTFTALK_SERVICE.MMT_LOG_HISTORY}/${mtPr}`, + ); + return response.data; + } + + async saveNtftalkMmtLogHistory( + payload: NtftalkMmtLogHistorySavePayload, + ): Promise { + const response = await client.post( + `${ENDPOINT.NTFTALK_SERVICE.MMT_LOG_HISTORY}`, + payload, + ); + return response.data; + } + + async updateNtftalkMmtLogHistory( + mtPr: number, + payload: NtftalkMmtLogHistoryUpdatePayload, + ): Promise { + const response = await client.put( + `${ENDPOINT.NTFTALK_SERVICE.MMT_LOG_HISTORY}/${mtPr}`, + payload, + ); + return response.data; + } + + async deleteNtftalkMmtLogHistory(mtPr: number): Promise { + await client.delete( + `${ENDPOINT.NTFTALK_SERVICE.MMT_LOG_HISTORY}/${mtPr}`, + ); + } +} + +export const NtftalkMmtLogHistoryApi = new NtftalkMmtLogHistoryApiService(); diff --git a/apps/cs/src/entities/ntftalk/index.ts b/apps/cs/src/entities/ntftalk/index.ts index 048eaad9e..0210af8a4 100644 --- a/apps/cs/src/entities/ntftalk/index.ts +++ b/apps/cs/src/entities/ntftalk/index.ts @@ -1,6 +1,7 @@ export { NtftalkVarApi } from "./api/ntftalk-var-service"; export { NtftalkTempApi } from "./api/ntftalk-temp-service"; export { NtftalkBiztalkApi } from "./api/ntftalk-biztalk-service"; +export { NtftalkMmtLogHistoryApi } from "./api/ntftalk-mmt-log-history-service"; export type { NtftalkVar, NtftalkVarPageList, @@ -27,3 +28,10 @@ export type { NtftalkTempSearchParams, NtftalkTempUpdatePayload, } from "./model/ntftalk-temp-interface"; +export type { + NtftalkMmtLogHistory, + NtftalkMmtLogHistoryPageList, + NtftalkMmtLogHistorySavePayload, + NtftalkMmtLogHistorySearchParams, + NtftalkMmtLogHistoryUpdatePayload, +} from "./model/ntftalk-mmt-log-history-interface"; diff --git a/apps/cs/src/entities/ntftalk/model/ntftalk-mmt-log-history-interface.ts b/apps/cs/src/entities/ntftalk/model/ntftalk-mmt-log-history-interface.ts new file mode 100644 index 000000000..9f272d1ac --- /dev/null +++ b/apps/cs/src/entities/ntftalk/model/ntftalk-mmt-log-history-interface.ts @@ -0,0 +1,84 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +export interface NtftalkMmtLogHistorySearchParams extends PageableParams { + subject?: string; + content?: string; + recipientNum?: string; + /** 1:전송대기, 2:결과대기, 3:완료 */ + msgStatus?: string; + /** 1000:성공 */ + reportCode?: string; + /** yyyy-MM-dd */ + dateFrom?: string; + /** yyyy-MM-dd */ + dateTo?: string; + /** ADSS | CWMS | PORT | ALL */ + system?: string; + /** SUCCESS | FAIL | ALL */ + successYn?: string; +} + +export interface NtftalkMmtLogHistory { + mtPr: number; + mtRefkey?: string | null; + priority?: string | null; + dateClientReq?: string | null; + subject?: string | null; + content?: string | null; + callback?: string | null; + msgStatus?: string | null; + recipientNum?: string | null; + dateMtSent?: string | null; + dateRslt?: string | null; + dateMtReport?: string | null; + reportCode?: string | null; + countryCode?: string | null; + msgType?: number | null; + regDate?: string | null; + senderKey?: string | null; + templateCode?: string | null; + siteSystem?: string | null; + successYn?: string | null; +} + +export interface NtftalkMmtLogHistorySavePayload { + mtPr: number; + mtRefkey?: string; + priority?: string; + dateClientReq?: string; + subject: string; + content: string; + callback: string; + msgStatus?: string; + recipientNum: string; + reportCode?: string; + countryCode?: string; + msgType?: number; + senderKey: string; + templateCode?: string; + responseMethod?: string; +} + +export interface NtftalkMmtLogHistoryUpdatePayload { + content: string; + recipientNum: string; + msgStatus?: string; + reportCode?: string; +} + +export type NtftalkMmtLogHistoryPageList = PageableList; + +export interface NtftalkMmtLogHistoryService { + getNtftalkMmtLogHistoryPage( + params: NtftalkMmtLogHistorySearchParams, + ): Promise; + getNtftalkMmtLogHistory(mtPr: number): Promise; + saveNtftalkMmtLogHistory( + payload: NtftalkMmtLogHistorySavePayload, + ): Promise; + updateNtftalkMmtLogHistory( + mtPr: number, + payload: NtftalkMmtLogHistoryUpdatePayload, + ): Promise; + deleteNtftalkMmtLogHistory(mtPr: number): Promise; +} diff --git a/apps/cs/src/pages/ntftalk-history/index.ts b/apps/cs/src/pages/ntftalk-history/index.ts new file mode 100644 index 000000000..e9871422b --- /dev/null +++ b/apps/cs/src/pages/ntftalk-history/index.ts @@ -0,0 +1 @@ +export { NtftalkHistoryPage } from "./ui/ntftalk-history-page"; diff --git a/apps/cs/src/pages/ntftalk-history/model/ntftalk-history-table-columns.tsx b/apps/cs/src/pages/ntftalk-history/model/ntftalk-history-table-columns.tsx new file mode 100644 index 000000000..637285075 --- /dev/null +++ b/apps/cs/src/pages/ntftalk-history/model/ntftalk-history-table-columns.tsx @@ -0,0 +1,64 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import type { NtftalkMmtLogHistory } from "@/entities/ntftalk"; + +function formatDateTime(value: string | null | undefined): string { + if (!value) return "—"; + const normalized = value.includes("T") + ? value.replace("T", " ").slice(0, 19) + : value.slice(0, 19); + return normalized; +} + +function formatResult(successYn: string | null | undefined): string { + if (!successYn) return "—"; + if (successYn === "SUCCESS") return "성공"; + if (successYn === "FAIL") return "실패"; + return successYn; +} + +export function createNtftalkHistoryColumns(): ColumnDef[] { + return [ + { + id: "rowNo", + header: "순번", + enableSorting: false, + cell: ({ row, table }) => { + const { pageIndex, pageSize } = table.getState().pagination; + return pageIndex * pageSize + row.index + 1; + }, + }, + { + accessorKey: "siteSystem", + header: "시스템", + cell: ({ getValue }) => (getValue() as string) || "—", + }, + { + accessorKey: "content", + header: "알림내용", + cell: ({ getValue }) => { + const v = getValue() as string; + if (!v) return "—"; + return ( + + {v} + + ); + }, + }, + { + accessorKey: "recipientNum", + header: "수신자", + cell: ({ getValue }) => (getValue() as string) || "—", + }, + { + accessorKey: "dateRslt", + header: "수신일", + cell: ({ getValue }) => formatDateTime(getValue() as string | null), + }, + { + accessorKey: "successYn", + header: "결과", + cell: ({ getValue }) => formatResult(getValue() as string | null), + }, + ]; +} diff --git a/apps/cs/src/pages/ntftalk-history/ui/ntftalk-history-page.tsx b/apps/cs/src/pages/ntftalk-history/ui/ntftalk-history-page.tsx new file mode 100644 index 000000000..1f2e0e9c4 --- /dev/null +++ b/apps/cs/src/pages/ntftalk-history/ui/ntftalk-history-page.tsx @@ -0,0 +1,247 @@ +"use client"; + +import { useMemo, useState } from "react"; + +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { getCoreRowModel, type PaginationState } from "@tanstack/react-table"; +import dayjs from "dayjs"; + +import { ENDPOINT } from "@/shared/config"; +import { useDataTable } from "@/shared/lib"; +import { InputDateRangePicker } from "@/shared/ui/input-date-range-picker"; +import { + NtftalkMmtLogHistoryApi, + type NtftalkMmtLogHistorySearchParams, +} from "@/entities/ntftalk"; +import { SiteSelect } from "@/entities/site"; +import { + Button, + Input, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@common/components"; + +import { createNtftalkHistoryColumns } from "../model/ntftalk-history-table-columns"; + +type DateRange = { + from?: Date | null; + to?: Date | null; +}; + +type PresetPeriod = "" | "3M" | "6M" | "1Y" | "CUSTOM"; + +function computeDateRangeFromPreset( + preset: Extract, +): DateRange { + const to = dayjs().endOf("day").toDate(); + const base = dayjs(); + let fromDay: dayjs.Dayjs; + switch (preset) { + case "3M": + fromDay = base.subtract(3, "month"); + break; + case "6M": + fromDay = base.subtract(6, "month"); + break; + case "1Y": + fromDay = base.subtract(1, "year"); + break; + } + return { from: fromDay.startOf("day").toDate(), to }; +} + +function NtftalkHistoryPage() { + const [contentInput, setContentInput] = useState(""); + const [recipientInput, setRecipientInput] = useState(""); + const [presetPeriod, setPresetPeriod] = useState(""); + const [dateRange, setDateRange] = useState({ + from: null, + to: null, + }); + const [resultFilter, setResultFilter] = useState("ALL"); + const [systemFilter, setSystemFilter] = useState("ALL"); + + const [searchParams, setSearchParams] = + useState({ sort: "dateMtReport,desc" }); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + + const listQuery = useQuery({ + queryKey: [ + ENDPOINT.NTFTALK_SERVICE.MMT_LOG_HISTORY, + searchParams, + pagination, + ], + queryFn: () => + NtftalkMmtLogHistoryApi.getNtftalkMmtLogHistoryPage({ + ...searchParams, + page: pagination.pageIndex, + size: pagination.pageSize, + }), + placeholderData: keepPreviousData, + }); + + const columns = useMemo(() => createNtftalkHistoryColumns(), []); + + const { tableComponent } = useDataTable({ + query: listQuery, + tableOptions: { + columns, + state: { pagination }, + manualPagination: true, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: setPagination, + }, + dataTableOptions: { + viewPagination: true, + }, + }); + + const buildSearchParamsFromInputs = (): NtftalkMmtLogHistorySearchParams => ({ + sort: searchParams.sort ?? "dateMtReport,desc", + content: contentInput.trim() || undefined, + recipientNum: recipientInput.trim() || undefined, + successYn: resultFilter, + system: systemFilter, + dateFrom: dateRange.from + ? dayjs(dateRange.from).format("YYYY-MM-DD") + : undefined, + dateTo: dateRange.to ? dayjs(dateRange.to).format("YYYY-MM-DD") : undefined, + }); + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + setPagination((p) => ({ ...p, pageIndex: 0 })); + setSearchParams(buildSearchParamsFromInputs()); + }; + + const handlePresetPeriodChange = (value: PresetPeriod) => { + setPresetPeriod(value); + if (value === "3M" || value === "6M" || value === "1Y") { + setDateRange(computeDateRangeFromPreset(value)); + } + }; + + const handleDateRangeChange = (next: DateRange) => { + setDateRange(next); + setPresetPeriod("CUSTOM"); + }; + + const handleReset = () => { + setContentInput(""); + setRecipientInput(""); + setPresetPeriod(""); + setDateRange({ from: null, to: null }); + setResultFilter("ALL"); + setSystemFilter("ALL"); + setPagination((p) => ({ ...p, pageIndex: 0 })); + setSearchParams({ sort: "dateMtReport,desc" }); + }; + + return ( +
+
+
+
+
+
+ + + +
+
+
+
+ + setContentInput(e.target.value)} + /> +
+
+ + setRecipientInput(e.target.value)} + placeholder="수신자 번호" + /> +
+
+ + +
+
+ + setSystemFilter(value)} + enabledAllSite + /> +
+
+
+
+ + +
+
+
+ +
+
+

알림톡 발송 이력

+
+
{tableComponent}
+
+
+ ); +} + +export { NtftalkHistoryPage }; diff --git a/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-detail-dialog.tsx b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-detail-dialog.tsx index f196df4e3..aa08f92ca 100644 --- a/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-detail-dialog.tsx +++ b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-detail-dialog.tsx @@ -1,6 +1,10 @@ "use client"; import { useQuery } from "@tanstack/react-query"; + +import { ENDPOINT } from "@/shared/config"; +import { NoData } from "@/shared/ui/no-data"; +import { NtftalkTempApi } from "@/entities/ntftalk"; import { Dialog, DialogContent, @@ -13,9 +17,6 @@ import { TableHead, TableRow, } from "@common/components"; -import { NtftalkTempApi } from "@/entities/ntftalk"; -import { ENDPOINT } from "@/shared/config"; -import { NoData } from "@/shared/ui/no-data"; interface NtftalkTempDetailDialogProps { templateCode: string | null; diff --git a/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-page.tsx b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-page.tsx index 8cdd4e338..57db5d6e2 100644 --- a/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-page.tsx +++ b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-page.tsx @@ -2,12 +2,38 @@ import { useCallback, useMemo, useState } from "react"; import { useFieldArray, useForm, useWatch } from "react-hook-form"; + import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { keepPreviousData, useQuery, useQueryClient, } from "@tanstack/react-query"; +import { + getCoreRowModel, + type PaginationState, + type RowSelectionState, + useReactTable, +} from "@tanstack/react-table"; +import { z } from "zod"; + +import { AUTH_USER_ID, ENDPOINT } from "@/shared/config"; +import { + getCookie, + useAlertStore, + useDataTable, + useMutationWrapper, +} from "@/shared/lib"; +import { NoData } from "@/shared/ui/no-data"; +import { + NtftalkBiztalkApi, + type NtftalkBiztalkInspectionRequestItem, + type NtftalkTemp, + NtftalkTempApi, + type NtftalkTempSearchParams, + NtftalkVarApi, + type NtftalkVarSearchParams, +} from "@/entities/ntftalk"; import { Button, DataTable, @@ -20,36 +46,14 @@ import { SelectTrigger, SelectValue, } from "@common/components"; -import { - getCoreRowModel, - type PaginationState, - type RowSelectionState, - useReactTable, -} from "@tanstack/react-table"; -import { z } from "zod"; -import { - NtftalkBiztalkApi, - NtftalkTempApi, - NtftalkVarApi, - type NtftalkBiztalkInspectionRequestItem, - type NtftalkTemp, - type NtftalkTempSearchParams, - type NtftalkVarSearchParams, -} from "@/entities/ntftalk"; -import { AUTH_USER_ID, ENDPOINT } from "@/shared/config"; -import { - getCookie, - useAlertStore, - useDataTable, - useMutationWrapper, -} from "@/shared/lib"; -import { NoData } from "@/shared/ui/no-data"; -import { createTemplateColumns } from "../model/template-table-columns"; + import { createEmptyVarDraft, type NtftalkVarGridRow, } from "../model/ntftalk-var-grid"; +import { createTemplateColumns } from "../model/template-table-columns"; import { createVarColumns } from "../model/var-table-columns"; + import { NtftalkTempDetailDialog } from "./ntftalk-temp-detail-dialog"; import { NtftalkTempRegistDialog } from "./ntftalk-temp-regist-dialog"; diff --git a/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-regist-dialog.tsx b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-regist-dialog.tsx index 6a8937073..8a537e7a2 100644 --- a/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-regist-dialog.tsx +++ b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-regist-dialog.tsx @@ -1,8 +1,9 @@ "use client"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; import { useEffect, useMemo, useState } from "react"; +import { useForm } from "react-hook-form"; + +import { zodResolver } from "@hookform/resolvers/zod"; import { keepPreviousData, useQuery, @@ -13,10 +14,28 @@ import { type PaginationState, type RowSelectionState, } from "@tanstack/react-table"; + +import { ENDPOINT } from "@/shared/config"; +import { useAlertStore, useDataTable, useMutationWrapper } from "@/shared/lib"; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/shared/ui/form"; +import { NoData } from "@/shared/ui/no-data"; +import { + NtftalkBiztalkApi, + NtftalkTempApi, + NtftalkVarApi, + type NtftalkVarSearchParams, +} from "@/entities/ntftalk"; import { Button, Dialog, DialogContent, + DialogFooter, DialogHeader, DialogTitle, Input, @@ -33,29 +52,13 @@ import { TableBody, TableCell, TableHead, - Textarea, TableRow, - DialogFooter, + Textarea, } from "@common/components"; + import { - NtftalkBiztalkApi, - NtftalkTempApi, - NtftalkVarApi, - type NtftalkVarSearchParams, -} from "@/entities/ntftalk"; -import { ENDPOINT } from "@/shared/config"; -import { useAlertStore, useDataTable, useMutationWrapper } from "@/shared/lib"; -import { NoData } from "@/shared/ui/no-data"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@/shared/ui/form"; -import { - ntftalkTempSaveFormSchema, type NtftalkTempSaveFormData, + ntftalkTempSaveFormSchema, } from "../model/ntftalk-temp-save-schema"; import { createVarColumns } from "../model/var-table-columns"; diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index c380a8817..e577c5bb1 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -70,6 +70,7 @@ export const ENDPOINT = { NTFTALK_VAR: "/ntftalk-service/api/v1/ntftalk-var", NTFTALK_TEMP: "/ntftalk-service/api/v1/ntftalk-temp", NTFTALK_BIZTALK: "/ntftalk-service/api/v1/ntftalk-biztalk", + MMT_LOG_HISTORY: "/ntftalk-service/api/v1/ata/mmt-log-history", }, }; diff --git a/apps/cs/src/shared/ui/input-date-range-picker.tsx b/apps/cs/src/shared/ui/input-date-range-picker.tsx index 1ee2bc5e1..ec7c5b5b4 100644 --- a/apps/cs/src/shared/ui/input-date-range-picker.tsx +++ b/apps/cs/src/shared/ui/input-date-range-picker.tsx @@ -1,20 +1,24 @@ -import { InputDatePicker } from "@common/components"; import { useEffect } from "react"; + +import { InputDatePicker } from "@common/components"; + import { useAlertStore } from "../lib"; type DateRange = { - from?: Date; - to?: Date; + from?: Date | null; + to?: Date | null; }; type InputDateRangePickerProps = { dateRange: DateRange; setDateRange: (dateRange: DateRange) => void; + disabled?: boolean; }; function InputDateRangePicker({ dateRange, setDateRange, + disabled, }: InputDateRangePickerProps) { const alert = useAlertStore((state) => state.setMessage); @@ -32,6 +36,7 @@ function InputDateRangePicker({ onChange={(date) => { setDateRange({ ...dateRange, from: date }); }} + disabled={disabled} /> ~ { setDateRange({ ...dateRange, to: date }); }} + disabled={disabled} /> ); diff --git a/packages/common/components/src/ui/input-date-picker/index.tsx b/packages/common/components/src/ui/input-date-picker/index.tsx index 93d2caf7c..e5ec1eac3 100644 --- a/packages/common/components/src/ui/input-date-picker/index.tsx +++ b/packages/common/components/src/ui/input-date-picker/index.tsx @@ -12,7 +12,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "../popover"; import "./input-date-picker.css"; export interface InputDatePickerProps { - value?: Date; + value?: Date | null; defaultValue?: Date; onChange?: (date: Date | undefined) => void; placeholder?: string; @@ -34,7 +34,7 @@ export function InputDatePicker({ const [internalDate, setInternalDate] = useState( defaultValue, ); - const [month, setMonth] = useState( + const [month, setMonth] = useState( isControlled ? controlledValue : defaultValue, ); @@ -112,9 +112,9 @@ export function InputDatePicker({ > { handleDateChange(newDate);