[as] feat: 공종코드 및 회계코드 하위코드 추가 방식 변경
Deploy Frontend / deploy (push) Successful in 9m46s

This commit is contained in:
2026-06-05 17:18:14 +09:00
parent acaf2dd9f4
commit 84013993c9
9 changed files with 193 additions and 157 deletions
-6
View File
@@ -1,6 +0,0 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -145,6 +145,15 @@ export function collectDescendantRowKeys(
return result;
}
export function insertRootSlot(
structure: AcntgDisplaySlot[],
slot: AcntgDisplaySlot,
): AcntgDisplaySlot[] {
const firstRootIndex = structure.findIndex((item) => item.parentRowKey === null);
if (firstRootIndex === -1) return [slot, ...structure];
return [...structure.slice(0, firstRootIndex), slot, ...structure.slice(firstRootIndex)];
}
export function insertChildSlot(
structure: AcntgDisplaySlot[],
parentRowKey: string,
@@ -153,12 +162,7 @@ export function insertChildSlot(
const parentIndex = structure.findIndex((item) => item.rowKey === parentRowKey);
if (parentIndex === -1) return [...structure, slot];
const parentDepth = structure[parentIndex].depth;
let insertAt = parentIndex + 1;
while (insertAt < structure.length && structure[insertAt].depth > parentDepth) {
insertAt += 1;
}
const insertAt = parentIndex + 1;
return [...structure.slice(0, insertAt), slot, ...structure.slice(insertAt)];
}
@@ -214,6 +218,21 @@ export function markAcntgRowsDeleted(
return { ...state, deletedKeys };
}
export function validateDraftParentAcntgSeCd(
state: AcntgSeGridState,
rowKey: string,
): string | null {
const draft = state.drafts[rowKey];
if (!draft?.parentRowKey) return null;
const parent = mergeAcntgRow(state, draft.parentRowKey);
if (!parent?.acntgSeCd?.trim()) {
return "하위 항목을 추가하려면 상위 코드를 먼저 입력하세요.";
}
return null;
}
export function removeDraftFromGrid(state: AcntgSeGridState, rowKey: string): AcntgSeGridState {
const { [rowKey]: _removed, ...restDrafts } = state.drafts;
const keysToRemove = collectDescendantRowKeys(state.structure, rowKey);
@@ -13,11 +13,13 @@ import {
hasAcntgGridPendingChanges,
hydrateGridFromServer,
insertChildSlot,
insertRootSlot,
markAcntgRowsDeleted,
mergeAcntgRow,
mergeAllAcntgRows,
patchAcntgGridRow,
removeDraftFromGrid,
validateDraftParentAcntgSeCd,
} from "./acntg-se-grid";
import { buildSaveMultiPayload } from "./build-save-multi-payload";
@@ -81,51 +83,43 @@ export function useAcntgSeRows({ serverContent, onAlert, onSave }: UseAcntgSeRow
useYn: "Y",
},
},
structure: [...prev.structure, { rowKey, depth: 0, parentRowKey: null }],
structure: insertRootSlot(prev.structure, { rowKey, depth: 0, parentRowKey: null }),
}));
}, []);
const addChildRow = useCallback(
(parent: AcntgSeRow) => {
if (parent.isNew) {
onAlert("상위 행을 먼저 저장한 후 하위 항목을 추가할 수 있습니다.");
return;
}
const addChildRow = useCallback((parent: AcntgSeRow) => {
const rowKey = createDraftRowKey();
const slot = {
rowKey,
depth: 0,
parentRowKey: parent.rowKey,
};
const rowKey = createDraftRowKey();
const slot = {
rowKey,
depth: 0,
parentRowKey: parent.rowKey,
};
setGrid((prev) => {
const parentSlot = prev.structure.find((item) => item.rowKey === parent.rowKey);
const depth = (parentSlot?.depth ?? 0) + 1;
setGrid((prev) => {
const parentSlot = prev.structure.find((item) => item.rowKey === parent.rowKey);
const depth = (parentSlot?.depth ?? 0) + 1;
return {
...prev,
drafts: {
...prev.drafts,
[rowKey]: {
rowKey,
parentRowKey: parent.rowKey,
cdLvNo: (parent.cdLvNo ?? 1) + 1,
acntgSeCd: "",
acntgSeCdNm: "",
expln: "",
useYn: "Y",
},
return {
...prev,
drafts: {
...prev.drafts,
[rowKey]: {
rowKey,
parentRowKey: parent.rowKey,
cdLvNo: (parent.cdLvNo ?? 1) + 1,
acntgSeCd: "",
acntgSeCdNm: "",
expln: "",
useYn: "Y",
},
structure: insertChildSlot(prev.structure, parent.rowKey, {
...slot,
depth,
}),
};
});
},
[onAlert],
);
},
structure: insertChildSlot(prev.structure, parent.rowKey, {
...slot,
depth,
}),
};
});
}, []);
const removeDraft = useCallback((rowKey: string) => {
setGrid((prev) => removeDraftFromGrid(prev, rowKey));
@@ -146,6 +140,12 @@ export function useAcntgSeRows({ serverContent, onAlert, onSave }: UseAcntgSeRow
for (const row of mergedRows) {
if (!row.isDeleted && (row.isNew || row.isDirty)) {
const parentCodeError = validateDraftParentAcntgSeCd(grid, row.rowKey);
if (parentCodeError) {
onAlert(parentCodeError);
return false;
}
const fieldError = validateAcntgSeRow(row);
if (fieldError) {
onAlert(fieldError);
@@ -105,38 +105,40 @@ function AcntgCodeManagementPage() {
등록된 코드가 없습니다. [코드 추가]로 1단계 코드를 등록하세요.
</p>
) : (
<Table className="w-full">
<colgroup>
<col width="15%" />
<col width="20%" />
<col width="35%" />
<col width="12%" />
<col width="8%" />
<col width="10%" />
</colgroup>
<TableHeader>
<TableRow>
<TableHead>코드</TableHead>
<TableHead>코드명</TableHead>
<TableHead>설명</TableHead>
<TableHead>사용 여부</TableHead>
<TableHead>구분</TableHead>
<TableHead>관리</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{displayRows.map(({ row, depth }) => (
<AcntgSeRowComponent
key={row.rowKey}
row={row}
depth={depth}
onChange={(patch) => updateRow(row.rowKey, patch)}
onAddChild={() => addChildRow(row)}
onDelete={() => handleDelete(row)}
/>
))}
</TableBody>
</Table>
<div className="max-h-[60rem] overflow-y-auto pt-3">
<Table className="w-full">
<colgroup>
<col width="15%" />
<col width="20%" />
<col width="35%" />
<col width="12%" />
<col width="8%" />
<col width="10%" />
</colgroup>
<TableHeader>
<TableRow>
<TableHead>코드</TableHead>
<TableHead>코드명</TableHead>
<TableHead>설명</TableHead>
<TableHead>사용 여부</TableHead>
<TableHead>구분</TableHead>
<TableHead>관리</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{displayRows.map(({ row, depth }) => (
<AcntgSeRowComponent
key={row.rowKey}
row={row}
depth={depth}
onChange={(patch) => updateRow(row.rowKey, patch)}
onAddChild={() => addChildRow(row)}
onDelete={() => handleDelete(row)}
/>
))}
</TableBody>
</Table>
</div>
)}
</section>
</div>
@@ -85,7 +85,7 @@ function AcntgSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Acn
type="button"
size="sm"
color="white"
disabled={row.isNew || !canAddChildRow()}
disabled={!canAddChildRow()}
onClick={onAddChild}
>
하위추가
@@ -145,6 +145,15 @@ export function collectDescendantRowKeys(
return result;
}
export function insertRootSlot(
structure: CnstpDisplaySlot[],
slot: CnstpDisplaySlot,
): CnstpDisplaySlot[] {
const firstRootIndex = structure.findIndex((item) => item.parentRowKey === null);
if (firstRootIndex === -1) return [slot, ...structure];
return [...structure.slice(0, firstRootIndex), slot, ...structure.slice(firstRootIndex)];
}
export function insertChildSlot(
structure: CnstpDisplaySlot[],
parentRowKey: string,
@@ -153,12 +162,7 @@ export function insertChildSlot(
const parentIndex = structure.findIndex((item) => item.rowKey === parentRowKey);
if (parentIndex === -1) return [...structure, slot];
const parentDepth = structure[parentIndex].depth;
let insertAt = parentIndex + 1;
while (insertAt < structure.length && structure[insertAt].depth > parentDepth) {
insertAt += 1;
}
const insertAt = parentIndex + 1;
return [...structure.slice(0, insertAt), slot, ...structure.slice(insertAt)];
}
@@ -214,6 +218,21 @@ export function markCnstpRowsDeleted(
return { ...state, deletedKeys };
}
export function validateDraftParentCnstpSeCd(
state: CnstpSeGridState,
rowKey: string,
): string | null {
const draft = state.drafts[rowKey];
if (!draft?.parentRowKey) return null;
const parent = mergeCnstpRow(state, draft.parentRowKey);
if (!parent?.cnstpSeCd?.trim()) {
return "하위 항목을 추가하려면 상위 코드를 먼저 입력하세요.";
}
return null;
}
export function removeDraftFromGrid(state: CnstpSeGridState, rowKey: string): CnstpSeGridState {
const { [rowKey]: _removed, ...restDrafts } = state.drafts;
const keysToRemove = collectDescendantRowKeys(state.structure, rowKey);
@@ -14,11 +14,13 @@ import {
hasCnstpGridPendingChanges,
hydrateGridFromServer,
insertChildSlot,
insertRootSlot,
markCnstpRowsDeleted,
mergeAllCnstpRows,
mergeCnstpRow,
patchCnstpGridRow,
removeDraftFromGrid,
validateDraftParentCnstpSeCd,
} from "./cnstp-se-grid";
function validateCnstpSeRow(row: CnstpSeRow): string | null {
@@ -80,47 +82,39 @@ export function useCnstpSeRows({ serverContent, onAlert, onSave }: UseCnstpSeRow
useYn: "Y",
},
},
structure: [...prev.structure, { rowKey, depth: 0, parentRowKey: null }],
structure: insertRootSlot(prev.structure, { rowKey, depth: 0, parentRowKey: null }),
}));
}, []);
const addChildRow = useCallback(
(parent: CnstpSeRow) => {
if (parent.isNew) {
onAlert("상위 행을 먼저 저장한 후 하위 항목을 추가할 수 있습니다.");
return;
}
const addChildRow = useCallback((parent: CnstpSeRow) => {
const rowKey = createDraftRowKey();
const rowKey = createDraftRowKey();
setGrid((prev) => {
const parentSlot = prev.structure.find((item) => item.rowKey === parent.rowKey);
const depth = (parentSlot?.depth ?? 0) + 1;
setGrid((prev) => {
const parentSlot = prev.structure.find((item) => item.rowKey === parent.rowKey);
const depth = (parentSlot?.depth ?? 0) + 1;
return {
...prev,
drafts: {
...prev.drafts,
[rowKey]: {
rowKey,
parentRowKey: parent.rowKey,
cdLvNo: (parent.cdLvNo ?? 1) + 1,
cnstpSeCd: "",
cnstpSeCdNm: "",
expln: "",
useYn: "Y",
},
},
structure: insertChildSlot(prev.structure, parent.rowKey, {
return {
...prev,
drafts: {
...prev.drafts,
[rowKey]: {
rowKey,
depth,
parentRowKey: parent.rowKey,
}),
};
});
},
[onAlert],
);
cdLvNo: (parent.cdLvNo ?? 1) + 1,
cnstpSeCd: "",
cnstpSeCdNm: "",
expln: "",
useYn: "Y",
},
},
structure: insertChildSlot(prev.structure, parent.rowKey, {
rowKey,
depth,
parentRowKey: parent.rowKey,
}),
};
});
}, []);
const removeDraft = useCallback((rowKey: string) => {
setGrid((prev) => removeDraftFromGrid(prev, rowKey));
@@ -141,6 +135,12 @@ export function useCnstpSeRows({ serverContent, onAlert, onSave }: UseCnstpSeRow
for (const row of mergedRows) {
if (!row.isDeleted && (row.isNew || row.isDirty)) {
const parentCodeError = validateDraftParentCnstpSeCd(grid, row.rowKey);
if (parentCodeError) {
onAlert(parentCodeError);
return false;
}
const fieldError = validateCnstpSeRow(row);
if (fieldError) {
onAlert(fieldError);
@@ -105,38 +105,40 @@ function CnstpCodeManagementPage() {
등록된 코드가 없습니다. [코드 추가]로 1단계 코드를 등록하세요.
</p>
) : (
<Table className="w-full">
<colgroup>
<col width="15%" />
<col width="20%" />
<col width="35%" />
<col width="12%" />
<col width="8%" />
<col width="10%" />
</colgroup>
<TableHeader>
<TableRow>
<TableHead>코드</TableHead>
<TableHead>코드명</TableHead>
<TableHead>설명</TableHead>
<TableHead>사용 여부</TableHead>
<TableHead>구분</TableHead>
<TableHead>관리</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{displayRows.map(({ row, depth }) => (
<CnstpSeRowComponent
key={row.rowKey}
row={row}
depth={depth}
onChange={(patch) => updateRow(row.rowKey, patch)}
onAddChild={() => addChildRow(row)}
onDelete={() => handleDelete(row)}
/>
))}
</TableBody>
</Table>
<div className="max-h-[60rem] overflow-y-auto pt-3">
<Table className="w-full">
<colgroup>
<col width="15%" />
<col width="20%" />
<col width="35%" />
<col width="12%" />
<col width="8%" />
<col width="10%" />
</colgroup>
<TableHeader>
<TableRow>
<TableHead>코드</TableHead>
<TableHead>코드명</TableHead>
<TableHead>설명</TableHead>
<TableHead>사용 여부</TableHead>
<TableHead>구분</TableHead>
<TableHead>관리</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{displayRows.map(({ row, depth }) => (
<CnstpSeRowComponent
key={row.rowKey}
row={row}
depth={depth}
onChange={(patch) => updateRow(row.rowKey, patch)}
onAddChild={() => addChildRow(row)}
onDelete={() => handleDelete(row)}
/>
))}
</TableBody>
</Table>
</div>
)}
</section>
</div>
@@ -85,7 +85,7 @@ function CnstpSeRowComponent({ row, depth, onChange, onAddChild, onDelete }: Cns
type="button"
size="sm"
color="white"
disabled={row.isNew || !canAddChildRow()}
disabled={!canAddChildRow()}
onClick={onAddChild}
>
하위추가