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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user