Merge branch 'iipbs/cs/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend
This commit is contained in:
@@ -11,7 +11,7 @@ export interface NtftalkVar {
|
||||
sender: string;
|
||||
varNm: string;
|
||||
varDesc: string;
|
||||
regDt: string;
|
||||
regDt?: string;
|
||||
regr: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
>;
|
||||
@@ -12,15 +12,21 @@ export const varDraftRowSchema = z.object({
|
||||
});
|
||||
|
||||
export function buildNtftalkVarBatchPayload(input: {
|
||||
draftVars: NtftalkVarGridRow[];
|
||||
pendingDeleteVarCds: ReadonlySet<string>;
|
||||
regr: string | undefined;
|
||||
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) {
|
||||
if (!d.isDraft) continue;
|
||||
const parsed = varDraftRowSchema.safeParse({
|
||||
varCd: d.varCd,
|
||||
varNm: d.varNm,
|
||||
@@ -42,9 +48,5 @@ export function buildNtftalkVarBatchPayload(input: {
|
||||
});
|
||||
}
|
||||
|
||||
for (const varCd of pendingDeleteVarCds) {
|
||||
payloads.push({ mode: "D", varCd });
|
||||
}
|
||||
|
||||
return payloads;
|
||||
}
|
||||
|
||||
@@ -2,17 +2,11 @@ import type { NtftalkVar } from "@/entities/ntftalk";
|
||||
|
||||
export interface NtftalkVarGridRow extends NtftalkVar {
|
||||
clientId?: string;
|
||||
isDraft?: boolean;
|
||||
}
|
||||
|
||||
export interface VarDraftFormValues {
|
||||
draftVars: NtftalkVarGridRow[];
|
||||
}
|
||||
|
||||
export function createEmptyVarDraft(clientId: string): NtftalkVarGridRow {
|
||||
return {
|
||||
clientId,
|
||||
isDraft: true,
|
||||
varCd: "",
|
||||
varNm: "",
|
||||
varDesc: "",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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";
|
||||
|
||||
@@ -12,9 +12,7 @@ export interface VarColumnHandlers {
|
||||
) => void;
|
||||
}
|
||||
|
||||
export function createVarColumns(
|
||||
handlers: VarColumnHandlers,
|
||||
): ColumnDef<NtftalkVarGridRow>[] {
|
||||
export function createVarColumns(): ColumnDef<NtftalkVarGridRow>[] {
|
||||
return [
|
||||
{
|
||||
id: "select",
|
||||
@@ -65,74 +63,14 @@ export function createVarColumns(
|
||||
{
|
||||
accessorKey: "varCd",
|
||||
header: "변수코드",
|
||||
cell: ({ row }) => {
|
||||
const r = row.original;
|
||||
if (r.isDraft && r.clientId) {
|
||||
return (
|
||||
<Input
|
||||
className="h-[3rem] min-w-[10rem]"
|
||||
value={r.varCd}
|
||||
onChange={(e) =>
|
||||
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 (
|
||||
<Input
|
||||
className="h-[3rem] min-w-[10rem]"
|
||||
value={r.varNm}
|
||||
onChange={(e) =>
|
||||
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 (
|
||||
<Input
|
||||
className="h-[3rem] min-w-[16rem]"
|
||||
value={r.varDesc ?? ""}
|
||||
onChange={(e) =>
|
||||
handlers.onDraftFieldChange(
|
||||
r.clientId!,
|
||||
"varDesc",
|
||||
e.target.value,
|
||||
)
|
||||
}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return r.varDesc ?? "—";
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "regDt",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -60,8 +60,10 @@ function NtftalkTempTablePanel({
|
||||
}: NtftalkTempTablePanelProps) {
|
||||
if (query.isLoading) {
|
||||
return (
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
<div className="flex items-center justify-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -217,8 +219,7 @@ function NtftalkTempTemplateSection() {
|
||||
alert("삭제할 템플릿을 선택해 주세요.");
|
||||
return;
|
||||
}
|
||||
if (!(await confirm(`선택한 ${rows.length}건을 삭제하시겠습니까?`)))
|
||||
return;
|
||||
if (!(await confirm(`선택한 ${rows.length}건을 삭제하시겠습니까?`))) return;
|
||||
const codes = rows.map((r) => r.original.templateCode);
|
||||
setPendingDeleteTemplateCodes((prev) => {
|
||||
const next = new Set(prev);
|
||||
@@ -420,10 +421,7 @@ function NtftalkTempTemplateSection() {
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleSaveTemplateGrid}
|
||||
disabled={
|
||||
deleteMultiTemplatesMutation.isPending ||
|
||||
pendingDeleteTemplateCodes.size === 0
|
||||
}
|
||||
disabled={deleteMultiTemplatesMutation.isPending}
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"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 type { UseQueryResult } from "@tanstack/react-query";
|
||||
@@ -19,7 +18,7 @@ import {
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import { useAlertStore, useMutationWrapper, useUserStore } from "@/shared/lib";
|
||||
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
|
||||
import { NoData } from "@/shared/ui/no-data";
|
||||
import {
|
||||
NtftalkVarApi,
|
||||
@@ -30,13 +29,10 @@ import {
|
||||
import { Button, DataTable, LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import { buildNtftalkVarBatchPayload } from "../model/ntftalk-var-batch-payload";
|
||||
import {
|
||||
createEmptyVarDraft,
|
||||
type NtftalkVarGridRow,
|
||||
type VarDraftFormValues,
|
||||
} from "../model/ntftalk-var-grid";
|
||||
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 {
|
||||
@@ -67,7 +63,6 @@ function NtftalkVarSection() {
|
||||
const queryClient = useQueryClient();
|
||||
const alert = useAlertStore((s) => s.setMessage);
|
||||
const { confirm } = useConfirm();
|
||||
const userInfo = useUserStore((s) => s.userInfo);
|
||||
|
||||
const [varPagination, setVarPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
@@ -81,16 +76,7 @@ function NtftalkVarSection() {
|
||||
() => new Set<string>(),
|
||||
);
|
||||
const [editTargetVarCd, setEditTargetVarCd] = useState<string | null>(null);
|
||||
|
||||
const varDraftForm = useForm<VarDraftFormValues>({
|
||||
defaultValues: { draftVars: [] },
|
||||
});
|
||||
const { control, setValue, getValues } = varDraftForm;
|
||||
const { append, remove, replace } = useFieldArray({
|
||||
control,
|
||||
name: "draftVars",
|
||||
});
|
||||
const draftVars = useWatch({ control, name: "draftVars" }) ?? [];
|
||||
const [addVarDialogOpen, setAddVarDialogOpen] = useState(false);
|
||||
|
||||
const varQuery = useQuery({
|
||||
queryKey: [
|
||||
@@ -107,48 +93,15 @@ function NtftalkVarSection() {
|
||||
placeholderData: keepPreviousData,
|
||||
});
|
||||
|
||||
const draftIndexByClientId = useMemo(() => {
|
||||
const indexByClientId = new Map<string, number>();
|
||||
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 drafts = getValues("draftVars") ?? [];
|
||||
const draftIndex = drafts.findIndex(
|
||||
(r) => r.isDraft && r.clientId === clientId,
|
||||
);
|
||||
if (draftIndex === -1) return;
|
||||
setValue(`draftVars.${draftIndex}.${field}`, value, {
|
||||
shouldDirty: true,
|
||||
});
|
||||
},
|
||||
[getValues, setValue],
|
||||
);
|
||||
|
||||
const varColumns = useMemo(
|
||||
() => createVarColumns({ onDraftFieldChange }),
|
||||
[onDraftFieldChange],
|
||||
);
|
||||
|
||||
const varTableRows = useMemo((): NtftalkVarGridRow[] => {
|
||||
const server = (varQuery.data?.content ?? []).filter(
|
||||
return (varQuery.data?.content ?? []).filter(
|
||||
(row) => !pendingDeleteVarCds.has(row.varCd),
|
||||
);
|
||||
if (varPagination.pageIndex === 0) return [...server, ...draftVars];
|
||||
return server;
|
||||
}, [varQuery.data, draftVars, varPagination.pageIndex, pendingDeleteVarCds]);
|
||||
}, [varQuery.data, pendingDeleteVarCds]);
|
||||
|
||||
const varTable = useReactTable({
|
||||
data: varTableRows,
|
||||
columns: varColumns,
|
||||
columns: createVarColumns(),
|
||||
state: { pagination: varPagination, rowSelection: varRowSelection },
|
||||
manualPagination: true,
|
||||
rowCount: varQuery.data?.totalElements ?? 0,
|
||||
@@ -158,17 +111,9 @@ function NtftalkVarSection() {
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onPaginationChange: setVarPagination,
|
||||
onRowSelectionChange: setVarRowSelection,
|
||||
getRowId: (row) =>
|
||||
row.isDraft && row.clientId
|
||||
? `draft:${row.clientId}`
|
||||
: `srv:${row.varCd}`,
|
||||
getRowId: (row) => `srv:${row.varCd}`,
|
||||
});
|
||||
|
||||
const handleAddVarRow = () => {
|
||||
append(createEmptyVarDraft(crypto.randomUUID()));
|
||||
setVarPagination((p) => ({ ...p, pageIndex: 0 }));
|
||||
};
|
||||
|
||||
const handleDeleteSelectedVars = async () => {
|
||||
const selected = varTable.getSelectedRowModel().rows;
|
||||
if (selected.length === 0) {
|
||||
@@ -178,18 +123,9 @@ function NtftalkVarSection() {
|
||||
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));
|
||||
const serverCodes = selected
|
||||
.map((r) => r.original.varCd)
|
||||
.filter((varCd): varCd is string => Boolean(varCd));
|
||||
|
||||
if (serverCodes.length) {
|
||||
setPendingDeleteVarCds((prev) => {
|
||||
@@ -208,29 +144,26 @@ function NtftalkVarSection() {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.NTFTALK_SERVICE.NTFTALK_VAR],
|
||||
});
|
||||
replace([]);
|
||||
setPendingDeleteVarCds(new Set());
|
||||
setVarRowSelection({});
|
||||
setAddVarDialogOpen(false);
|
||||
alert("저장되었습니다.");
|
||||
},
|
||||
});
|
||||
|
||||
const handleVarRowClick = useCallback((row: NtftalkVarGridRow) => {
|
||||
if (row.isDraft) return;
|
||||
setEditTargetVarCd(row.varCd);
|
||||
}, []);
|
||||
|
||||
const handleSaveVars = () => {
|
||||
if (draftVars.length === 0 && pendingDeleteVarCds.size === 0) {
|
||||
if (pendingDeleteVarCds.size === 0) {
|
||||
alert("저장할 변경 사항이 없습니다.");
|
||||
return;
|
||||
}
|
||||
let payload: NtftalkVarSavePayload[];
|
||||
try {
|
||||
payload = buildNtftalkVarBatchPayload({
|
||||
draftVars,
|
||||
pendingDeleteVarCds,
|
||||
regr: userInfo?.userName ?? "",
|
||||
});
|
||||
} catch (e) {
|
||||
alert(e instanceof Error ? e.message : "유효하지 않은 입력입니다.");
|
||||
@@ -252,7 +185,7 @@ function NtftalkVarSection() {
|
||||
type="button"
|
||||
color="green"
|
||||
className="h-[3.2rem]"
|
||||
onClick={handleAddVarRow}
|
||||
onClick={() => setAddVarDialogOpen(true)}
|
||||
>
|
||||
변수추가
|
||||
</Button>
|
||||
@@ -282,6 +215,11 @@ function NtftalkVarSection() {
|
||||
onRowClick={handleVarRowClick}
|
||||
/>
|
||||
</div>
|
||||
<NtftalkVarAddDialog
|
||||
open={addVarDialogOpen}
|
||||
onOpenChange={setAddVarDialogOpen}
|
||||
onSave={saveVarsMutation.mutate}
|
||||
/>
|
||||
<NtftalkVarEditDialog
|
||||
varCd={editTargetVarCd}
|
||||
open={editTargetVarCd !== null}
|
||||
|
||||
@@ -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<NtftalkVarAdditionalFormData>({
|
||||
resolver: zodResolver(ntftalkVarAdditionalSchema),
|
||||
defaultValues: { draftVars: [] },
|
||||
});
|
||||
const { control, reset } = form;
|
||||
const { fields, append } = useFieldArray({
|
||||
control,
|
||||
name: "draftVars",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
reset({
|
||||
draftVars: [createEmptyVarDraft(crypto.randomUUID())],
|
||||
});
|
||||
}, [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 (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
className="max-h-[85vh] max-w-[72rem] overflow-y-auto"
|
||||
aria-describedby={undefined}
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>변수 추가</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Form {...form}>
|
||||
<form id="ntftalk-var-add-form" onSubmit={onSubmit}>
|
||||
<Table variant="secondary" className="w-full border">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="min-w-[12rem] text-left text-label">
|
||||
변수코드
|
||||
</TableHead>
|
||||
<TableHead className="min-w-[12rem] text-left text-label">
|
||||
변수명
|
||||
</TableHead>
|
||||
<TableHead className="min-w-[16rem] text-left text-label">
|
||||
설명
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{fields.map((field, index) => (
|
||||
<TableRow key={field.id}>
|
||||
<TableCell className="align-top">
|
||||
<FormField
|
||||
control={control}
|
||||
name={`draftVars.${index}.varCd`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="align-top">
|
||||
<FormField
|
||||
control={control}
|
||||
name={`draftVars.${index}.varNm`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="align-top">
|
||||
<FormField
|
||||
control={control}
|
||||
name={`draftVars.${index}.varDesc`}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</form>
|
||||
</Form>
|
||||
<div className="flex justify-start">
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
size="sm"
|
||||
onClick={() => append(createEmptyVarDraft(crypto.randomUUID()))}
|
||||
>
|
||||
+ 행추가
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter className="gap-2 sm:gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
color="white"
|
||||
onClick={() => onOpenChange(false)}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button type="submit" form="ntftalk-var-add-form">
|
||||
저장
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { NtftalkVarAddDialog };
|
||||
Reference in New Issue
Block a user