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

@@ -11,7 +11,7 @@
* 제거된 섹션: 자동분류규칙, 작업정보, 설명
*/
import { useState, useCallback, useEffect, useRef } from 'react';
import { useState, useCallback, useEffect, useRef, useMemo } from 'react';
import { useRouter } from 'next/navigation';
import { Plus, GripVertical, Trash2, Package } from 'lucide-react';
import { IntegratedDetailTemplate } from '@/components/templates/IntegratedDetailTemplate';
@@ -28,11 +28,10 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { RuleModal } from './RuleModal';
import { toast } from 'sonner';
import type { Process, ClassificationRule, ProcessType, ProcessStep } from '@/types/process';
import { PROCESS_TYPE_OPTIONS } from '@/types/process';
import { PROCESS_TYPE_OPTIONS, PROCESS_CATEGORY_OPTIONS } from '@/types/process';
import {
createProcess,
updateProcess,
@@ -57,6 +56,9 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
);
const [department, setDepartment] = useState(initialData?.department || '');
const [manager, setManager] = useState(initialData?.manager || '');
const [processCategory, setProcessCategory] = useState(
initialData?.processCategory || ''
);
const [useProductionDate, setUseProductionDate] = useState(
initialData?.useProductionDate ?? false
);
@@ -86,6 +88,24 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
const [dragIndex, setDragIndex] = useState<number | null>(null);
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
// 공정명에 따른 구분 옵션 계산
const categoryOptions = useMemo(() => {
const name = processName.trim();
for (const [key, options] of Object.entries(PROCESS_CATEGORY_OPTIONS)) {
if (name.includes(key)) return options;
}
return [{ value: '없음', label: '없음' }];
}, [processName]);
// 공정명 변경 시 구분 값 리셋
useEffect(() => {
if (categoryOptions.length === 0) {
setProcessCategory('');
} else if (processCategory && !categoryOptions.find(o => o.value === processCategory)) {
setProcessCategory('');
}
}, [categoryOptions]); // eslint-disable-line react-hooks/exhaustive-deps
// 품목 개수 계산
const itemCount = classificationRules
.filter((r) => r.registrationType === 'individual')
@@ -227,6 +247,7 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
const formData = {
processName: processName.trim(),
processType,
processCategory: processCategory || undefined,
department,
classificationRules: classificationRules.map((rule) => ({
registrationType: rule.registrationType,
@@ -288,7 +309,18 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
<CardTitle className="text-base"> </CardTitle>
</CardHeader>
<CardContent className="pt-6">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-6">
{/* Row 1: 공정번호(수정시) | 공정명 | 담당부서 | 담당자 */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
{isEdit && initialData?.processCode && (
<div className="space-y-2">
<Label></Label>
<Input
value={initialData.processCode}
disabled
className="bg-muted"
/>
</div>
)}
<div className="space-y-2">
<Label htmlFor="processName"> *</Label>
<Input
@@ -298,24 +330,6 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
placeholder="예: 스크린"
/>
</div>
<div className="space-y-2">
<Label></Label>
<Select
value={processType}
onValueChange={(v) => setProcessType(v as ProcessType)}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{PROCESS_TYPE_OPTIONS.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label> *</Label>
<Select
@@ -345,17 +359,42 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
placeholder="담당자명"
/>
</div>
</div>
{/* Row 2: 구분 | 생산일자 | 상태 */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mt-6">
<div className="space-y-2">
<Label></Label>
<Select
key={`category-${processName}`}
value={processCategory}
onValueChange={setProcessCategory}
>
<SelectTrigger>
<SelectValue placeholder="선택하세요" />
</SelectTrigger>
<SelectContent>
{categoryOptions.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{opt.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label></Label>
<div className="flex items-center gap-2 h-10">
<Switch
checked={useProductionDate}
onCheckedChange={setUseProductionDate}
/>
<span className="text-sm text-muted-foreground">
{useProductionDate ? '사용' : '미사용'}
</span>
</div>
<Select
value={useProductionDate ? '사용' : '미사용'}
onValueChange={(v) => setUseProductionDate(v === '사용')}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="사용"></SelectItem>
<SelectItem value="미사용"></SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label></Label>
@@ -399,7 +438,7 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
size="sm"
onClick={() => setRuleModalOpen(true)}
>
</Button>
</div>
</div>
@@ -553,12 +592,15 @@ export function ProcessForm({ mode, initialData }: ProcessFormProps) {
onAdd={handleSaveRule}
editRule={editingRule}
processId={initialData?.id}
processName={processName}
/>
</>
),
[
processName,
processType,
processCategory,
categoryOptions,
department,
manager,
useProductionDate,