From 2fcd735d18083f0c4cad4861d2d9fe2e3577c8be Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 7 May 2026 10:47:14 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[st]=20feat:=20=EB=8C=80=EC=8B=9C=EB=B3=B4?= =?UTF-8?q?=EB=93=9C=20=EB=AF=B8=EC=A0=9C=EC=B6=9C=20=EC=A7=80=ED=91=9C=20?= =?UTF-8?q?API=20=EB=B3=80=EA=B2=BD=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistics/api/dashboard/dashboard-queries.ts | 7 +++++-- .../statistics/api/dashboard/dashboard-service.ts | 2 +- .../model/dashboard/dashboard-interface.ts | 14 ++++++++++++-- .../pages/statistics/ui/panels/unsubmit-panel.tsx | 6 +++--- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/st/src/entities/statistics/api/dashboard/dashboard-queries.ts b/apps/st/src/entities/statistics/api/dashboard/dashboard-queries.ts index 2f37dbaa2..5b4e124f8 100644 --- a/apps/st/src/entities/statistics/api/dashboard/dashboard-queries.ts +++ b/apps/st/src/entities/statistics/api/dashboard/dashboard-queries.ts @@ -41,11 +41,14 @@ export function useDashboardUnSubmitCount( params: DashboardSearchParams | null, ) { return useQuery({ - queryKey: createDashboardQueryKey("un-submit-count", params), + queryKey: createDashboardQueryKey("submission-status", params), queryFn: async () => { if (!params) throw new Error("Dashboard search params are required."); - const response = await dashboardApi.getUnSubmitCount(params); + const response = await dashboardApi.getUnSubmitCount({ + ...params, + unSubmitOnly: true, + }); return response.data; }, enabled: hasRequiredDashboardParams(params), diff --git a/apps/st/src/entities/statistics/api/dashboard/dashboard-service.ts b/apps/st/src/entities/statistics/api/dashboard/dashboard-service.ts index 5fcaa3ac8..0bb830e61 100644 --- a/apps/st/src/entities/statistics/api/dashboard/dashboard-service.ts +++ b/apps/st/src/entities/statistics/api/dashboard/dashboard-service.ts @@ -35,7 +35,7 @@ async function getUnSubmitCount( ): Promise> { const response = await stClient.get< DashboardApiResponse - >(`${ENDPOINT.ST_SERVICE.DASHBOARD}/un-submit-count`, { params }); + >(`${ENDPOINT.ST_SERVICE.DASHBOARD}/submission-status`, { params }); return response.data; } diff --git a/apps/st/src/entities/statistics/model/dashboard/dashboard-interface.ts b/apps/st/src/entities/statistics/model/dashboard/dashboard-interface.ts index f66eec7dc..b0d9093a0 100644 --- a/apps/st/src/entities/statistics/model/dashboard/dashboard-interface.ts +++ b/apps/st/src/entities/statistics/model/dashboard/dashboard-interface.ts @@ -3,6 +3,10 @@ export interface DashboardSearchParams { dtlInstCd: string; enoCd?: string; laeSeCd?: string; + sewerBzmnCd?: string; + sido?: string; + sggNm?: string; + unSubmitOnly?: boolean; } export interface DashboardApiResponse { @@ -34,13 +38,19 @@ export interface VrfcErrCountResponse { export interface UnSubmitCountResponse { statsYr: string; - unSubmitCount: number; - unSubmitList: UnSubmitStatsResponse[]; + statusList: UnSubmitStatsResponse[]; + submittedCount: number; + totalCount: number; + unSubmittedCount: number; } export interface UnSubmitStatsResponse { + sbmsnYn: string; + sidoSggNm: string; statsCd: string; statsNm: string; + statusCd: string; + statusNm: string; } export interface StatsSubmissionResponse { diff --git a/apps/st/src/pages/statistics/ui/panels/unsubmit-panel.tsx b/apps/st/src/pages/statistics/ui/panels/unsubmit-panel.tsx index fae349694..c4981be02 100644 --- a/apps/st/src/pages/statistics/ui/panels/unsubmit-panel.tsx +++ b/apps/st/src/pages/statistics/ui/panels/unsubmit-panel.tsx @@ -24,7 +24,7 @@ export function UnSubmitPanel({ params }: UnSubmitPanelProps) {

미제출 지표

{data && ( - {data.unSubmitCount}건 + {data.unSubmittedCount ?? 0}건 )} @@ -43,13 +43,13 @@ export function UnSubmitPanel({ params }: UnSubmitPanelProps) { )} {data && (
- {data.unSubmitList.length === 0 ? ( + {data.statusList.length === 0 ? (
미제출 지표가 없습니다.
) : (
- {data.unSubmitList.map((item) => ( + {data.statusList.map((item) => (
Date: Thu, 7 May 2026 10:48:12 +0900 Subject: [PATCH 2/3] =?UTF-8?q?[st]=20WIP:=20=EC=95=8C=EB=A6=BC=ED=86=A1?= =?UTF-8?q?=EB=B0=9C=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [st] WIP: 알림톡발송 --- .../management/NoticeTalkSending.tsx | 977 ++++++++++++++++++ .../st/src/entities/statistics/api/queries.ts | 15 + .../statistics/api/sewer-st-user-service.ts | 29 + apps/st/src/entities/statistics/index.ts | 7 + .../model/sewer-st-user-interface.ts | 46 + apps/st/src/shared/config/api.ts | 5 + 6 files changed, 1079 insertions(+) create mode 100644 apps/st/src/components/statistics/management/NoticeTalkSending.tsx create mode 100644 apps/st/src/entities/statistics/api/sewer-st-user-service.ts create mode 100644 apps/st/src/entities/statistics/model/sewer-st-user-interface.ts diff --git a/apps/st/src/components/statistics/management/NoticeTalkSending.tsx b/apps/st/src/components/statistics/management/NoticeTalkSending.tsx new file mode 100644 index 000000000..592791cec --- /dev/null +++ b/apps/st/src/components/statistics/management/NoticeTalkSending.tsx @@ -0,0 +1,977 @@ +"use client"; + +import { useMemo, useState } from "react"; + +import { useQuery } from "@tanstack/react-query"; +import { + type ColumnDef, + getCoreRowModel, + type RowSelectionState, + useReactTable, +} from "@tanstack/react-table"; + +import { client } from "@/shared/api/client"; +import { ENDPOINT } from "@/shared/config"; +import { useAlertStore } from "@/shared/lib"; +import type { PageableList } from "@/shared/model"; +import { + type AdstZoneResponseDto, + type SewerStUserItem, + useSewerStUserList, + useStAnlsSidoCombo, + useStAnlsSigunguCombo, +} from "@/entities/statistics"; +import { + Button, + Checkbox, + DataTable, + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + Input, + Label, + LoadingWithAnimation, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@common/components"; + +interface NtftalkTemplate { + templateCode: string; + templateName: string; + templateContent: string; +} + +interface TemplateVariablesResponse { + templateCode: string; + variables: string[]; + count: number; +} + +interface BusinessSendRequest { + templateCode: string; + callback?: string; + fixedRecipientNum?: string; + recipients: Record[]; +} + +interface BusinessSendResponse { + success: boolean; + sentCount: number; + message: string; +} + +interface SelectedRecipient extends SewerStUserItem { + variableOverrides: Record; +} + +const EMPTY_ADST_LIST: AdstZoneResponseDto[] = []; +const GUBUN_OPTIONS = [ + { value: "PIC", label: "입력자" }, + { value: "IDFR", label: "승인자" }, + { value: "MCTPV", label: "검토자" }, +] as const; +const DEFAULT_TEMPLATE_PARAMS = { + sort: "createdAt,desc", + senderKey: "", + inspectionStatus: "APR", + templateMessageType: "BA", + page: 0, + size: 10, +}; + +function getDefaultStatsYear(): string { + return String(new Date().getFullYear() - 1); +} + +function buildTemplateMessage( + templateContent: string, + variables: Record, +): string { + return templateContent.replace(/#?\{([^}]+)\}/g, (_, key: string) => { + const value = variables[key.trim()]; + return value ?? ""; + }); +} + +function getMergedVariables( + recipient: SelectedRecipient, + commonValues: Record, +): Record { + return { + ...commonValues, + ...recipient.variableOverrides, + 수신자명: recipient.userNm, + 전화번호: recipient.telNo, + 시도: recipient.sido, + 시군구: recipient.gugun, + 소속: recipient.deptNm, + }; +} + +function NtitlkSndng() { + const alert = useAlertStore((state) => state.setMessage); + + const [searchStatsYr, setSearchStatsYr] = useState(getDefaultStatsYear()); + const [searchSido, setSearchSido] = useState(""); + const [searchSigungu, setSearchSigungu] = useState(""); + const [searchGubun, setSearchGubun] = useState(""); + + const [appliedParams, setAppliedParams] = useState({ + statsYr: getDefaultStatsYear(), + sewerBzmnCd: "", + gubun: "", + }); + + const [selectedTemplateCode, setSelectedTemplateCode] = useState(""); + const [selectedRecipients, setSelectedRecipients] = useState< + SelectedRecipient[] + >([]); + const [commonVariableValues, setCommonVariableValues] = useState< + Record + >({}); + const [commonVariableMapping, setCommonVariableMapping] = useState< + Record< + string, + | "userNm" + | "sido" + | "gugun" + | "gubunNm" + | "deptNm" + | "jbgdNm" + | "telNo" + | "custom" + | "" + > + >({}); + + const [previewRecipient, setPreviewRecipient] = + useState(null); + const [isPreviewOpen, setIsPreviewOpen] = useState(false); + const [isSending, setIsSending] = useState(false); + const [userListRowSelection, setUserListRowSelection] = + useState({}); + + const { data: templatePageData } = useQuery({ + queryKey: [ + ENDPOINT.NTFTALK_SERVICE.TEMP, + "ntitlk-sndng", + DEFAULT_TEMPLATE_PARAMS, + ], + queryFn: async () => { + const response = await client.get>( + ENDPOINT.NTFTALK_SERVICE.TEMP, + { params: DEFAULT_TEMPLATE_PARAMS }, + ); + return response.data; + }, + }); + + const templates = templatePageData?.content ?? []; + + const { data: templateVariablesData } = useQuery({ + queryKey: [ + ENDPOINT.NTFTALK_SERVICE.ATA_SEND, + "template-variables", + selectedTemplateCode, + ], + queryFn: async () => { + const response = await client.get( + `${ENDPOINT.NTFTALK_SERVICE.ATA_SEND}/template-variables/${selectedTemplateCode}`, + ); + return response.data; + }, + enabled: Boolean(selectedTemplateCode), + }); + + const variableNames = templateVariablesData?.variables ?? []; + + const { data: sidoData } = useStAnlsSidoCombo(); + const { data: sigunguData } = useStAnlsSigunguCombo(searchSido); + const sidoOptions = sidoData ?? EMPTY_ADST_LIST; + const sigunguOptions = sigunguData ?? EMPTY_ADST_LIST; + + const { data: userListResponse, isLoading: isUserListLoading } = + useSewerStUserList(appliedParams); + const userList = (userListResponse as unknown as SewerStUserItem[]) ?? []; + const userListColumns = useMemo[]>( + () => [ + { + id: "select", + header: ({ table }) => ( +
e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} + role="presentation" + > + + table.toggleAllPageRowsSelected(!!value) + } + aria-label="전체 선택" + /> +
+ ), + cell: ({ row }) => ( +
e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} + role="presentation" + > + + row.toggleSelected(!!value) + } + aria-label="행 선택" + /> +
+ ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "userNm", + header: "이름", + }, + { + accessorKey: "sido", + header: "시도", + }, + { + accessorKey: "gugun", + header: "시군구", + }, + { + accessorKey: "gubunNm", + header: "구분", + }, + { + accessorKey: "deptNm", + header: "소속", + }, + { + accessorKey: "jbgdNm", + header: "직급", + }, + { + accessorKey: "telNo", + header: "전화번호", + }, + ], + [], + ); + const userListTable = useReactTable({ + data: userList, + columns: userListColumns, + getCoreRowModel: getCoreRowModel(), + enableRowSelection: true, + enableMultiRowSelection: true, + state: { + rowSelection: userListRowSelection, + }, + onRowSelectionChange: setUserListRowSelection, + getRowId: (row) => `${row.userNm}-${row.telNo}`, + }); + + const selectedTemplate = useMemo( + () => + templates.find( + (template) => template.templateCode === selectedTemplateCode, + ), + [templates, selectedTemplateCode], + ); + + const previewMessage = useMemo(() => { + if (!previewRecipient || !selectedTemplate?.templateContent) return ""; + return buildTemplateMessage( + selectedTemplate.templateContent, + getMergedVariables(previewRecipient, commonVariableValues), + ); + }, [previewRecipient, selectedTemplate, commonVariableValues]); + + const handleTemplateChange = (templateCode: string) => { + setSelectedTemplateCode(templateCode); + setCommonVariableValues({}); + setCommonVariableMapping({}); + setSelectedRecipients((prev) => + prev.map((recipient) => ({ ...recipient, variableOverrides: {} })), + ); + }; + + const handleSearch = () => { + const sewerBzmnCd = + searchSido !== "" + ? searchSigungu !== "" + ? searchSigungu + : searchSido + : ""; + + setAppliedParams({ + statsYr: searchStatsYr, + sewerBzmnCd, + gubun: searchGubun, + }); + setUserListRowSelection({}); + }; + + const handleReset = () => { + const defaultYear = getDefaultStatsYear(); + setSearchStatsYr(defaultYear); + setSearchSido(""); + setSearchSigungu(""); + setSearchGubun(""); + setAppliedParams({ + statsYr: defaultYear, + sewerBzmnCd: "", + gubun: "", + }); + setUserListRowSelection({}); + }; + + const handleRemoveRecipient = (telNo: string) => { + setSelectedRecipients((prev) => + prev.filter((recipient) => recipient.telNo !== telNo), + ); + }; + + const handleAddSelectedToRecipients = () => { + const rows = userListTable.getSelectedRowModel().rows; + if (rows.length === 0) { + alert("추가할 담당자를 선택해주세요."); + return; + } + + setSelectedRecipients((prev) => { + const existingTelNos = new Set(prev.map((r) => r.telNo)); + const newRecipients = rows + .map((row) => row.original) + .filter((recipient) => !existingTelNos.has(recipient.telNo)) + .map((recipient) => ({ + ...recipient, + variableOverrides: {}, + })); + + return [...prev, ...newRecipients]; + }); + + setUserListRowSelection({}); + alert(`${rows.length}명의 담당자를 추가했습니다.`); + }; + + const handleApplyCommonValues = () => { + setSelectedRecipients((prev) => + prev.map((recipient) => { + const newOverrides: Record = {}; + + variableNames.forEach((variableName) => { + const mappingType = commonVariableMapping[variableName]; + + if (!mappingType) return; + + if (mappingType === "custom") { + newOverrides[variableName] = + commonVariableValues[variableName] ?? ""; + } else { + newOverrides[variableName] = + (recipient[ + mappingType as keyof SewerStUserItem + ] as string) ?? ""; + } + }); + + return { + ...recipient, + variableOverrides: { + ...recipient.variableOverrides, + ...newOverrides, + }, + }; + }), + ); + }; + + const handleResetCommonValues = () => { + setCommonVariableMapping({}); + setCommonVariableValues({}); + }; + + const handleRecipientVariableChange = ( + telNo: string, + variableName: string, + value: string, + ) => { + setSelectedRecipients((prev) => + prev.map((recipient) => + recipient.telNo === telNo + ? { + ...recipient, + variableOverrides: { + ...recipient.variableOverrides, + [variableName]: value, + }, + } + : recipient, + ), + ); + }; + + const handlePreview = (recipient: SelectedRecipient) => { + if (!selectedTemplate) { + alert("알림톡 템플릿을 먼저 선택해주세요."); + return; + } + setPreviewRecipient(recipient); + setIsPreviewOpen(true); + }; + + const handleSend = async () => { + if (!selectedTemplateCode) { + alert("알림톡 템플릿을 선택해주세요."); + return; + } + if (selectedRecipients.length === 0) { + alert("발송할 담당자를 1명 이상 선택해주세요."); + return; + } + + const missingVariable = selectedRecipients.some((recipient) => + variableNames.some((name) => { + const mergedValue = + recipient.variableOverrides[name] ?? + commonVariableValues[name] ?? + ""; + return mergedValue.trim() === ""; + }), + ); + if (missingVariable) { + alert("필수 변수값이 입력되지 않은 발송 대상이 있습니다."); + return; + } + + const payload: BusinessSendRequest = { + templateCode: selectedTemplateCode, + callback: "032-590-4000", + recipients: selectedRecipients.map((recipient) => + getMergedVariables(recipient, commonVariableValues), + ), + }; + + try { + setIsSending(true); + const response = await client.post( + `${ENDPOINT.NTFTALK_SERVICE.ATA_SEND}/business`, + payload, + ); + const data = response.data; + if (!data.success) { + alert(data.message || "알림톡 발송 요청에 실패했습니다."); + return; + } + + alert( + data.message || + `${data.sentCount}건의 알림톡 발송이 요청되었습니다.`, + ); + setSelectedRecipients([]); + setCommonVariableValues({}); + } catch { + alert("알림톡 발송 중 오류가 발생했습니다."); + } finally { + setIsSending(false); + } + }; + + return ( +
+
+
+
+

+ 알림톡 템플릿 선택 +

+
+ +
+
+ +
+

미리보기

+
+
+
+ + 하수도통계시스템 + +
+ 알림톡 도착 +
+
+ {selectedTemplate?.templateContent ?? + "알림톡 템플릿을 선택하면 미리보기 메시지가 표시됩니다."} +
+
+
+
+
+
+ +
+

통계 담당자 목록

+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+ {isUserListLoading ? ( +
+
+ +
+
+ ) : userList.length === 0 ? ( +
+ 조회된 담당자가 없습니다. +
+ ) : ( + + )} +
+ {!isUserListLoading && userList.length > 0 && ( +
+

+ 선택{" "} + + { + userListTable.getSelectedRowModel().rows + .length + } + 명 + {" "} + / 전체{" "} + + {userList.length}명 + +

+ +
+ )} +
+
+ +
+
+

발송 목록

+
+ + {selectedRecipients.length}명 + + +
+
+ + {variableNames.length > 0 && ( +
+
+

+ 변수 일괄 적용 +

+
+ + +
+
+
+ {variableNames.map((variableName) => ( +
+ + + {commonVariableMapping[variableName] === + "custom" && ( + + setCommonVariableValues( + (prev) => ({ + ...prev, + [variableName]: + event.target.value, + }), + ) + } + /> + )} +
+ ))} +
+
+ )} + +
+ {selectedRecipients.length === 0 ? ( +

+ 선택된 발송 대상이 없습니다. +

+ ) : ( +
+ + + + + 이름 + + + 전화번호 + + {/* + 시도 + + + 시군구 + + + 소속 + */} + {variableNames.map((variableName) => ( + + #{variableName} + + ))} + + 액션 + + + + + {selectedRecipients.map((recipient) => ( + + + {recipient.userNm} + + + {recipient.telNo} + + {/* + {recipient.sido} + + + {recipient.gugun} + + + {recipient.deptNm} + */} + {variableNames.map( + (variableName) => ( + + + handleRecipientVariableChange( + recipient.telNo, + variableName, + event.target + .value, + ) + } + className="w-full min-w-[150px]" + /> + + ), + )} + +
+ + +
+
+
+ ))} +
+
+
+ )} +
+
+ + + + + 알림톡 미리보기 + + 수신자: {previewRecipient?.userNm ?? "-"} /{" "} + {previewRecipient?.telNo ?? "-"} + + +
+
+
+ + 하수도통계시스템 + +
+ 알림톡 도착 +
+
+ {previewMessage || + "메시지를 생성할 수 없습니다."} +
+
+
+
+ + + + + +
+
+
+ ); +} + +export default NtitlkSndng; diff --git a/apps/st/src/entities/statistics/api/queries.ts b/apps/st/src/entities/statistics/api/queries.ts index 1ca1c6635..30a6437ca 100644 --- a/apps/st/src/entities/statistics/api/queries.ts +++ b/apps/st/src/entities/statistics/api/queries.ts @@ -9,6 +9,9 @@ import type { StatisticsVerificationSubFilterState, } from "@/entities/statistics/model/statistics-verification-report/statistics-verification-report-types"; +import type { SewerStUserSearchParams } from "../model/sewer-st-user-interface"; + +import { sewerStUserApi } from "./sewer-st-user-service"; import { stAnlsMstApi } from "./statistics-analysis-master-api"; import { stAnlsRsltApi } from "./statistics-analysis-result-api"; @@ -154,3 +157,15 @@ export const useStatsVrfcRsltData = ( refetchOnWindowFocus: false, }); }; + +export const useSewerStUserList = (params: SewerStUserSearchParams) => { + return useQuery({ + queryKey: [ + ENDPOINT.STATVERIFY_SERVICE.SEWERST_USER, + "user-list", + params, + ], + queryFn: () => sewerStUserApi.getUserList(params), + enabled: Boolean(params.statsYr), + }); +}; diff --git a/apps/st/src/entities/statistics/api/sewer-st-user-service.ts b/apps/st/src/entities/statistics/api/sewer-st-user-service.ts new file mode 100644 index 000000000..fb0184820 --- /dev/null +++ b/apps/st/src/entities/statistics/api/sewer-st-user-service.ts @@ -0,0 +1,29 @@ +import { stClient } from "@/shared/api/client"; +import { ENDPOINT } from "@/shared/config"; + +import type { + SewerStUserListResponse, + SewerStUserSearchParams, +} from "../model/sewer-st-user-interface"; + +/** + * 하수도 통계 사용자 목록 API 서비스 + */ +export class SewerStUserApiService { + /** + * 하수도 통계 제출현황 사용자 목록 조회 + * @param params 검색 조건 (statsYr, sewerBzmnCd, gubun) + * @returns 사용자 목록 (입력자·승인자·검토자) + */ + public async getUserList( + params: SewerStUserSearchParams, + ): Promise { + const response = await stClient.get( + `${ENDPOINT.STATVERIFY_SERVICE.SEWERST_USER}/user-list`, + { params }, + ); + return response.data; + } +} + +export const sewerStUserApi = new SewerStUserApiService(); diff --git a/apps/st/src/entities/statistics/index.ts b/apps/st/src/entities/statistics/index.ts index ff37b0535..28a485e76 100644 --- a/apps/st/src/entities/statistics/index.ts +++ b/apps/st/src/entities/statistics/index.ts @@ -9,11 +9,13 @@ export { dashboardApi } from "./api/dashboard/dashboard-service"; export { useScheduleInfo } from "./api/dashboard/schedule-info-queries"; export { scheduleInfoApi } from "./api/dashboard/schedule-info-service"; export { + useSewerStUserList, useStAnlsSidoCombo, useStAnlsSigunguCombo, useStatsVrfcRptData, useStatsVrfcRsltData, } from "./api/queries"; +export { sewerStUserApi } from "./api/sewer-st-user-service"; export { statisticsApprovalApi } from "./api/statistics-approval-service"; export { type AnalysisHistoryListApiResponse, @@ -62,6 +64,11 @@ export { } from "./model/imr/imr-interface"; export { imrVerificationHistoryColumns } from "./model/imr/imr-verification-history-columns"; export { type StatsInputStatusList } from "./model/input/statistics-input-status-interface"; +export { + type SewerStUserItem, + type SewerStUserListResponse, + type SewerStUserSearchParams, +} from "./model/sewer-st-user-interface"; export { type StAnlsDtlListApiResponse, type StAnlsDtlResponse, diff --git a/apps/st/src/entities/statistics/model/sewer-st-user-interface.ts b/apps/st/src/entities/statistics/model/sewer-st-user-interface.ts new file mode 100644 index 000000000..b9af09c1c --- /dev/null +++ b/apps/st/src/entities/statistics/model/sewer-st-user-interface.ts @@ -0,0 +1,46 @@ +/** + * 하수도 통계 제출현황 사용자 목록 조회 인터페이스 + */ + +/** + * 하수도 통계 사용자 목록 검색 조건 + */ +export interface SewerStUserSearchParams { + /** 통계연도 (예: "2024") */ + statsYr?: string; + /** 하수도 사업자코드 (예: "4145000000") */ + sewerBzmnCd?: string; + /** 구분 코드 (PIC: 입력자, IDFR: 승인자, MCTPV: 검토자, null: 전체) */ + gubun?: string; +} + +/** + * 하수도 통계 사용자 목록 한 행 + */ +export interface SewerStUserItem { + /** 시도 */ + sido: string; + /** 시군구 */ + gugun: string; + /** 이름 */ + userNm: string; + /** 전화번호 */ + telNo: string; + /** 소속 */ + deptNm: string; + /** 직급 */ + jbgdNm: string; + /** 구분 코드 (PIC: 입력자, IDFR: 승인자, MCTPV: 검토자) */ + gubun: string; + /** 구분명 (입력자 / 승인자 / 검토자) */ + gubunNm: string; +} + +/** + * 하수도 통계 사용자 목록 조회 API 응답 + */ +export interface SewerStUserListResponse { + status: string; + message?: string; + data?: SewerStUserItem[]; +} diff --git a/apps/st/src/shared/config/api.ts b/apps/st/src/shared/config/api.ts index 318bc6656..0cf732396 100644 --- a/apps/st/src/shared/config/api.ts +++ b/apps/st/src/shared/config/api.ts @@ -76,6 +76,10 @@ export const ENDPOINT = { APPROVAL: "/st-management-service/api/stats", DASHBOARD: "/st-management-service/api/v1/dashboard", }, + NTFTALK_SERVICE: { + TEMP: "/ntftalk-service/api/v1/ntftalk-temp", + ATA_SEND: "/ntftalk-service/api/v1/ata/send", + }, STATVERIFY_SERVICE: { VRFCTGT_MNG: "/statverify-service/api/v1/vrfc-trgt-mng", ANALYSIS: "/statverify-service/api/v1/stat-verify/analysis", @@ -83,6 +87,7 @@ export const ENDPOINT = { ST_ANLS_DTL: "/statverify-service/api/v1/st-anls-dtl", ST_ANLS_RSLT: "/statverify-service/api/v1/st-anls-rslt", IMR: "/statverify-service/api/v1/imr/outlier/detect", + SEWERST_USER: "/statverify-service/api/v1/sewerst", }, }; From 4d23e07be054a36712bf59d31362b4f5272c2a2b Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 7 May 2026 13:35:27 +0900 Subject: [PATCH 3/3] =?UTF-8?q?[st]=20feat:=20=EC=95=8C=EB=A6=BC=ED=86=A1?= =?UTF-8?q?=20=EB=B0=9C=EC=86=A1=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../statistics/management/NoticeTalkSending.tsx | 8 ++------ .../pages/statistics/ui/panels/unsubmit-panel.tsx | 12 +++++++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/apps/st/src/components/statistics/management/NoticeTalkSending.tsx b/apps/st/src/components/statistics/management/NoticeTalkSending.tsx index 592791cec..cdb060e30 100644 --- a/apps/st/src/components/statistics/management/NoticeTalkSending.tsx +++ b/apps/st/src/components/statistics/management/NoticeTalkSending.tsx @@ -113,11 +113,7 @@ function getMergedVariables( return { ...commonValues, ...recipient.variableOverrides, - 수신자명: recipient.userNm, - 전화번호: recipient.telNo, - 시도: recipient.sido, - 시군구: recipient.gugun, - 소속: recipient.deptNm, + TEL_NUM: recipient.telNo, }; } @@ -375,7 +371,6 @@ function NtitlkSndng() { }); setUserListRowSelection({}); - alert(`${rows.length}명의 담당자를 추가했습니다.`); }; const handleApplyCommonValues = () => { @@ -408,6 +403,7 @@ function NtitlkSndng() { }; }), ); + setCommonVariableMapping({}); }; const handleResetCommonValues = () => { diff --git a/apps/st/src/pages/statistics/ui/panels/unsubmit-panel.tsx b/apps/st/src/pages/statistics/ui/panels/unsubmit-panel.tsx index c4981be02..a7eeb6551 100644 --- a/apps/st/src/pages/statistics/ui/panels/unsubmit-panel.tsx +++ b/apps/st/src/pages/statistics/ui/panels/unsubmit-panel.tsx @@ -49,14 +49,20 @@ export function UnSubmitPanel({ params }: UnSubmitPanelProps) {
) : (
- {data.statusList.map((item) => ( + {data.statusList.map((item, i) => (

{item.statsNm}

+ {params?.dtlInstCd !== + "ROLE_USER05" && ( + + {item.sidoSggNm} + + )}
))}