feat: [작업자화면] 공정 그룹(process_group) 도입 — 절곡 탭 1개로 통합 + 하위 공정 필터
This commit is contained in:
@@ -103,6 +103,7 @@ function transformApiToFrontend(apiData: ApiProcess): Process {
|
||||
workLogTemplateName: apiData.work_log_template_relation?.name ?? undefined,
|
||||
needsInspection: apiData.options?.needs_inspection ?? false,
|
||||
needsWorkLog: apiData.options?.needs_work_log ?? false,
|
||||
processGroup: apiData.options?.process_group ?? undefined,
|
||||
classificationRules: [...patternRules, ...individualRules],
|
||||
requiredWorkers: apiData.required_workers,
|
||||
equipmentInfo: apiData.equipment_info ?? undefined,
|
||||
|
||||
@@ -284,20 +284,50 @@ export default function WorkerScreen() {
|
||||
// 공정 목록 캐시
|
||||
const [processListCache, setProcessListCache] = useState<Process[]>([]);
|
||||
|
||||
// 활성 공정 목록 (탭용) - 공정관리에서 등록된 활성 공정만
|
||||
const processTabs = useMemo(() => {
|
||||
// 활성 공정 목록
|
||||
const activeProcesses = useMemo(() => {
|
||||
return processListCache.filter((p) => p.status === '사용중');
|
||||
}, [processListCache]);
|
||||
|
||||
// 공정 목록 로드 후 첫 번째 공정을 기본 선택 (폴백: 'screen')
|
||||
// 그룹별 탭 구성 (process_group 기준, 없으면 공정명 사용)
|
||||
const groupedTabs = useMemo(() => {
|
||||
const groupMap = new Map<string, Process[]>();
|
||||
activeProcesses.forEach((p) => {
|
||||
const group = p.processGroup || p.processName;
|
||||
if (!groupMap.has(group)) groupMap.set(group, []);
|
||||
groupMap.get(group)!.push(p);
|
||||
});
|
||||
return Array.from(groupMap.entries()).map(([group, processes]) => ({
|
||||
group,
|
||||
processes,
|
||||
// 그룹 내 첫 번째 공정 ID를 탭 value로 사용
|
||||
defaultProcessId: processes[0].id,
|
||||
}));
|
||||
}, [activeProcesses]);
|
||||
|
||||
// processTabs 호환 (기존 코드에서 참조)
|
||||
const processTabs = activeProcesses;
|
||||
|
||||
// 선택된 그룹 내 하위 공정 필터
|
||||
const [subProcessId, setSubProcessId] = useState<string>('all');
|
||||
|
||||
// 현재 탭의 그룹 정보
|
||||
const activeGroup = useMemo(() => {
|
||||
const process = processListCache.find((p) => p.id === activeTab);
|
||||
if (!process) return null;
|
||||
const group = process.processGroup || process.processName;
|
||||
return groupedTabs.find((g) => g.group === group) || null;
|
||||
}, [activeTab, processListCache, groupedTabs]);
|
||||
|
||||
// 공정 목록 로드 후 첫 번째 그룹을 기본 선택
|
||||
useEffect(() => {
|
||||
if (activeTab) return;
|
||||
if (processTabs.length > 0) {
|
||||
setActiveTab(processTabs[0].id);
|
||||
if (groupedTabs.length > 0) {
|
||||
setActiveTab(groupedTabs[0].defaultProcessId);
|
||||
} else if (!isLoading) {
|
||||
setActiveTab('screen');
|
||||
}
|
||||
}, [processTabs, activeTab, isLoading]);
|
||||
}, [groupedTabs, activeTab, isLoading]);
|
||||
|
||||
// 선택된 공정의 ProcessTab 키 (mock 데이터 및 기존 로직 호환용)
|
||||
const activeProcessTabKey: ProcessTab = useMemo(() => {
|
||||
@@ -358,17 +388,40 @@ export default function WorkerScreen() {
|
||||
|
||||
}, [selectedSidebarOrderId]);
|
||||
|
||||
// ===== 탭별 필터링된 작업 =====
|
||||
// ===== 탭별 필터링된 작업 (그룹 + 하위 공정 필터) =====
|
||||
const filteredWorkOrders = useMemo(() => {
|
||||
// 하위 공정이 특정되면 해당 공정만 필터
|
||||
if (subProcessId !== 'all') {
|
||||
const subProcess = processListCache.find((p) => p.id === subProcessId);
|
||||
if (subProcess) {
|
||||
const subName = subProcess.processName.toLowerCase();
|
||||
return workOrders.filter((order) => {
|
||||
const orderProcessName = (order.processName || '').toLowerCase();
|
||||
return orderProcessName.includes(subName) || subName.includes(orderProcessName);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 그룹 내 모든 공정 매칭
|
||||
if (activeGroup) {
|
||||
const groupProcessNames = activeGroup.processes.map((p) => p.processName.toLowerCase());
|
||||
return workOrders.filter((order) => {
|
||||
const orderProcessName = (order.processName || '').toLowerCase();
|
||||
return groupProcessNames.some((gpn) =>
|
||||
orderProcessName.includes(gpn) || gpn.includes(orderProcessName)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 폴백: 기존 방식
|
||||
const selectedProcess = processListCache.find((p) => p.id === activeTab);
|
||||
if (!selectedProcess) return workOrders;
|
||||
|
||||
const selectedName = selectedProcess.processName.toLowerCase();
|
||||
return workOrders.filter((order) => {
|
||||
const orderProcessName = (order.processName || '').toLowerCase();
|
||||
return orderProcessName.includes(selectedName) || selectedName.includes(orderProcessName);
|
||||
});
|
||||
}, [workOrders, activeTab, processListCache]);
|
||||
}, [workOrders, activeTab, activeGroup, subProcessId, processListCache]);
|
||||
|
||||
// ===== API WorkOrders → SidebarOrder 변환 =====
|
||||
const apiSidebarOrders: SidebarOrder[] = useMemo(() => {
|
||||
@@ -1313,10 +1366,15 @@ export default function WorkerScreen() {
|
||||
>
|
||||
<div className="overflow-x-auto -mx-3 px-3 md:mx-0 md:px-0">
|
||||
<TabsList className="w-max md:w-full">
|
||||
{processTabs.length > 0 ? (
|
||||
processTabs.map((proc) => (
|
||||
<TabsTrigger key={proc.id} value={proc.id} className="px-4 md:flex-1">
|
||||
{proc.processName}
|
||||
{groupedTabs.length > 0 ? (
|
||||
groupedTabs.map((g) => (
|
||||
<TabsTrigger
|
||||
key={g.defaultProcessId}
|
||||
value={g.defaultProcessId}
|
||||
className="px-4 md:flex-1"
|
||||
onClick={() => { setSubProcessId('all'); }}
|
||||
>
|
||||
{g.group}
|
||||
</TabsTrigger>
|
||||
))
|
||||
) : (
|
||||
@@ -1329,8 +1387,37 @@ export default function WorkerScreen() {
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
{(processTabs.length > 0
|
||||
? processTabs.map((p) => p.id)
|
||||
{/* 그룹 내 하위 공정 필터 (2개 이상일 때만 표시) */}
|
||||
{activeGroup && activeGroup.processes.length > 1 && (
|
||||
<div className="flex items-center gap-2 mt-2 px-1 overflow-x-auto">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSubProcessId('all')}
|
||||
className={cn(
|
||||
'px-3 py-1 rounded-full text-xs font-medium whitespace-nowrap transition-colors',
|
||||
subProcessId === 'all' ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
)}
|
||||
>
|
||||
전체
|
||||
</button>
|
||||
{activeGroup.processes.map((p) => (
|
||||
<button
|
||||
key={p.id}
|
||||
type="button"
|
||||
onClick={() => setSubProcessId(p.id)}
|
||||
className={cn(
|
||||
'px-3 py-1 rounded-full text-xs font-medium whitespace-nowrap transition-colors',
|
||||
subProcessId === p.id ? 'bg-blue-600 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
)}
|
||||
>
|
||||
{p.processName.replace(activeGroup.group, '').replace(/[()()]/g, '').trim() || p.processName}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(groupedTabs.length > 0
|
||||
? groupedTabs.map((g) => g.defaultProcessId)
|
||||
: ['screen', 'slat', 'bending']
|
||||
).map((tabValue) => (
|
||||
<TabsContent key={tabValue} value={tabValue} className="mt-4">
|
||||
|
||||
Reference in New Issue
Block a user