refactor: Simplify customer form validation and remove unused fields

- Updated the customer form validation to only check for the business number, removing checks for contact phone and email.
- Removed unused input fields for contact person, phone, and email from the customer management page to streamline the form and improve user experience.
- This change aims to enhance the clarity and usability of the customer management interface across multiple company implementations.
This commit is contained in:
kjs
2026-04-13 10:52:48 +09:00
parent 8524c80cab
commit 2776437702
14 changed files with 2780 additions and 1924 deletions
@@ -715,7 +715,7 @@ export default function CustomerManagementPage() {
const handleCustomerSave = async () => {
if (!customerForm.customer_name) { toast.error("거래처명은 필수입니다."); return; }
if (!customerForm.status) { toast.error("상태는 필수입니다."); return; }
const errors = validateForm(customerForm, ["contact_phone", "email", "business_number"]);
const errors = validateForm(customerForm, ["business_number"]);
setFormErrors(errors);
if (Object.keys(errors).length > 0) {
toast.error("입력 형식을 확인해주세요.");
@@ -1877,35 +1877,6 @@ export default function CustomerManagementPage() {
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_person || ""}
onChange={(e) => setCustomerForm((p) => ({ ...p, contact_person: e.target.value }))}
placeholder="거래처담당자"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_phone || ""}
onChange={(e) => handleFormChange("contact_phone", e.target.value)}
placeholder="010-0000-0000"
className={cn("h-9", formErrors.contact_phone && "border-destructive")}
/>
{formErrors.contact_phone && <p className="text-xs text-destructive">{formErrors.contact_phone}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.email || ""}
onChange={(e) => handleFormChange("email", e.target.value)}
placeholder="example@email.com"
className={cn("h-9", formErrors.email && "border-destructive")}
/>
{formErrors.email && <p className="text-xs text-destructive">{formErrors.email}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
@@ -517,29 +517,44 @@ export default function SalesOrderPage() {
}
};
// 삭제 (마스터 단위)
// 삭제 (선택한 디테일 삭제 → 디테일 0건인 마스터 자동 삭제)
const handleDelete = async () => {
if (checkedIds.length === 0) { toast.error("삭제할 수주를 선택해주세요."); return; }
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
const ok = await confirm(`${orderNos.length}건의 수주를 삭제하시겠습니까?`, {
if (checkedIds.length === 0) { toast.error("삭제할 항목을 선택해주세요."); return; }
const ok = await confirm(`${checkedIds.length}건의 수주 항목을 삭제하시겠습니까?`, {
description: "삭제된 데이터는 복구할 수 없습니다.",
variant: "destructive",
confirmText: "삭제",
});
if (!ok) return;
try {
// 1. 선택한 디테일 삭제
await apiClient.delete(`/table-management/tables/${DETAIL_TABLE}/delete`, {
data: checkedIds.map((id) => ({ id })),
});
// 2. 영향받는 수주번호의 잔여 디테일 확인 → 0건이면 마스터도 삭제
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
for (const orderNo of orderNos) {
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
const remainRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
page: 1, size: 1,
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
autoFilter: true,
});
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
if (masters.length > 0) {
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
data: masters.map((m: any) => ({ id: m.id })),
const remaining = remainRes.data?.data?.data || remainRes.data?.data?.rows || [];
if (remaining.length === 0) {
// 디테일 0건 → 마스터 삭제
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
page: 1, size: 1,
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
autoFilter: true,
});
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
if (masters.length > 0) {
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
data: masters.map((m: any) => ({ id: m.id })),
});
}
}
}
toast.success("삭제되었습니다.");
@@ -918,7 +933,7 @@ export default function SalesOrderPage() {
{/* 데이터 테이블 (플랫 리스트) */}
<div className="flex-1 overflow-hidden rounded-lg border border-border bg-card">
<div className="h-full overflow-auto">
<Table style={{ minWidth: "1500px" }}>
<Table noWrapper style={{ minWidth: "1500px" }}>
<colgroup>
<col style={{ width: "40px" }} />
<col style={{ width: "140px" }} />
@@ -1107,6 +1122,7 @@ export default function SalesOrderPage() {
<Select value={masterForm.input_mode || ""} onValueChange={(v) => {
setMasterForm((p) => {
const next = { ...p, input_mode: v };
// 입력방식 변경 시 거래처 관련 값 초기화
delete next.partner_id;
delete next.delivery_partner_id;
delete next.delivery_address;
@@ -715,7 +715,7 @@ export default function CustomerManagementPage() {
const handleCustomerSave = async () => {
if (!customerForm.customer_name) { toast.error("거래처명은 필수입니다."); return; }
if (!customerForm.status) { toast.error("상태는 필수입니다."); return; }
const errors = validateForm(customerForm, ["contact_phone", "email", "business_number"]);
const errors = validateForm(customerForm, ["business_number"]);
setFormErrors(errors);
if (Object.keys(errors).length > 0) {
toast.error("입력 형식을 확인해주세요.");
@@ -1877,35 +1877,6 @@ export default function CustomerManagementPage() {
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_person || ""}
onChange={(e) => setCustomerForm((p) => ({ ...p, contact_person: e.target.value }))}
placeholder="거래처담당자"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_phone || ""}
onChange={(e) => handleFormChange("contact_phone", e.target.value)}
placeholder="010-0000-0000"
className={cn("h-9", formErrors.contact_phone && "border-destructive")}
/>
{formErrors.contact_phone && <p className="text-xs text-destructive">{formErrors.contact_phone}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.email || ""}
onChange={(e) => handleFormChange("email", e.target.value)}
placeholder="example@email.com"
className={cn("h-9", formErrors.email && "border-destructive")}
/>
{formErrors.email && <p className="text-xs text-destructive">{formErrors.email}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
@@ -517,29 +517,44 @@ export default function SalesOrderPage() {
}
};
// 삭제 (마스터 단위)
// 삭제 (선택한 디테일 삭제 → 디테일 0건인 마스터 자동 삭제)
const handleDelete = async () => {
if (checkedIds.length === 0) { toast.error("삭제할 수주를 선택해주세요."); return; }
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
const ok = await confirm(`${orderNos.length}건의 수주를 삭제하시겠습니까?`, {
if (checkedIds.length === 0) { toast.error("삭제할 항목을 선택해주세요."); return; }
const ok = await confirm(`${checkedIds.length}건의 수주 항목을 삭제하시겠습니까?`, {
description: "삭제된 데이터는 복구할 수 없습니다.",
variant: "destructive",
confirmText: "삭제",
});
if (!ok) return;
try {
// 1. 선택한 디테일 삭제
await apiClient.delete(`/table-management/tables/${DETAIL_TABLE}/delete`, {
data: checkedIds.map((id) => ({ id })),
});
// 2. 영향받는 수주번호의 잔여 디테일 확인 → 0건이면 마스터도 삭제
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
for (const orderNo of orderNos) {
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
const remainRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
page: 1, size: 1,
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
autoFilter: true,
});
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
if (masters.length > 0) {
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
data: masters.map((m: any) => ({ id: m.id })),
const remaining = remainRes.data?.data?.data || remainRes.data?.data?.rows || [];
if (remaining.length === 0) {
// 디테일 0건 → 마스터 삭제
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
page: 1, size: 1,
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
autoFilter: true,
});
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
if (masters.length > 0) {
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
data: masters.map((m: any) => ({ id: m.id })),
});
}
}
}
toast.success("삭제되었습니다.");
@@ -918,7 +933,7 @@ export default function SalesOrderPage() {
{/* 데이터 테이블 (플랫 리스트) */}
<div className="flex-1 overflow-hidden rounded-lg border border-border bg-card">
<div className="h-full overflow-auto">
<Table style={{ minWidth: "1500px" }}>
<Table noWrapper style={{ minWidth: "1500px" }}>
<colgroup>
<col style={{ width: "40px" }} />
<col style={{ width: "140px" }} />
@@ -1107,6 +1122,7 @@ export default function SalesOrderPage() {
<Select value={masterForm.input_mode || ""} onValueChange={(v) => {
setMasterForm((p) => {
const next = { ...p, input_mode: v };
// 입력방식 변경 시 거래처 관련 값 초기화
delete next.partner_id;
delete next.delivery_partner_id;
delete next.delivery_address;
@@ -715,7 +715,7 @@ export default function CustomerManagementPage() {
const handleCustomerSave = async () => {
if (!customerForm.customer_name) { toast.error("거래처명은 필수입니다."); return; }
if (!customerForm.status) { toast.error("상태는 필수입니다."); return; }
const errors = validateForm(customerForm, ["contact_phone", "email", "business_number"]);
const errors = validateForm(customerForm, ["business_number"]);
setFormErrors(errors);
if (Object.keys(errors).length > 0) {
toast.error("입력 형식을 확인해주세요.");
@@ -1877,35 +1877,6 @@ export default function CustomerManagementPage() {
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_person || ""}
onChange={(e) => setCustomerForm((p) => ({ ...p, contact_person: e.target.value }))}
placeholder="거래처담당자"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_phone || ""}
onChange={(e) => handleFormChange("contact_phone", e.target.value)}
placeholder="010-0000-0000"
className={cn("h-9", formErrors.contact_phone && "border-destructive")}
/>
{formErrors.contact_phone && <p className="text-xs text-destructive">{formErrors.contact_phone}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.email || ""}
onChange={(e) => handleFormChange("email", e.target.value)}
placeholder="example@email.com"
className={cn("h-9", formErrors.email && "border-destructive")}
/>
{formErrors.email && <p className="text-xs text-destructive">{formErrors.email}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
@@ -517,29 +517,44 @@ export default function SalesOrderPage() {
}
};
// 삭제 (마스터 단위)
// 삭제 (선택한 디테일 삭제 → 디테일 0건인 마스터 자동 삭제)
const handleDelete = async () => {
if (checkedIds.length === 0) { toast.error("삭제할 수주를 선택해주세요."); return; }
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
const ok = await confirm(`${orderNos.length}건의 수주를 삭제하시겠습니까?`, {
if (checkedIds.length === 0) { toast.error("삭제할 항목을 선택해주세요."); return; }
const ok = await confirm(`${checkedIds.length}건의 수주 항목을 삭제하시겠습니까?`, {
description: "삭제된 데이터는 복구할 수 없습니다.",
variant: "destructive",
confirmText: "삭제",
});
if (!ok) return;
try {
// 1. 선택한 디테일 삭제
await apiClient.delete(`/table-management/tables/${DETAIL_TABLE}/delete`, {
data: checkedIds.map((id) => ({ id })),
});
// 2. 영향받는 수주번호의 잔여 디테일 확인 → 0건이면 마스터도 삭제
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
for (const orderNo of orderNos) {
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
const remainRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
page: 1, size: 1,
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
autoFilter: true,
});
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
if (masters.length > 0) {
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
data: masters.map((m: any) => ({ id: m.id })),
const remaining = remainRes.data?.data?.data || remainRes.data?.data?.rows || [];
if (remaining.length === 0) {
// 디테일 0건 → 마스터 삭제
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
page: 1, size: 1,
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
autoFilter: true,
});
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
if (masters.length > 0) {
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
data: masters.map((m: any) => ({ id: m.id })),
});
}
}
}
toast.success("삭제되었습니다.");
@@ -918,7 +933,7 @@ export default function SalesOrderPage() {
{/* 데이터 테이블 (플랫 리스트) */}
<div className="flex-1 overflow-hidden rounded-lg border border-border bg-card">
<div className="h-full overflow-auto">
<Table style={{ minWidth: "1500px" }}>
<Table noWrapper style={{ minWidth: "1500px" }}>
<colgroup>
<col style={{ width: "40px" }} />
<col style={{ width: "140px" }} />
@@ -1107,6 +1122,7 @@ export default function SalesOrderPage() {
<Select value={masterForm.input_mode || ""} onValueChange={(v) => {
setMasterForm((p) => {
const next = { ...p, input_mode: v };
// 입력방식 변경 시 거래처 관련 값 초기화
delete next.partner_id;
delete next.delivery_partner_id;
delete next.delivery_address;
@@ -715,7 +715,7 @@ export default function CustomerManagementPage() {
const handleCustomerSave = async () => {
if (!customerForm.customer_name) { toast.error("거래처명은 필수입니다."); return; }
if (!customerForm.status) { toast.error("상태는 필수입니다."); return; }
const errors = validateForm(customerForm, ["contact_phone", "email", "business_number"]);
const errors = validateForm(customerForm, ["business_number"]);
setFormErrors(errors);
if (Object.keys(errors).length > 0) {
toast.error("입력 형식을 확인해주세요.");
@@ -1877,35 +1877,6 @@ export default function CustomerManagementPage() {
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_person || ""}
onChange={(e) => setCustomerForm((p) => ({ ...p, contact_person: e.target.value }))}
placeholder="거래처담당자"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_phone || ""}
onChange={(e) => handleFormChange("contact_phone", e.target.value)}
placeholder="010-0000-0000"
className={cn("h-9", formErrors.contact_phone && "border-destructive")}
/>
{formErrors.contact_phone && <p className="text-xs text-destructive">{formErrors.contact_phone}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.email || ""}
onChange={(e) => handleFormChange("email", e.target.value)}
placeholder="example@email.com"
className={cn("h-9", formErrors.email && "border-destructive")}
/>
{formErrors.email && <p className="text-xs text-destructive">{formErrors.email}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
File diff suppressed because it is too large Load Diff
@@ -715,7 +715,7 @@ export default function CustomerManagementPage() {
const handleCustomerSave = async () => {
if (!customerForm.customer_name) { toast.error("거래처명은 필수입니다."); return; }
if (!customerForm.status) { toast.error("상태는 필수입니다."); return; }
const errors = validateForm(customerForm, ["contact_phone", "email", "business_number"]);
const errors = validateForm(customerForm, ["business_number"]);
setFormErrors(errors);
if (Object.keys(errors).length > 0) {
toast.error("입력 형식을 확인해주세요.");
@@ -1877,35 +1877,6 @@ export default function CustomerManagementPage() {
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_person || ""}
onChange={(e) => setCustomerForm((p) => ({ ...p, contact_person: e.target.value }))}
placeholder="거래처담당자"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_phone || ""}
onChange={(e) => handleFormChange("contact_phone", e.target.value)}
placeholder="010-0000-0000"
className={cn("h-9", formErrors.contact_phone && "border-destructive")}
/>
{formErrors.contact_phone && <p className="text-xs text-destructive">{formErrors.contact_phone}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.email || ""}
onChange={(e) => handleFormChange("email", e.target.value)}
placeholder="example@email.com"
className={cn("h-9", formErrors.email && "border-destructive")}
/>
{formErrors.email && <p className="text-xs text-destructive">{formErrors.email}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
@@ -517,29 +517,44 @@ export default function SalesOrderPage() {
}
};
// 삭제 (마스터 단위)
// 삭제 (선택한 디테일 삭제 → 디테일 0건인 마스터 자동 삭제)
const handleDelete = async () => {
if (checkedIds.length === 0) { toast.error("삭제할 수주를 선택해주세요."); return; }
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
const ok = await confirm(`${orderNos.length}건의 수주를 삭제하시겠습니까?`, {
if (checkedIds.length === 0) { toast.error("삭제할 항목을 선택해주세요."); return; }
const ok = await confirm(`${checkedIds.length}건의 수주 항목을 삭제하시겠습니까?`, {
description: "삭제된 데이터는 복구할 수 없습니다.",
variant: "destructive",
confirmText: "삭제",
});
if (!ok) return;
try {
// 1. 선택한 디테일 삭제
await apiClient.delete(`/table-management/tables/${DETAIL_TABLE}/delete`, {
data: checkedIds.map((id) => ({ id })),
});
// 2. 영향받는 수주번호의 잔여 디테일 확인 → 0건이면 마스터도 삭제
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
for (const orderNo of orderNos) {
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
const remainRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
page: 1, size: 1,
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
autoFilter: true,
});
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
if (masters.length > 0) {
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
data: masters.map((m: any) => ({ id: m.id })),
const remaining = remainRes.data?.data?.data || remainRes.data?.data?.rows || [];
if (remaining.length === 0) {
// 디테일 0건 → 마스터 삭제
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
page: 1, size: 1,
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
autoFilter: true,
});
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
if (masters.length > 0) {
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
data: masters.map((m: any) => ({ id: m.id })),
});
}
}
}
toast.success("삭제되었습니다.");
@@ -715,7 +715,7 @@ export default function CustomerManagementPage() {
const handleCustomerSave = async () => {
if (!customerForm.customer_name) { toast.error("거래처명은 필수입니다."); return; }
if (!customerForm.status) { toast.error("상태는 필수입니다."); return; }
const errors = validateForm(customerForm, ["contact_phone", "email", "business_number"]);
const errors = validateForm(customerForm, ["business_number"]);
setFormErrors(errors);
if (Object.keys(errors).length > 0) {
toast.error("입력 형식을 확인해주세요.");
@@ -1877,35 +1877,6 @@ export default function CustomerManagementPage() {
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_person || ""}
onChange={(e) => setCustomerForm((p) => ({ ...p, contact_person: e.target.value }))}
placeholder="거래처담당자"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_phone || ""}
onChange={(e) => handleFormChange("contact_phone", e.target.value)}
placeholder="010-0000-0000"
className={cn("h-9", formErrors.contact_phone && "border-destructive")}
/>
{formErrors.contact_phone && <p className="text-xs text-destructive">{formErrors.contact_phone}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.email || ""}
onChange={(e) => handleFormChange("email", e.target.value)}
placeholder="example@email.com"
className={cn("h-9", formErrors.email && "border-destructive")}
/>
{formErrors.email && <p className="text-xs text-destructive">{formErrors.email}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
@@ -517,29 +517,44 @@ export default function SalesOrderPage() {
}
};
// 삭제 (마스터 단위)
// 삭제 (선택한 디테일 삭제 → 디테일 0건인 마스터 자동 삭제)
const handleDelete = async () => {
if (checkedIds.length === 0) { toast.error("삭제할 수주를 선택해주세요."); return; }
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
const ok = await confirm(`${orderNos.length}건의 수주를 삭제하시겠습니까?`, {
if (checkedIds.length === 0) { toast.error("삭제할 항목을 선택해주세요."); return; }
const ok = await confirm(`${checkedIds.length}건의 수주 항목을 삭제하시겠습니까?`, {
description: "삭제된 데이터는 복구할 수 없습니다.",
variant: "destructive",
confirmText: "삭제",
});
if (!ok) return;
try {
// 1. 선택한 디테일 삭제
await apiClient.delete(`/table-management/tables/${DETAIL_TABLE}/delete`, {
data: checkedIds.map((id) => ({ id })),
});
// 2. 영향받는 수주번호의 잔여 디테일 확인 → 0건이면 마스터도 삭제
const selectedItems = orders.filter((o) => checkedIds.includes(o.id));
const orderNos = [...new Set(selectedItems.map((o) => o.order_no))];
for (const orderNo of orderNos) {
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
const remainRes = await apiClient.post(`/table-management/tables/${DETAIL_TABLE}/data`, {
page: 1, size: 1,
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
autoFilter: true,
});
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
if (masters.length > 0) {
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
data: masters.map((m: any) => ({ id: m.id })),
const remaining = remainRes.data?.data?.data || remainRes.data?.data?.rows || [];
if (remaining.length === 0) {
// 디테일 0건 → 마스터 삭제
const masterRes = await apiClient.post(`/table-management/tables/${MASTER_TABLE}/data`, {
page: 1, size: 1,
dataFilter: { enabled: true, filters: [{ columnName: "order_no", operator: "equals", value: orderNo }] },
autoFilter: true,
});
const masters = masterRes.data?.data?.data || masterRes.data?.data?.rows || [];
if (masters.length > 0) {
await apiClient.delete(`/table-management/tables/${MASTER_TABLE}/delete`, {
data: masters.map((m: any) => ({ id: m.id })),
});
}
}
}
toast.success("삭제되었습니다.");
@@ -918,7 +933,7 @@ export default function SalesOrderPage() {
{/* 데이터 테이블 (플랫 리스트) */}
<div className="flex-1 overflow-hidden rounded-lg border border-border bg-card">
<div className="h-full overflow-auto">
<Table style={{ minWidth: "1500px" }}>
<Table noWrapper style={{ minWidth: "1500px" }}>
<colgroup>
<col style={{ width: "40px" }} />
<col style={{ width: "140px" }} />
@@ -1107,6 +1122,7 @@ export default function SalesOrderPage() {
<Select value={masterForm.input_mode || ""} onValueChange={(v) => {
setMasterForm((p) => {
const next = { ...p, input_mode: v };
// 입력방식 변경 시 거래처 관련 값 초기화
delete next.partner_id;
delete next.delivery_partner_id;
delete next.delivery_address;
@@ -715,7 +715,7 @@ export default function CustomerManagementPage() {
const handleCustomerSave = async () => {
if (!customerForm.customer_name) { toast.error("거래처명은 필수입니다."); return; }
if (!customerForm.status) { toast.error("상태는 필수입니다."); return; }
const errors = validateForm(customerForm, ["contact_phone", "email", "business_number"]);
const errors = validateForm(customerForm, ["business_number"]);
setFormErrors(errors);
if (Object.keys(errors).length > 0) {
toast.error("입력 형식을 확인해주세요.");
@@ -1877,35 +1877,6 @@ export default function CustomerManagementPage() {
</SelectContent>
</Select>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_person || ""}
onChange={(e) => setCustomerForm((p) => ({ ...p, contact_person: e.target.value }))}
placeholder="거래처담당자"
className="h-9"
/>
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.contact_phone || ""}
onChange={(e) => handleFormChange("contact_phone", e.target.value)}
placeholder="010-0000-0000"
className={cn("h-9", formErrors.contact_phone && "border-destructive")}
/>
{formErrors.contact_phone && <p className="text-xs text-destructive">{formErrors.contact_phone}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
value={customerForm.email || ""}
onChange={(e) => handleFormChange("email", e.target.value)}
placeholder="example@email.com"
className={cn("h-9", formErrors.email && "border-destructive")}
/>
{formErrors.email && <p className="text-xs text-destructive">{formErrors.email}</p>}
</div>
<div className="space-y-1.5">
<Label className="text-sm"></Label>
<Input
File diff suppressed because it is too large Load Diff