-
- 하수도정보시스템
+
+ 유역하수도통합플랫폼
- CMS 통합관리시스템
+ 하수도플랫폼 통합관리(CMS)시스템
-
하수도정보시스템 CMS 통합관리시스템에 오신 것을 환영합니다.
+
유역하수도통합플랫폼 통합관리(CMS)시스템에 오신 것을 환영합니다.
diff --git a/apps/cs/src/pages/ntftalk-temp/model/ntftalk-var-additional-schema.ts b/apps/cs/src/pages/ntftalk-temp/model/ntftalk-var-additional-schema.ts
new file mode 100644
index 000000000..e2368fe77
--- /dev/null
+++ b/apps/cs/src/pages/ntftalk-temp/model/ntftalk-var-additional-schema.ts
@@ -0,0 +1,17 @@
+import { z } from "zod";
+
+export const ntftalkVarAdditionalSchema = z.object({
+ draftVars: z.array(
+ z.object({
+ varCd: z.string().min(1, "변수코드를 입력하세요."),
+ varNm: z.string().min(1, "변수명을 입력하세요."),
+ varDesc: z.string().default(""),
+ sender: z.string().default(""),
+ regr: z.string().default(""),
+ }),
+ ),
+});
+
+export type NtftalkVarAdditionalFormData = z.infer<
+ typeof ntftalkVarAdditionalSchema
+>;
diff --git a/apps/cs/src/pages/ntftalk-temp/model/ntftalk-var-batch-payload.ts b/apps/cs/src/pages/ntftalk-temp/model/ntftalk-var-batch-payload.ts
new file mode 100644
index 000000000..91d715188
--- /dev/null
+++ b/apps/cs/src/pages/ntftalk-temp/model/ntftalk-var-batch-payload.ts
@@ -0,0 +1,52 @@
+import { z } from "zod";
+
+import type { NtftalkVarSavePayload } from "@/entities/ntftalk";
+
+import type { NtftalkVarGridRow } from "./ntftalk-var-grid";
+
+export const varDraftRowSchema = z.object({
+ varCd: z.string().min(1),
+ varNm: z.string().min(1),
+ varDesc: z.string().optional(),
+ sender: z.string().optional(),
+});
+
+export function buildNtftalkVarBatchPayload(input: {
+ pendingDeleteVarCds: ReadonlySet;
+ draftVars?: NtftalkVarGridRow[];
+ regr?: string;
+}): NtftalkVarSavePayload[] {
+ const { draftVars, pendingDeleteVarCds, regr } = input;
+ const payloads: NtftalkVarSavePayload[] = [];
+
+ if (!draftVars) {
+ for (const varCd of pendingDeleteVarCds) {
+ payloads.push({ mode: "D", varCd });
+ }
+ return payloads;
+ }
+
+ for (const d of draftVars) {
+ const parsed = varDraftRowSchema.safeParse({
+ varCd: d.varCd,
+ varNm: d.varNm,
+ varDesc: d.varDesc || undefined,
+ sender: d.sender || undefined,
+ });
+ if (!parsed.success) {
+ throw new Error(
+ parsed.error.issues[0]?.message ?? "유효하지 않은 입력입니다.",
+ );
+ }
+ payloads.push({
+ mode: "C",
+ varCd: parsed.data.varCd,
+ varNm: parsed.data.varNm,
+ varDesc: parsed.data.varDesc,
+ sender: parsed.data.sender,
+ regr,
+ });
+ }
+
+ return payloads;
+}
diff --git a/apps/cs/src/pages/ntftalk-temp/model/ntftalk-var-grid.ts b/apps/cs/src/pages/ntftalk-temp/model/ntftalk-var-grid.ts
index aa4892b1f..914130e09 100644
--- a/apps/cs/src/pages/ntftalk-temp/model/ntftalk-var-grid.ts
+++ b/apps/cs/src/pages/ntftalk-temp/model/ntftalk-var-grid.ts
@@ -2,13 +2,11 @@ import type { NtftalkVar } from "@/entities/ntftalk";
export interface NtftalkVarGridRow extends NtftalkVar {
clientId?: string;
- isDraft?: boolean;
}
export function createEmptyVarDraft(clientId: string): NtftalkVarGridRow {
return {
clientId,
- isDraft: true,
varCd: "",
varNm: "",
varDesc: "",
diff --git a/apps/cs/src/pages/ntftalk-temp/model/template-table-columns.tsx b/apps/cs/src/pages/ntftalk-temp/model/template-table-columns.tsx
index 29df948ad..e5d8a00fe 100644
--- a/apps/cs/src/pages/ntftalk-temp/model/template-table-columns.tsx
+++ b/apps/cs/src/pages/ntftalk-temp/model/template-table-columns.tsx
@@ -1,13 +1,18 @@
+import { TooltipContent } from "@radix-ui/react-tooltip";
import type { ColumnDef, Row, Table } from "@tanstack/react-table";
-import { Checkbox } from "@common/components";
-import type { NtftalkTemp } from "@/entities/ntftalk";
+
+import { InfoTooltip } from "@/shared/ui/info-tooltip";
+import type { NtftalkBiztalkSender, NtftalkTemp } from "@/entities/ntftalk";
+import { Checkbox, Tooltip, TooltipTrigger } from "@common/components";
function formatButtons(row: NtftalkTemp): string {
if (!row.buttons?.length) return "—";
return row.buttons.map((b) => b.name).join(", ");
}
-export function createTemplateColumns(): ColumnDef[] {
+export function createTemplateColumns(
+ senderData: NtftalkBiztalkSender[],
+): ColumnDef[] {
return [
{
id: "select",
@@ -23,7 +28,9 @@ export function createTemplateColumns(): ColumnDef[] {
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
- onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+ onCheckedChange={(value) =>
+ table.toggleAllPageRowsSelected(!!value)
+ }
aria-label="전체 선택"
/>
@@ -48,6 +55,12 @@ export function createTemplateColumns(): ColumnDef
[] {
{
accessorKey: "senderKey",
header: "@채널",
+ cell: ({ row }) => {
+ const sender = senderData.find(
+ (s) => s.senderKey === row.original.senderKey,
+ );
+ return sender ? sender.name : "—";
+ },
},
{
accessorKey: "templateCode",
@@ -58,7 +71,7 @@ export function createTemplateColumns(): ColumnDef[] {
header: "템플릿명",
},
{
- accessorKey: "templateMessageType",
+ accessorKey: "templateMessageTypeNm",
header: "메시지유형",
},
{
@@ -68,14 +81,30 @@ export function createTemplateColumns(): ColumnDef[] {
const v = getValue() as string;
if (!v) return "—";
return (
-
- {v}
-
+
+
+
+
+ {v}
+
+
+
+
+ {v.split("\n").map((line, idx) => (
+
+ {line}
+ {idx !== v.split("\n").length - 1 &&
}
+
+ ))}
+
+
+
+
);
},
},
{
- accessorKey: "templateEmphasizeType",
+ accessorKey: "templateEmphasizeTypeNm",
header: "강조유형",
},
{
@@ -84,11 +113,11 @@ export function createTemplateColumns(): ColumnDef[] {
cell: ({ row }) => formatButtons(row.original),
},
{
- accessorKey: "inspectionStatus",
+ accessorKey: "inspectionStatusNm",
header: "검수상태",
},
{
- accessorKey: "status",
+ accessorKey: "statusNm",
header: "상태",
},
{
diff --git a/apps/cs/src/pages/ntftalk-temp/model/var-table-columns.tsx b/apps/cs/src/pages/ntftalk-temp/model/var-table-columns.tsx
index 3b0d39ced..19e02e78d 100644
--- a/apps/cs/src/pages/ntftalk-temp/model/var-table-columns.tsx
+++ b/apps/cs/src/pages/ntftalk-temp/model/var-table-columns.tsx
@@ -1,5 +1,7 @@
import type { ColumnDef, Row, Table } from "@tanstack/react-table";
-import { Checkbox, Input } from "@common/components";
+
+import { Checkbox } from "@common/components";
+
import type { NtftalkVarGridRow } from "./ntftalk-var-grid";
export interface VarColumnHandlers {
@@ -10,9 +12,7 @@ export interface VarColumnHandlers {
) => void;
}
-export function createVarColumns(
- handlers: VarColumnHandlers,
-): ColumnDef[] {
+export function createVarColumns(): ColumnDef[] {
return [
{
id: "select",
@@ -28,7 +28,9 @@ export function createVarColumns(
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
- onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
+ onCheckedChange={(value) =>
+ table.toggleAllPageRowsSelected(!!value)
+ }
aria-label="전체 선택"
/>
@@ -61,62 +63,14 @@ export function createVarColumns(
{
accessorKey: "varCd",
header: "변수코드",
- cell: ({ row }) => {
- const r = row.original;
- if (r.isDraft && r.clientId) {
- return (
-
- handlers.onDraftFieldChange(r.clientId!, "varCd", e.target.value)
- }
- onClick={(e) => e.stopPropagation()}
- />
- );
- }
- return r.varCd;
- },
},
{
accessorKey: "varNm",
header: "변수명",
- cell: ({ row }) => {
- const r = row.original;
- if (r.isDraft && r.clientId) {
- return (
-
- handlers.onDraftFieldChange(r.clientId!, "varNm", e.target.value)
- }
- onClick={(e) => e.stopPropagation()}
- />
- );
- }
- return r.varNm;
- },
},
{
accessorKey: "varDesc",
header: "설명",
- cell: ({ row }) => {
- const r = row.original;
- if (r.isDraft && r.clientId) {
- return (
-
- handlers.onDraftFieldChange(r.clientId!, "varDesc", e.target.value)
- }
- onClick={(e) => e.stopPropagation()}
- />
- );
- }
- return r.varDesc ?? "—";
- },
},
{
accessorKey: "regDt",
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 787f33f93..ac2cbfc6f 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
@@ -1,676 +1,13 @@
"use client";
-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,
- Input,
- Label,
- LoadingWithAnimation,
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@common/components";
-
-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";
-
-const ALL = "__ALL__";
-
-const varDraftSaveSchema = z.object({
- varCd: z.string().min(1),
- varNm: z.string().min(1),
- varDesc: z.string().optional(),
- sender: z.string().optional(),
-});
-
-interface VarDraftFormValues {
- draftVars: NtftalkVarGridRow[];
-}
+import { NtftalkTempTemplateSection } from "./ntftalk-temp-template-section";
+import { NtftalkVarSection } from "./ntftalk-temp-var-section";
function NtftalkTempPage() {
- const queryClient = useQueryClient();
- const alert = useAlertStore((s) => s.setMessage);
- const { confirm } = useConfirm();
-
- const [templateCodeInput, setTemplateCodeInput] = useState("");
- const [templateNameInput, setTemplateNameInput] = useState("");
- const [templateContentInput, setTemplateContentInput] = useState("");
- const [senderKeyFilter, setSenderKeyFilter] = useState(ALL);
- const [inspectionFilter, setInspectionFilter] = useState(ALL);
- const [messageTypeFilter, setMessageTypeFilter] = useState(ALL);
-
- const [searchParams, setSearchParams] = useState
({
- sort: "createdAt,desc",
- });
- const [pagination, setPagination] = useState({
- pageIndex: 0,
- pageSize: 10,
- });
- const [rowSelection, setRowSelection] = useState({});
-
- const [varPagination, setVarPagination] = useState({
- pageIndex: 0,
- pageSize: 10,
- });
- const [varSearchParams] = useState({
- sort: "regDt,desc",
- });
- const [varRowSelection, setVarRowSelection] = useState({});
- const varDraftForm = useForm({
- defaultValues: { draftVars: [] },
- });
- const { control, setValue } = varDraftForm;
- const { append, remove, replace } = useFieldArray({
- control,
- name: "draftVars",
- });
- const draftVars = useWatch({ control, name: "draftVars" }) ?? [];
-
- const [detailCode, setDetailCode] = useState(null);
- const [detailOpen, setDetailOpen] = useState(false);
- const [registOpen, setRegistOpen] = useState(false);
-
- const tempQuery = useQuery({
- queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_TEMP, searchParams, pagination],
- queryFn: () =>
- NtftalkTempApi.getNtftalkTempPage({
- ...searchParams,
- page: pagination.pageIndex,
- size: pagination.pageSize,
- }),
- placeholderData: keepPreviousData,
- });
-
- const varQuery = useQuery({
- queryKey: [
- ENDPOINT.NTFTALK_SERVICE.NTFTALK_VAR,
- varSearchParams,
- varPagination,
- ],
- queryFn: () =>
- NtftalkVarApi.getNtftalkVarPage({
- ...varSearchParams,
- page: varPagination.pageIndex,
- size: varPagination.pageSize,
- }),
- placeholderData: keepPreviousData,
- });
-
- const senderQuery = useQuery({
- queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_BIZTALK, "sender"],
- queryFn: () => NtftalkBiztalkApi.getSender(),
- });
-
- const senderKeyOptions = useMemo(() => {
- if (senderQuery.data) return [senderQuery.data];
- return [];
- }, [senderQuery.data]);
-
- const inspectionOptions = useMemo(() => {
- const set = new Set();
- (tempQuery.data?.content ?? []).forEach((r) => {
- if (r.inspectionStatus) set.add(r.inspectionStatus);
- });
- return Array.from(set).sort();
- }, [tempQuery.data]);
-
- const messageTypeOptions = useMemo(() => {
- const set = new Set();
- (tempQuery.data?.content ?? []).forEach((r) => {
- if (r.templateMessageType) set.add(r.templateMessageType);
- });
- return Array.from(set).sort();
- }, [tempQuery.data]);
-
- const templateColumns = useMemo(() => createTemplateColumns(), []);
-
- const {
- tableComponent: templateTableComponent,
- tableInstance: templateTable,
- } = useDataTable({
- query: tempQuery,
- tableOptions: {
- columns: templateColumns,
- state: { pagination, rowSelection },
- manualPagination: true,
- enableRowSelection: true,
- enableMultiRowSelection: true,
- getCoreRowModel: getCoreRowModel(),
- onPaginationChange: setPagination,
- onRowSelectionChange: setRowSelection,
- },
- dataTableOptions: {
- viewPagination: true,
- onRowClick: (row: NtftalkTemp) => {
- setDetailCode(row.templateCode);
- setDetailOpen(true);
- },
- },
- });
-
- const draftIndexByClientId = useMemo(() => {
- const indexByClientId = new Map();
- draftVars.forEach((row, index) => {
- if (row.clientId) indexByClientId.set(row.clientId, index);
- });
- return indexByClientId;
- }, [draftVars]);
-
- const onDraftFieldChange = useCallback(
- (
- clientId: string,
- field: "varCd" | "varNm" | "varDesc" | "sender",
- value: string,
- ) => {
- const draftIndex = draftIndexByClientId.get(clientId);
- if (draftIndex === undefined) return;
- setValue(`draftVars.${draftIndex}.${field}`, value, {
- shouldDirty: true,
- });
- },
- [draftIndexByClientId, setValue],
- );
-
- const varColumns = useMemo(
- () => createVarColumns({ onDraftFieldChange }),
- [onDraftFieldChange],
- );
-
- const varTableRows = useMemo((): NtftalkVarGridRow[] => {
- const server = varQuery.data?.content ?? [];
- if (varPagination.pageIndex === 0) return [...server, ...draftVars];
- return server;
- }, [varQuery.data, draftVars, varPagination.pageIndex]);
-
- const varTable = useReactTable({
- data: varTableRows,
- columns: varColumns,
- state: { pagination: varPagination, rowSelection: varRowSelection },
- manualPagination: true,
- rowCount: varQuery.data?.totalElements ?? 0,
- pageCount: varQuery.data?.totalPages ?? 0,
- enableRowSelection: true,
- enableMultiRowSelection: true,
- getCoreRowModel: getCoreRowModel(),
- onPaginationChange: setVarPagination,
- onRowSelectionChange: setVarRowSelection,
- getRowId: (row) =>
- row.isDraft && row.clientId
- ? `draft:${row.clientId}`
- : `srv:${row.varCd}`,
- });
-
- const buildSearchParamsFromInputs = (): NtftalkTempSearchParams => ({
- sort: searchParams.sort ?? "createdAt,desc",
- templateCode: templateCodeInput.trim() || undefined,
- templateName: templateNameInput.trim() || undefined,
- templateContent: templateContentInput.trim() || undefined,
- senderKey: senderKeyFilter === ALL ? undefined : senderKeyFilter,
- inspectionStatus: inspectionFilter === ALL ? undefined : inspectionFilter,
- templateMessageType:
- messageTypeFilter === ALL ? undefined : messageTypeFilter,
- });
-
- const handleSearch = (e: React.SubmitEvent) => {
- e.preventDefault();
- setPagination((p) => ({ ...p, pageIndex: 0 }));
- setSearchParams(buildSearchParamsFromInputs());
- };
-
- const handleReset = () => {
- setTemplateCodeInput("");
- setTemplateNameInput("");
- setTemplateContentInput("");
- setSenderKeyFilter(ALL);
- setInspectionFilter(ALL);
- setMessageTypeFilter(ALL);
- setPagination((p) => ({ ...p, pageIndex: 0 }));
- setSearchParams({ sort: "createdAt,desc" });
- };
-
- const syncTemplatesMutation = useMutationWrapper({
- mutationFn: () => NtftalkBiztalkApi.syncTemplates({ senderKeyType: "S" }),
- onSuccess: async (data) => {
- await Promise.all([
- queryClient.invalidateQueries({
- queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_TEMP],
- }),
- queryClient.invalidateQueries({
- queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_BIZTALK, "sender"],
- }),
- ]);
- alert(data.message);
- },
- });
-
- const handleRefreshTemplates = () => {
- syncTemplatesMutation.mutate();
- };
-
- const deleteTemplatesMutation = useMutationWrapper({
- mutationFn: async (codes: string[]) => {
- for (const code of codes) {
- await NtftalkTempApi.deleteNtftalkTemp(code);
- }
- },
- onSuccess: async () => {
- await queryClient.invalidateQueries({
- queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_TEMP],
- });
- setRowSelection({});
- alert("삭제되었습니다.");
- },
- });
-
- const handleDeleteSelectedTemplates = async () => {
- const rows = templateTable.getSelectedRowModel().rows;
- if (rows.length === 0) {
- alert("삭제할 템플릿을 선택해 주세요.");
- return;
- }
- if (
- !(await confirm(
- `선택한 ${rows.length}건의 템플릿을 삭제(비표시) 처리하시겠습니까?`,
- ))
- )
- return;
- deleteTemplatesMutation.mutate(rows.map((r) => r.original.templateCode));
- };
-
- const inspectionRequestMutation = useMutationWrapper({
- mutationFn: (requestList: NtftalkBiztalkInspectionRequestItem[]) =>
- NtftalkBiztalkApi.requestInspection(requestList),
- onSuccess: async (data) => {
- await queryClient.invalidateQueries({
- queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_TEMP],
- });
- setRowSelection({});
- alert(data.message);
- },
- });
-
- const handleInspectionRequest = async () => {
- const rows = templateTable.getSelectedRowModel().rows;
- if (rows.length === 0) {
- alert("검수요청할 템플릿을 선택해 주세요.");
- return;
- }
-
- const requestList: NtftalkBiztalkInspectionRequestItem[] = [];
- for (const row of rows) {
- const { senderKey, templateCode, senderKeyType } = row.original;
- if (!senderKey || !templateCode) {
- alert("선택 항목에 발신키 또는 템플릿코드가 없습니다.");
- return;
- }
- requestList.push({
- senderKey,
- templateCode,
- senderKeyType: senderKeyType || "S",
- });
- }
-
- if (
- !(await confirm(`선택한 ${requestList.length}건을 검수요청하시겠습니까?`))
- )
- return;
- inspectionRequestMutation.mutate(requestList);
- };
-
- const handleSaveTemplateGrid = () => {
- alert("템플릿 그리드 셀 편집·일괄 저장은 별도 구현 시 연동합니다.");
- };
-
- const handleAddVarRow = () => {
- append(createEmptyVarDraft(crypto.randomUUID()));
- setVarPagination((p) => ({ ...p, pageIndex: 0 }));
- };
-
- const deleteVarsMutation = useMutationWrapper({
- mutationFn: async (codes: string[]) => {
- for (const code of codes) {
- await NtftalkVarApi.deleteNtftalkVar(code);
- }
- },
- onSuccess: async () => {
- await queryClient.invalidateQueries({
- queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_VAR],
- });
- setVarRowSelection({});
- alert("삭제되었습니다.");
- },
- });
-
- const handleDeleteSelectedVars = async () => {
- const selected = varTable.getSelectedRowModel().rows;
- if (selected.length === 0) {
- alert("삭제할 변수를 선택해 주세요.");
- return;
- }
- if (!(await confirm(`선택한 ${selected.length}건을 삭제하시겠습니까?`)))
- return;
-
- const serverCodes: string[] = [];
- const draftIndexes: number[] = [];
- selected.forEach((r) => {
- const o = r.original;
- if (o.isDraft && o.clientId) {
- const draftIndex = draftIndexByClientId.get(o.clientId);
- if (draftIndex !== undefined) draftIndexes.push(draftIndex);
- } else if (o.varCd) serverCodes.push(o.varCd);
- });
-
- if (draftIndexes.length)
- remove([...new Set(draftIndexes)].sort((a, b) => b - a));
-
- if (serverCodes.length) deleteVarsMutation.mutate(serverCodes);
- };
-
- const saveVarsMutation = useMutationWrapper({
- mutationFn: async (drafts: NtftalkVarGridRow[]) => {
- const regr = getCookie(AUTH_USER_ID) ?? undefined;
- for (const d of drafts) {
- const parsed = varDraftSaveSchema.safeParse({
- varCd: d.varCd,
- varNm: d.varNm,
- varDesc: d.varDesc || undefined,
- sender: d.sender || undefined,
- });
- if (!parsed.success)
- throw new Error(
- parsed.error.issues[0]?.message ?? "유효하지 않은 입력입니다.",
- );
- await NtftalkVarApi.saveNtftalkVar({
- varCd: parsed.data.varCd,
- varNm: parsed.data.varNm,
- varDesc: parsed.data.varDesc,
- sender: parsed.data.sender,
- regr,
- });
- }
- },
- onSuccess: async () => {
- await queryClient.invalidateQueries({
- queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_VAR],
- });
- replace([]);
- setVarRowSelection({});
- alert("저장되었습니다.");
- },
- });
-
- const handleSaveVars = () => {
- if (draftVars.length === 0) {
- alert("저장할 신규 변수 행이 없습니다. 변수추가로 행을 추가하세요.");
- return;
- }
- saveVarsMutation.mutate(draftVars);
- };
-
- const varTableComponent = (() => {
- if (varQuery.isLoading)
- return (
-
-
-
- );
- if (varQuery.isError) return ;
- return ;
- })();
-
return (
-
-
-
-
-
- 알림톡 템플릿 관리
-
-
-
-
-
-
-
-
-
-
{templateTableComponent}
-
-
-
-
-
변수 관리
-
-
-
-
-
-
-
{varTableComponent}
-
-
-
{
- setDetailOpen(o);
- if (!o) setDetailCode(null);
- }}
- />
-
+
+
);
}
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 8a537e7a2..f989a0165 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
@@ -109,17 +109,10 @@ function NtftalkTempRegistDialog({
enabled: isVarDialogOpen,
});
- const varColumns = useMemo(
- () =>
- createVarColumns({
- onDraftFieldChange: () => {},
- }),
- [],
- );
const { tableComponent, tableInstance } = useDataTable({
query: varQuery,
tableOptions: {
- columns: varColumns,
+ columns: createVarColumns(),
state: { pagination: varPagination, rowSelection: varRowSelection },
manualPagination: true,
enableRowSelection: true,
diff --git a/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-template-section.tsx b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-template-section.tsx
new file mode 100644
index 000000000..fea00f6d9
--- /dev/null
+++ b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-template-section.tsx
@@ -0,0 +1,455 @@
+"use client";
+
+import { useMemo, useState } from "react";
+
+import { useConfirm } from "@frontend-opensource/use-react-hooks";
+import {
+ keepPreviousData,
+ useQuery,
+ useQueryClient,
+ type UseQueryResult,
+} from "@tanstack/react-query";
+import {
+ getCoreRowModel,
+ type PaginationState,
+ type RowSelectionState,
+ type Table,
+ useReactTable,
+} from "@tanstack/react-table";
+
+import { ENDPOINT } from "@/shared/config";
+import { useAlertStore, useMutationWrapper } from "@/shared/lib";
+import { NoData } from "@/shared/ui/no-data";
+import { CodeSelect } from "@/entities/code";
+import {
+ NtftalkBiztalkApi,
+ type NtftalkBiztalkInspectionRequestItem,
+ type NtftalkTemp,
+ NtftalkTempApi,
+ type NtftalkTempPageList,
+ type NtftalkTempSearchParams,
+} from "@/entities/ntftalk";
+import {
+ Button,
+ DataTable,
+ Input,
+ Label,
+ LoadingWithAnimation,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@common/components";
+
+import { createTemplateColumns } from "../model/template-table-columns";
+
+import { NtftalkTempDetailDialog } from "./ntftalk-temp-detail-dialog";
+import { NtftalkTempRegistDialog } from "./ntftalk-temp-regist-dialog";
+
+interface NtftalkTempTablePanelProps {
+ query: UseQueryResult;
+ table: Table;
+ onRowClick: (row: NtftalkTemp) => void;
+}
+
+function NtftalkTempTablePanel({
+ query,
+ table,
+ onRowClick,
+}: NtftalkTempTablePanelProps) {
+ if (query.isLoading) {
+ return (
+
+ );
+ }
+ if (query.isError) return ;
+ return ;
+}
+
+function NtftalkTempTemplateSection() {
+ const queryClient = useQueryClient();
+ const alert = useAlertStore((s) => s.setMessage);
+ const { confirm } = useConfirm();
+
+ const [templateCodeInput, setTemplateCodeInput] = useState("");
+ const [templateNameInput, setTemplateNameInput] = useState("");
+ const [templateContentInput, setTemplateContentInput] = useState("");
+ const [senderKeyFilter, setSenderKeyFilter] = useState("");
+ const [inspectionFilter, setInspectionFilter] = useState("");
+ const [messageTypeFilter, setMessageTypeFilter] = useState("");
+
+ const [searchParams, setSearchParams] = useState({
+ sort: "createdAt,desc",
+ });
+ const [pagination, setPagination] = useState({
+ pageIndex: 0,
+ pageSize: 10,
+ });
+ const [rowSelection, setRowSelection] = useState({});
+ const [pendingDeleteTemplateCodes, setPendingDeleteTemplateCodes] = useState(
+ () => new Set(),
+ );
+
+ const [detailCode, setDetailCode] = useState(null);
+ const [detailOpen, setDetailOpen] = useState(false);
+ const [registOpen, setRegistOpen] = useState(false);
+
+ const tempQuery = useQuery({
+ queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_TEMP, searchParams, pagination],
+ queryFn: () =>
+ NtftalkTempApi.getNtftalkTempPage({
+ ...searchParams,
+ page: pagination.pageIndex,
+ size: pagination.pageSize,
+ }),
+ placeholderData: keepPreviousData,
+ });
+
+ const senderQuery = useQuery({
+ queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_BIZTALK, "sender"],
+ queryFn: () => NtftalkBiztalkApi.getSender(),
+ });
+
+ const senderKeyOptions = useMemo(() => {
+ if (senderQuery.data) return [senderQuery.data];
+ return [];
+ }, [senderQuery.data]);
+
+ const templateColumns = useMemo(
+ () => createTemplateColumns(senderQuery?.data ? [senderQuery.data] : []),
+ [senderQuery.data],
+ );
+
+ const visibleTemplates = useMemo(
+ () =>
+ (tempQuery.data?.content ?? []).filter(
+ (row) => !pendingDeleteTemplateCodes.has(row.templateCode),
+ ),
+ [tempQuery.data, pendingDeleteTemplateCodes],
+ );
+
+ const templateTable = useReactTable({
+ data: visibleTemplates,
+ columns: templateColumns,
+ state: { pagination, rowSelection },
+ manualPagination: true,
+ rowCount: tempQuery.data?.totalElements ?? 0,
+ pageCount: tempQuery.data?.totalPages ?? 0,
+ enableRowSelection: true,
+ enableMultiRowSelection: true,
+ getCoreRowModel: getCoreRowModel(),
+ onPaginationChange: setPagination,
+ onRowSelectionChange: setRowSelection,
+ getRowId: (row) => row.templateCode,
+ });
+
+ const buildSearchParamsFromInputs = (): NtftalkTempSearchParams => ({
+ sort: searchParams.sort ?? "createdAt,desc",
+ templateCode: templateCodeInput.trim() || undefined,
+ templateName: templateNameInput.trim() || undefined,
+ templateContent: templateContentInput.trim() || undefined,
+ senderKey: senderKeyFilter,
+ inspectionStatus: inspectionFilter,
+ templateMessageType: messageTypeFilter,
+ });
+
+ const handleSearch = (e: React.SubmitEvent) => {
+ e.preventDefault();
+ setPendingDeleteTemplateCodes(new Set());
+ setRowSelection({});
+ setPagination((p) => ({ ...p, pageIndex: 0 }));
+ setSearchParams(buildSearchParamsFromInputs());
+ };
+
+ const handleReset = () => {
+ setTemplateCodeInput("");
+ setTemplateNameInput("");
+ setTemplateContentInput("");
+ setSenderKeyFilter("");
+ setInspectionFilter("");
+ setMessageTypeFilter("");
+ setPendingDeleteTemplateCodes(new Set());
+ setRowSelection({});
+ setPagination((p) => ({ ...p, pageIndex: 0 }));
+ setSearchParams({ sort: "createdAt,desc" });
+ };
+
+ const syncTemplatesMutation = useMutationWrapper({
+ mutationFn: () => NtftalkBiztalkApi.syncTemplates({ senderKeyType: "S" }),
+ onSuccess: async (data) => {
+ await Promise.all([
+ queryClient.invalidateQueries({
+ queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_TEMP],
+ }),
+ queryClient.invalidateQueries({
+ queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_BIZTALK, "sender"],
+ }),
+ ]);
+ alert(data.message);
+ },
+ });
+
+ const handleRefreshTemplates = () => {
+ setPendingDeleteTemplateCodes(new Set());
+ setRowSelection({});
+ syncTemplatesMutation.mutate();
+ };
+
+ const deleteMultiTemplatesMutation = useMutationWrapper({
+ mutationFn: (templateCodes: string[]) =>
+ NtftalkTempApi.deleteMultiNtftalkTemp({ templateCodes }),
+ onSuccess: async () => {
+ await queryClient.invalidateQueries({
+ queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_TEMP],
+ });
+ setPendingDeleteTemplateCodes(new Set());
+ setRowSelection({});
+ alert("저장되었습니다.");
+ },
+ });
+
+ const handleDeleteSelectedTemplates = async () => {
+ const rows = templateTable.getSelectedRowModel().rows;
+ if (rows.length === 0) {
+ alert("삭제할 템플릿을 선택해 주세요.");
+ return;
+ }
+ if (!(await confirm(`선택한 ${rows.length}건을 삭제하시겠습니까?`))) return;
+ const codes = rows.map((r) => r.original.templateCode);
+ setPendingDeleteTemplateCodes((prev) => {
+ const next = new Set(prev);
+ for (const code of codes) next.add(code);
+ return next;
+ });
+ setRowSelection({});
+ };
+
+ const inspectionRequestMutation = useMutationWrapper({
+ mutationFn: (requestList: NtftalkBiztalkInspectionRequestItem[]) =>
+ NtftalkBiztalkApi.requestInspection(requestList),
+ onSuccess: async (data) => {
+ await queryClient.invalidateQueries({
+ queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_TEMP],
+ });
+ setRowSelection({});
+ alert(data.message);
+ },
+ });
+
+ const handleInspectionRequest = async () => {
+ const rows = templateTable.getSelectedRowModel().rows;
+ const requestList: NtftalkBiztalkInspectionRequestItem[] = [];
+ for (const row of rows) {
+ const { senderKey, templateCode, senderKeyType } = row.original;
+ if (!senderKey || !templateCode) {
+ alert("선택 항목에 발신키 또는 템플릿코드가 없습니다.");
+ return;
+ }
+ requestList.push({
+ senderKey,
+ templateCode,
+ senderKeyType: senderKeyType || "S",
+ });
+ }
+
+ if (
+ !(await confirm(`선택한 ${requestList.length}건을 검수요청하시겠습니까?`))
+ )
+ return;
+ inspectionRequestMutation.mutate(requestList);
+ };
+
+ const handleSaveTemplateGrid = () => {
+ if (pendingDeleteTemplateCodes.size === 0) {
+ alert("저장할 변경 사항이 없습니다.");
+ return;
+ }
+ deleteMultiTemplatesMutation.mutate([...pendingDeleteTemplateCodes]);
+ };
+
+ return (
+ <>
+
+
+
+
+
+ 알림톡 템플릿 관리
+
+
+
+
+
+
+
+
+
+
+ {
+ setDetailCode(row.templateCode);
+ setDetailOpen(true);
+ }}
+ />
+
+
+
+ {
+ setDetailOpen(o);
+ if (!o) setDetailCode(null);
+ }}
+ />
+
+ >
+ );
+}
+
+export { NtftalkTempTemplateSection };
diff --git a/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-var-section.tsx b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-var-section.tsx
new file mode 100644
index 000000000..6077be8ac
--- /dev/null
+++ b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-temp-var-section.tsx
@@ -0,0 +1,234 @@
+"use client";
+
+import { useCallback, useMemo, useState } from "react";
+
+import { useConfirm } from "@frontend-opensource/use-react-hooks";
+import type { UseQueryResult } from "@tanstack/react-query";
+import {
+ keepPreviousData,
+ useQuery,
+ useQueryClient,
+} from "@tanstack/react-query";
+import {
+ getCoreRowModel,
+ type PaginationState,
+ type RowSelectionState,
+ type Table,
+ useReactTable,
+} from "@tanstack/react-table";
+
+import { ENDPOINT } from "@/shared/config";
+import { useAlertStore, useMutationWrapper } from "@/shared/lib";
+import { NoData } from "@/shared/ui/no-data";
+import {
+ NtftalkVarApi,
+ type NtftalkVarPageList,
+ type NtftalkVarSavePayload,
+ type NtftalkVarSearchParams,
+} from "@/entities/ntftalk";
+import { Button, DataTable, LoadingWithAnimation } from "@common/components";
+
+import { buildNtftalkVarBatchPayload } from "../model/ntftalk-var-batch-payload";
+import { type NtftalkVarGridRow } from "../model/ntftalk-var-grid";
+import { createVarColumns } from "../model/var-table-columns";
+
+import { NtftalkVarAddDialog } from "./ntftalk-var-add-dialog";
+import { NtftalkVarEditDialog } from "./ntftalk-var-edit-dialog";
+
+interface NtftalkVarTablePanelProps {
+ query: UseQueryResult;
+ table: Table;
+ onRowClick?: (row: NtftalkVarGridRow) => void;
+}
+
+function NtftalkVarTablePanel({
+ query,
+ table,
+ onRowClick,
+}: NtftalkVarTablePanelProps) {
+ if (query.isLoading) {
+ return (
+
+ );
+ }
+ if (query.isError) return ;
+ return ;
+}
+
+function NtftalkVarSection() {
+ const queryClient = useQueryClient();
+ const alert = useAlertStore((s) => s.setMessage);
+ const { confirm } = useConfirm();
+
+ const [varPagination, setVarPagination] = useState({
+ pageIndex: 0,
+ pageSize: 10,
+ });
+ const [varSearchParams] = useState({
+ sort: "regDt,desc",
+ });
+ const [varRowSelection, setVarRowSelection] = useState({});
+ const [pendingDeleteVarCds, setPendingDeleteVarCds] = useState(
+ () => new Set(),
+ );
+ const [editTargetVarCd, setEditTargetVarCd] = useState(null);
+ const [addVarDialogOpen, setAddVarDialogOpen] = useState(false);
+
+ const varQuery = useQuery({
+ queryKey: [
+ ENDPOINT.NTFTALK_SERVICE.NTFTALK_VAR,
+ varSearchParams,
+ varPagination,
+ ],
+ queryFn: () =>
+ NtftalkVarApi.getNtftalkVarPage({
+ ...varSearchParams,
+ page: varPagination.pageIndex,
+ size: varPagination.pageSize,
+ }),
+ placeholderData: keepPreviousData,
+ });
+
+ const varTableRows = useMemo((): NtftalkVarGridRow[] => {
+ return (varQuery.data?.content ?? []).filter(
+ (row) => !pendingDeleteVarCds.has(row.varCd),
+ );
+ }, [varQuery.data, pendingDeleteVarCds]);
+
+ const varTable = useReactTable({
+ data: varTableRows,
+ columns: createVarColumns(),
+ state: { pagination: varPagination, rowSelection: varRowSelection },
+ manualPagination: true,
+ rowCount: varQuery.data?.totalElements ?? 0,
+ pageCount: varQuery.data?.totalPages ?? 0,
+ enableRowSelection: true,
+ enableMultiRowSelection: true,
+ getCoreRowModel: getCoreRowModel(),
+ onPaginationChange: setVarPagination,
+ onRowSelectionChange: setVarRowSelection,
+ getRowId: (row) => `srv:${row.varCd}`,
+ });
+
+ const handleDeleteSelectedVars = async () => {
+ const selected = varTable.getSelectedRowModel().rows;
+ if (selected.length === 0) {
+ alert("삭제할 변수를 선택해 주세요.");
+ return;
+ }
+ if (!(await confirm(`선택한 ${selected.length}건을 삭제하시겠습니까?`)))
+ return;
+
+ const serverCodes = selected
+ .map((r) => r.original.varCd)
+ .filter((varCd): varCd is string => Boolean(varCd));
+
+ if (serverCodes.length) {
+ setPendingDeleteVarCds((prev) => {
+ const next = new Set(prev);
+ for (const code of serverCodes) next.add(code);
+ return next;
+ });
+ }
+ setVarRowSelection({});
+ };
+
+ const saveVarsMutation = useMutationWrapper({
+ mutationFn: (payload: NtftalkVarSavePayload[]) =>
+ NtftalkVarApi.saveNtftalkVar(payload),
+ onSuccess: async () => {
+ await queryClient.invalidateQueries({
+ queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_VAR],
+ });
+ setPendingDeleteVarCds(new Set());
+ setVarRowSelection({});
+ setAddVarDialogOpen(false);
+ alert("저장되었습니다.");
+ },
+ });
+
+ const handleVarRowClick = useCallback((row: NtftalkVarGridRow) => {
+ setEditTargetVarCd(row.varCd);
+ }, []);
+
+ const handleSaveVars = () => {
+ if (pendingDeleteVarCds.size === 0) {
+ alert("저장할 변경 사항이 없습니다.");
+ return;
+ }
+ let payload: NtftalkVarSavePayload[];
+ try {
+ payload = buildNtftalkVarBatchPayload({
+ pendingDeleteVarCds,
+ });
+ } catch (e) {
+ alert(e instanceof Error ? e.message : "유효하지 않은 입력입니다.");
+ return;
+ }
+ if (payload.length === 0) {
+ alert("저장할 변경 사항이 없습니다.");
+ return;
+ }
+ saveVarsMutation.mutate(payload);
+ };
+
+ return (
+
+
+
변수 관리
+
+
+
+
+
+
+
+
+
+
+
{
+ if (!next) setEditTargetVarCd(null);
+ }}
+ />
+
+ );
+}
+
+export { NtftalkVarSection };
diff --git a/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-var-add-dialog.tsx b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-var-add-dialog.tsx
new file mode 100644
index 000000000..108495f30
--- /dev/null
+++ b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-var-add-dialog.tsx
@@ -0,0 +1,197 @@
+"use client";
+
+import { useEffect } from "react";
+import { useFieldArray, useForm } from "react-hook-form";
+
+import { zodResolver } from "@hookform/resolvers/zod";
+
+import { useAlertStore, useUserStore } from "@/shared/lib";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from "@/shared/ui/form";
+import type { NtftalkVarSavePayload } from "@/entities/ntftalk";
+import {
+ Button,
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ Input,
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@common/components";
+
+import {
+ type NtftalkVarAdditionalFormData,
+ ntftalkVarAdditionalSchema,
+} from "../model/ntftalk-var-additional-schema";
+import { buildNtftalkVarBatchPayload } from "../model/ntftalk-var-batch-payload";
+import { createEmptyVarDraft } from "../model/ntftalk-var-grid";
+
+interface NtftalkVarAddDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ onSave: (payload: NtftalkVarSavePayload[]) => void;
+}
+
+function NtftalkVarAddDialog({
+ open,
+ onOpenChange,
+ onSave,
+}: NtftalkVarAddDialogProps) {
+ const alert = useAlertStore((s) => s.setMessage);
+ const userInfo = useUserStore((s) => s.userInfo);
+
+ const form = useForm({
+ resolver: zodResolver(ntftalkVarAdditionalSchema),
+ defaultValues: { draftVars: [] },
+ });
+ const { control, reset } = form;
+ const { fields, append } = useFieldArray({
+ control,
+ name: "draftVars",
+ });
+
+ useEffect(() => {
+ if (!open) return;
+ reset({
+ draftVars: [createEmptyVarDraft(Date.now().toString())],
+ });
+ }, [open, reset]);
+
+ const onSubmit = form.handleSubmit((data) => {
+ const draftVars = data.draftVars ?? [];
+ let payload: NtftalkVarSavePayload[];
+ try {
+ payload = buildNtftalkVarBatchPayload({
+ draftVars,
+ pendingDeleteVarCds: new Set(),
+ regr: userInfo?.userName,
+ });
+ } catch (e) {
+ alert(e instanceof Error ? e.message : "유효하지 않은 입력입니다.");
+ return;
+ }
+ if (payload.length === 0) {
+ alert("저장할 변경 사항이 없습니다.");
+ return;
+ }
+ onSave(payload);
+ });
+
+ return (
+
+ );
+}
+
+export { NtftalkVarAddDialog };
diff --git a/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-var-edit-dialog.tsx b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-var-edit-dialog.tsx
new file mode 100644
index 000000000..4d1917674
--- /dev/null
+++ b/apps/cs/src/pages/ntftalk-temp/ui/ntftalk-var-edit-dialog.tsx
@@ -0,0 +1,180 @@
+"use client";
+
+import { useEffect } from "react";
+import { useForm } from "react-hook-form";
+
+import { zodResolver } from "@hookform/resolvers/zod";
+import { useQuery, useQueryClient } from "@tanstack/react-query";
+import { z } from "zod";
+
+import { ENDPOINT } from "@/shared/config";
+import { useAlertStore, useMutationWrapper } from "@/shared/lib";
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "@/shared/ui/form";
+import { NoData } from "@/shared/ui/no-data";
+import { NtftalkVarApi } from "@/entities/ntftalk";
+import {
+ Button,
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ Input,
+ Label,
+ LoadingWithAnimation,
+ Textarea,
+} from "@common/components";
+
+const ntftalkVarEditFormSchema = z.object({
+ varCd: z.string().min(1, "변수코드를 입력해 주세요."),
+ varNm: z.string().min(1, "변수명을 입력해 주세요."),
+ varDesc: z.string().optional(),
+});
+
+type NtftalkVarEditFormValues = z.infer;
+
+interface NtftalkVarEditDialogProps {
+ varCd: string | null;
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+function NtftalkVarEditDialog({
+ varCd,
+ open,
+ onOpenChange,
+}: NtftalkVarEditDialogProps) {
+ const queryClient = useQueryClient();
+ const alert = useAlertStore((s) => s.setMessage);
+
+ const detailQuery = useQuery({
+ queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_VAR, "detail", varCd],
+ queryFn: () => NtftalkVarApi.getNtftalkVar(varCd!),
+ enabled: open && !!varCd,
+ });
+
+ const form = useForm({
+ resolver: zodResolver(ntftalkVarEditFormSchema),
+ defaultValues: { varCd: "", varNm: "", varDesc: "" },
+ });
+
+ useEffect(() => {
+ const d = detailQuery.data;
+ if (!d) return;
+ form.reset({
+ varCd: d.varCd,
+ varNm: d.varNm,
+ varDesc: d.varDesc || "",
+ });
+ }, [detailQuery.data, form]);
+
+ const saveMutation = useMutationWrapper({
+ mutationFn: () => {
+ const values = form.getValues();
+ return NtftalkVarApi.saveNtftalkVar([
+ {
+ mode: "U",
+ varCd: values.varCd,
+ varNm: values.varNm,
+ varDesc: values.varDesc || undefined,
+ },
+ ]);
+ },
+ onSuccess: async () => {
+ await queryClient.invalidateQueries({
+ queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_VAR],
+ });
+ alert("저장되었습니다.");
+ onOpenChange(false);
+ },
+ });
+
+ const handleSubmit = form.handleSubmit(() => saveMutation.mutate());
+
+ return (
+
+ );
+}
+
+export { NtftalkVarEditDialog };
diff --git a/apps/cs/src/shared/ui/info-tooltip.tsx b/apps/cs/src/shared/ui/info-tooltip.tsx
index 2c43ecf9c..fc603e4ef 100644
--- a/apps/cs/src/shared/ui/info-tooltip.tsx
+++ b/apps/cs/src/shared/ui/info-tooltip.tsx
@@ -6,7 +6,7 @@ function InfoTooltip({ text }: { text: string }) {
-
+
{text.split("\n").map((line, idx) => (
diff --git a/packages/common/components/src/ui/lnb/index.tsx b/packages/common/components/src/ui/lnb/index.tsx
index 3339a6f26..1c7722a36 100644
--- a/packages/common/components/src/ui/lnb/index.tsx
+++ b/packages/common/components/src/ui/lnb/index.tsx
@@ -94,7 +94,7 @@ export function LNB({
enabled: !isLoggedIn,
});
- const menuList = isLoggedIn
+ const menuList: MenuItem[] | undefined = isLoggedIn
? previewMode
? previewTreeMenuListData
: menuListData