feat(WEB): DatePicker 공통화 및 공정관리/작업자화면 대폭 개선

DatePicker 공통화:
- date-picker.tsx 공통 컴포넌트 신규 추가
- 전체 폼 컴포넌트 DatePicker 통일 적용 (50+ 파일)
- DateRangeSelector 개선

공정관리:
- RuleModal 대폭 리팩토링 (-592줄 → 간소화)
- ProcessForm, StepForm 개선
- ProcessDetail 수정, actions 확장

작업자화면:
- WorkerScreen 기능 대폭 확장 (+543줄)
- WorkItemCard 개선
- types 확장

회계/인사/영업/품질:
- BadDebtDetail, BillDetail, DepositDetail, SalesDetail 등 DatePicker 적용
- EmployeeForm, VacationDialog 등 DatePicker 적용
- OrderRegistration, QuoteRegistration DatePicker 적용
- InspectionCreate, InspectionDetail DatePicker 적용

공사관리/CEO대시보드:
- BiddingDetail, ContractDetail, HandoverReport 등 DatePicker 적용
- ScheduleDetailModal, TodayIssueSection 개선

기타:
- WorkOrderCreate/Edit/Detail/List 개선
- ShipmentCreate/Edit, ReceivingDetail 개선
- calendar, calendarEvents 수정
- datepicker 마이그레이션 체크리스트 추가

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-02-06 15:48:00 +09:00
parent e453753bdd
commit c2ed71540f
68 changed files with 1436 additions and 1134 deletions

View File

@@ -12,6 +12,7 @@
import { useState, useEffect, useCallback } from "react";
import { useRouter, useParams } from "next/navigation";
import { Input } from "@/components/ui/input";
import { DatePicker } from "@/components/ui/date-picker";
import { Textarea } from "@/components/ui/textarea";
import { PhoneInput } from "@/components/ui/phone-input";
import { Checkbox } from "@/components/ui/checkbox";
@@ -322,11 +323,10 @@ export default function OrderEditPage() {
<div className="space-y-2">
<Label></Label>
<div className="flex items-center gap-4">
<Input
type="date"
<DatePicker
value={form.expectedShipDate}
onChange={(e) =>
setForm({ ...form, expectedShipDate: e.target.value })
onChange={(date) =>
setForm({ ...form, expectedShipDate: date })
}
disabled={form.expectedShipDateUndecided}
className="flex-1"
@@ -358,11 +358,10 @@ export default function OrderEditPage() {
<Label>
<span className="text-red-500">*</span>
</Label>
<Input
type="date"
<DatePicker
value={form.deliveryRequestDate}
onChange={(e) =>
setForm({ ...form, deliveryRequestDate: e.target.value })
onChange={(date) =>
setForm({ ...form, deliveryRequestDate: date })
}
/>
</div>

View File

@@ -75,7 +75,6 @@ import {
} from "@/components/orders";
import { sendSalesOrderNotification } from "@/lib/actions/fcm";
import { OrderSalesDetailEdit } from "@/components/orders/OrderSalesDetailEdit";
import { getDepartmentTree, type DepartmentRecord } from "@/components/hr/DepartmentManagement/actions";
/**
* 수량 포맷 함수
@@ -159,8 +158,6 @@ export default function OrderDetailPage() {
const [isProductionDialogOpen, setIsProductionDialogOpen] = useState(false);
const [isCreatingProduction, setIsCreatingProduction] = useState(false);
const [productionPriority, setProductionPriority] = useState<"normal" | "high" | "urgent">("normal");
const [productionDepartmentId, setProductionDepartmentId] = useState<string>("");
const [departments, setDepartments] = useState<DepartmentRecord[]>([]);
const [productionMemo, setProductionMemo] = useState("");
// 생산지시 완료 알림 모달 상태
const [isProductionSuccessDialogOpen, setIsProductionSuccessDialogOpen] = useState(false);
@@ -208,45 +205,11 @@ export default function OrderDetailPage() {
router.push(`/sales/order-management-sales/${orderId}?mode=edit`);
};
// 부서 트리를 평탄화
const flattenDepartments = (depts: DepartmentRecord[]): DepartmentRecord[] => {
const result: DepartmentRecord[] = [];
const traverse = (list: DepartmentRecord[]) => {
for (const dept of list) {
result.push(dept);
if (dept.children?.length) traverse(dept.children);
}
};
traverse(depts);
return result;
};
const handleProductionOrder = async () => {
const handleProductionOrder = () => {
// 생산지시 생성 모달 열기
setProductionPriority("normal");
setProductionMemo("");
setProductionDepartmentId("");
setIsProductionDialogOpen(true);
// 부서 목록 로드
if (departments.length === 0) {
const result = await getDepartmentTree();
if (result.success && result.data) {
const flatList = flattenDepartments(result.data);
setDepartments(flatList);
// 디폴트: 생산부서
const defaultDept = flatList.find(d => d.name.includes("생산"));
if (defaultDept) {
setProductionDepartmentId(String(defaultDept.id));
}
}
} else {
// 이미 로드된 경우 디폴트 설정
const defaultDept = departments.find(d => d.name.includes("생산"));
if (defaultDept) {
setProductionDepartmentId(String(defaultDept.id));
}
}
};
// 생산지시 확정 처리
@@ -256,7 +219,6 @@ export default function OrderDetailPage() {
try {
const result = await createProductionOrder(order.id, {
priority: productionPriority,
departmentId: productionDepartmentId ? Number(productionDepartmentId) : undefined,
memo: productionMemo || undefined,
});
if (result.success && result.data) {
@@ -1295,26 +1257,6 @@ export default function OrderDetailPage() {
</div>
</div>
{/* 부서 */}
<div className="space-y-2">
<Label className="text-base font-medium"></Label>
<Select
value={productionDepartmentId}
onValueChange={setProductionDepartmentId}
>
<SelectTrigger>
<SelectValue placeholder="부서를 선택하세요" />
</SelectTrigger>
<SelectContent>
{departments.map((dept) => (
<SelectItem key={dept.id} value={String(dept.id)}>
{dept.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 비고 */}
<div className="space-y-2">
<Label htmlFor="productionMemo"></Label>