feat(WEB): API 인프라 리팩토링, CEO 대시보드 현황판 개선 및 문서 시스템 강화
- API: fetch-wrapper/proxy/refresh-token 리팩토링, authenticated-fetch 신규 추가 - CEO 대시보드: EnhancedSections 현황판 기능 개선, dashboard transformers/types 확장 - 문서 시스템: ApprovalLine/DocumentHeader/DocumentToolbar/DocumentViewer 개선 - 작업지시서: 검사보고서/작업일지 문서 컴포넌트 개선 (벤딩/스크린/슬랫) - 레이아웃: Sidebar/AuthenticatedLayout 수정 - 작업자화면: WorkerScreen 수정 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -191,6 +191,20 @@ export const BendingInspectionContent = forwardRef<InspectionContentRef, Bending
|
||||
}),
|
||||
}), [products, inadequateContent, overallResult]);
|
||||
|
||||
// PDF 호환 체크박스 렌더
|
||||
const renderCheckbox = (checked: boolean, onClick: () => void) => (
|
||||
<span
|
||||
className={`inline-flex items-center justify-center w-3 h-3 border rounded-sm text-[8px] leading-none cursor-pointer select-none ${
|
||||
checked ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-400 bg-white'
|
||||
}`}
|
||||
onClick={() => !readOnly && onClick()}
|
||||
role="checkbox"
|
||||
aria-checked={checked}
|
||||
>
|
||||
{checked ? '✓' : ''}
|
||||
</span>
|
||||
);
|
||||
|
||||
const inputClass = 'w-full text-center border-0 bg-transparent focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-xs';
|
||||
|
||||
// 전체 행 수 계산 (간격 포인트 수 합계)
|
||||
@@ -202,7 +216,7 @@ export const BendingInspectionContent = forwardRef<InspectionContentRef, Bending
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">중간검사성적서 (절곡)</h1>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
|
||||
문서번호: {documentNo} | 작성일자: {fullDate}
|
||||
</p>
|
||||
</div>
|
||||
@@ -224,10 +238,10 @@ export const BendingInspectionContent = forwardRef<InspectionContentRef, Bending
|
||||
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400">이름</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -426,24 +440,12 @@ export const BendingInspectionContent = forwardRef<InspectionContentRef, Bending
|
||||
{/* 절곡상태 - 양호/불량 체크 */}
|
||||
<td className="border border-gray-400 p-1" rowSpan={rowCount}>
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={product.bendingStatus === '양호'}
|
||||
onChange={() => handleStatusChange(product.id, product.bendingStatus === '양호' ? null : '양호')}
|
||||
disabled={readOnly}
|
||||
className="w-3 h-3"
|
||||
/>
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
|
||||
{renderCheckbox(product.bendingStatus === '양호', () => handleStatusChange(product.id, product.bendingStatus === '양호' ? null : '양호'))}
|
||||
양호
|
||||
</label>
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={product.bendingStatus === '불량'}
|
||||
onChange={() => handleStatusChange(product.id, product.bendingStatus === '불량' ? null : '불량')}
|
||||
disabled={readOnly}
|
||||
className="w-3 h-3"
|
||||
/>
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
|
||||
{renderCheckbox(product.bendingStatus === '불량', () => handleStatusChange(product.id, product.bendingStatus === '불량' ? null : '불량'))}
|
||||
불량
|
||||
</label>
|
||||
</div>
|
||||
@@ -484,16 +486,13 @@ export const BendingInspectionContent = forwardRef<InspectionContentRef, Bending
|
||||
<table className="w-full border-collapse text-xs">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-top">부적합 내용</td>
|
||||
<td className="border border-gray-400 px-3 py-2" colSpan={2}>
|
||||
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-middle text-center">부적합 내용</td>
|
||||
<td className="border border-gray-400 px-3 py-2">
|
||||
<textarea value={inadequateContent} onChange={(e) => !readOnly && setInadequateContent(e.target.value)} disabled={readOnly}
|
||||
className="w-full border-0 bg-transparent focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-xs resize-none" rows={2} />
|
||||
</td>
|
||||
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium text-center w-24">종합판정</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-3 py-2" colSpan={3}></td>
|
||||
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm ${
|
||||
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm w-24 ${
|
||||
overallResult === '합격' ? 'text-blue-600' : overallResult === '불합격' ? 'text-red-600' : 'text-gray-400'
|
||||
}`}>
|
||||
{overallResult || '합격'}
|
||||
|
||||
@@ -53,7 +53,7 @@ export function BendingWorkLogContent({ data: order }: BendingWorkLogContentProp
|
||||
{/* 좌측: 제목 + 문서번호 */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">작업일지 (절곡)</h1>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
|
||||
문서번호: {documentNo} | 작성일자: {fullDate}
|
||||
</p>
|
||||
</div>
|
||||
@@ -75,10 +75,10 @@ export function BendingWorkLogContent({ data: order }: BendingWorkLogContentProp
|
||||
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400">이름</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -161,7 +161,7 @@ export function InspectionReportModal({
|
||||
) : (
|
||||
<Save className="w-4 h-4 mr-1.5" />
|
||||
)}
|
||||
저장
|
||||
{isSaving ? '저장 중...' : '저장'}
|
||||
</Button>
|
||||
) : undefined;
|
||||
|
||||
|
||||
@@ -83,10 +83,19 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
|
||||
));
|
||||
}, [readOnly]);
|
||||
|
||||
// 숫자 콤마 포맷
|
||||
const formatNumberWithComma = (value: string): string => {
|
||||
const num = value.replace(/[^\d]/g, '');
|
||||
if (!num) return '';
|
||||
return Number(num).toLocaleString();
|
||||
};
|
||||
|
||||
const handleInputChange = useCallback((rowId: number, field: 'lengthMeasured' | 'widthMeasured', value: string) => {
|
||||
if (readOnly) return;
|
||||
// 숫자만 저장 (콤마 제거)
|
||||
const numOnly = value.replace(/[^\d]/g, '');
|
||||
setRows(prev => prev.map(row =>
|
||||
row.id === rowId ? { ...row, [field]: value } : row
|
||||
row.id === rowId ? { ...row, [field]: numOnly } : row
|
||||
));
|
||||
}, [readOnly]);
|
||||
|
||||
@@ -135,28 +144,29 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
|
||||
}),
|
||||
}), [rows, inadequateContent, overallResult]);
|
||||
|
||||
// 체크박스 렌더 (양호/불량)
|
||||
// PDF 호환 체크박스 렌더 (양호/불량)
|
||||
const renderCheckbox = (checked: boolean, onClick: () => void) => (
|
||||
<span
|
||||
className={`inline-flex items-center justify-center w-3 h-3 border rounded-sm text-[8px] leading-none cursor-pointer select-none ${
|
||||
checked ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-400 bg-white'
|
||||
}`}
|
||||
onClick={() => !readOnly && onClick()}
|
||||
role="checkbox"
|
||||
aria-checked={checked}
|
||||
>
|
||||
{checked ? '✓' : ''}
|
||||
</span>
|
||||
);
|
||||
|
||||
const renderCheckStatus = (rowId: number, field: 'processStatus' | 'sewingStatus' | 'assemblyStatus', value: CheckStatus) => (
|
||||
<td className="border border-gray-400 p-1">
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value === '양호'}
|
||||
onChange={() => handleStatusChange(rowId, field, value === '양호' ? null : '양호')}
|
||||
disabled={readOnly}
|
||||
className="w-3 h-3"
|
||||
/>
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
|
||||
{renderCheckbox(value === '양호', () => handleStatusChange(rowId, field, value === '양호' ? null : '양호'))}
|
||||
양호
|
||||
</label>
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value === '불량'}
|
||||
onChange={() => handleStatusChange(rowId, field, value === '불량' ? null : '불량')}
|
||||
disabled={readOnly}
|
||||
className="w-3 h-3"
|
||||
/>
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
|
||||
{renderCheckbox(value === '불량', () => handleStatusChange(rowId, field, value === '불량' ? null : '불량'))}
|
||||
불량
|
||||
</label>
|
||||
</div>
|
||||
@@ -171,7 +181,7 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">중간검사성적서 (스크린)</h1>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
|
||||
문서번호: {documentNo} | 작성일자: {fullDate}
|
||||
</p>
|
||||
</div>
|
||||
@@ -193,10 +203,10 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
|
||||
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400">이름</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -332,35 +342,23 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
|
||||
{/* 길이 - 도면치수 표시 + 측정값 입력 */}
|
||||
<td className="border border-gray-400 p-1 text-center">{row.lengthDesign}</td>
|
||||
<td className="border border-gray-400 p-1">
|
||||
<input type="text" value={row.lengthMeasured} onChange={(e) => handleInputChange(row.id, 'lengthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" />
|
||||
<input type="text" value={formatNumberWithComma(row.lengthMeasured)} onChange={(e) => handleInputChange(row.id, 'lengthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" />
|
||||
</td>
|
||||
{/* 나비 - 도면치수 표시 + 측정값 입력 */}
|
||||
<td className="border border-gray-400 p-1 text-center">{row.widthDesign}</td>
|
||||
<td className="border border-gray-400 p-1">
|
||||
<input type="text" value={row.widthMeasured} onChange={(e) => handleInputChange(row.id, 'widthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" />
|
||||
<input type="text" value={formatNumberWithComma(row.widthMeasured)} onChange={(e) => handleInputChange(row.id, 'widthMeasured', e.target.value)} disabled={readOnly} className={inputClass} placeholder="-" />
|
||||
</td>
|
||||
{/* 간격 - 기준치 표시 + OK/NG 선택 */}
|
||||
<td className="border border-gray-400 p-1 text-center">{row.gapStandard}</td>
|
||||
<td className="border border-gray-400 p-1">
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={row.gapResult === 'OK'}
|
||||
onChange={() => handleGapChange(row.id, row.gapResult === 'OK' ? null : 'OK')}
|
||||
disabled={readOnly}
|
||||
className="w-3 h-3"
|
||||
/>
|
||||
{renderCheckbox(row.gapResult === 'OK', () => handleGapChange(row.id, row.gapResult === 'OK' ? null : 'OK'))}
|
||||
OK
|
||||
</label>
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={row.gapResult === 'NG'}
|
||||
onChange={() => handleGapChange(row.id, row.gapResult === 'NG' ? null : 'NG')}
|
||||
disabled={readOnly}
|
||||
className="w-3 h-3"
|
||||
/>
|
||||
{renderCheckbox(row.gapResult === 'NG', () => handleGapChange(row.id, row.gapResult === 'NG' ? null : 'NG'))}
|
||||
NG
|
||||
</label>
|
||||
</div>
|
||||
@@ -381,16 +379,13 @@ export const ScreenInspectionContent = forwardRef<InspectionContentRef, ScreenIn
|
||||
<table className="w-full border-collapse text-xs">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-top">부적합 내용</td>
|
||||
<td className="border border-gray-400 px-3 py-2" colSpan={2}>
|
||||
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-middle text-center">부적합 내용</td>
|
||||
<td className="border border-gray-400 px-3 py-2">
|
||||
<textarea value={inadequateContent} onChange={(e) => !readOnly && setInadequateContent(e.target.value)} disabled={readOnly}
|
||||
className="w-full border-0 bg-transparent focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-xs resize-none" rows={2} />
|
||||
</td>
|
||||
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium text-center w-24">종합판정</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-3 py-2" colSpan={3}></td>
|
||||
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm ${
|
||||
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm w-24 ${
|
||||
overallResult === '합격' ? 'text-blue-600' : overallResult === '불합격' ? 'text-red-600' : 'text-gray-400'
|
||||
}`}>
|
||||
{overallResult || '합격'}
|
||||
|
||||
@@ -55,7 +55,7 @@ export function ScreenWorkLogContent({ data: order }: ScreenWorkLogContentProps)
|
||||
{/* 좌측: 제목 + 문서번호 */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">작업일지 (스크린)</h1>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
|
||||
문서번호: {documentNo} | 작성일자: {fullDate}
|
||||
</p>
|
||||
</div>
|
||||
@@ -77,10 +77,10 @@ export function ScreenWorkLogContent({ data: order }: ScreenWorkLogContentProps)
|
||||
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400">이름</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -83,8 +83,10 @@ export const SlatInspectionContent = forwardRef<InspectionContentRef, SlatInspec
|
||||
|
||||
const handleInputChange = useCallback((rowId: number, field: keyof InspectionRow, value: string) => {
|
||||
if (readOnly) return;
|
||||
// 숫자 + 소수점만 허용
|
||||
const filtered = value.replace(/[^\d.]/g, '');
|
||||
setRows(prev => prev.map(row =>
|
||||
row.id === rowId ? { ...row, [field]: value } : row
|
||||
row.id === rowId ? { ...row, [field]: filtered } : row
|
||||
));
|
||||
}, [readOnly]);
|
||||
|
||||
@@ -119,28 +121,29 @@ export const SlatInspectionContent = forwardRef<InspectionContentRef, SlatInspec
|
||||
}),
|
||||
}), [rows, inadequateContent, overallResult]);
|
||||
|
||||
// 체크박스 렌더 (양호/불량)
|
||||
// PDF 호환 체크박스 렌더 (양호/불량)
|
||||
const renderCheckbox = (checked: boolean, onClick: () => void) => (
|
||||
<span
|
||||
className={`inline-flex items-center justify-center w-3 h-3 border rounded-sm text-[8px] leading-none cursor-pointer select-none ${
|
||||
checked ? 'border-gray-600 bg-gray-700 text-white' : 'border-gray-400 bg-white'
|
||||
}`}
|
||||
onClick={() => !readOnly && onClick()}
|
||||
role="checkbox"
|
||||
aria-checked={checked}
|
||||
>
|
||||
{checked ? '✓' : ''}
|
||||
</span>
|
||||
);
|
||||
|
||||
const renderCheckStatus = (rowId: number, field: 'processStatus' | 'assemblyStatus', value: CheckStatus) => (
|
||||
<td className="border border-gray-400 p-1">
|
||||
<div className="flex flex-col items-center gap-0.5">
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value === '양호'}
|
||||
onChange={() => handleStatusChange(rowId, field, value === '양호' ? null : '양호')}
|
||||
disabled={readOnly}
|
||||
className="w-3 h-3"
|
||||
/>
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
|
||||
{renderCheckbox(value === '양호', () => handleStatusChange(rowId, field, value === '양호' ? null : '양호'))}
|
||||
양호
|
||||
</label>
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value === '불량'}
|
||||
onChange={() => handleStatusChange(rowId, field, value === '불량' ? null : '불량')}
|
||||
disabled={readOnly}
|
||||
className="w-3 h-3"
|
||||
/>
|
||||
<label className="flex items-center gap-0.5 cursor-pointer text-xs whitespace-nowrap">
|
||||
{renderCheckbox(value === '불량', () => handleStatusChange(rowId, field, value === '불량' ? null : '불량'))}
|
||||
불량
|
||||
</label>
|
||||
</div>
|
||||
@@ -155,7 +158,7 @@ export const SlatInspectionContent = forwardRef<InspectionContentRef, SlatInspec
|
||||
<div className="flex justify-between items-start mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">중간검사성적서 (슬랫)</h1>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
|
||||
문서번호: {documentNo} | 작성일자: {fullDate}
|
||||
</p>
|
||||
</div>
|
||||
@@ -177,10 +180,10 @@ export const SlatInspectionContent = forwardRef<InspectionContentRef, SlatInspec
|
||||
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400">이름</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -339,16 +342,13 @@ export const SlatInspectionContent = forwardRef<InspectionContentRef, SlatInspec
|
||||
<table className="w-full border-collapse text-xs">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-top">부적합 내용</td>
|
||||
<td className="border border-gray-400 px-3 py-2" colSpan={2}>
|
||||
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium w-24 align-middle text-center">부적합 내용</td>
|
||||
<td className="border border-gray-400 px-3 py-2">
|
||||
<textarea value={inadequateContent} onChange={(e) => !readOnly && setInadequateContent(e.target.value)} disabled={readOnly}
|
||||
className="w-full border-0 bg-transparent focus:outline-none focus:ring-1 focus:ring-blue-500 rounded text-xs resize-none" rows={2} />
|
||||
</td>
|
||||
<td className="border border-gray-400 bg-gray-100 px-3 py-2 font-medium text-center w-24">종합판정</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-3 py-2" colSpan={3}></td>
|
||||
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm ${
|
||||
<td className={`border border-gray-400 px-3 py-2 text-center font-bold text-sm w-24 ${
|
||||
overallResult === '합격' ? 'text-blue-600' : overallResult === '불합격' ? 'text-red-600' : 'text-gray-400'
|
||||
}`}>
|
||||
{overallResult || '합격'}
|
||||
|
||||
@@ -51,7 +51,7 @@ export function SlatWorkLogContent({ data: order }: SlatWorkLogContentProps) {
|
||||
{/* 좌측: 제목 + 문서번호 */}
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">작업일지 (슬랫)</h1>
|
||||
<p className="text-xs text-gray-500 mt-1">
|
||||
<p className="text-xs text-gray-500 mt-1 whitespace-nowrap">
|
||||
문서번호: {documentNo} | 작성일자: {fullDate}
|
||||
</p>
|
||||
</div>
|
||||
@@ -73,10 +73,10 @@ export function SlatWorkLogContent({ data: order }: SlatWorkLogContentProps) {
|
||||
<td className="border border-gray-400 px-6 py-3 text-center text-gray-400">이름</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
<td className="border border-gray-400 px-6 py-1 text-center whitespace-nowrap">부서명</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import { useMenuStore } from '@/store/menuStore';
|
||||
import { ClipboardList, PlayCircle, CheckCircle2, AlertTriangle } from 'lucide-react';
|
||||
import { ContentSkeleton } from '@/components/ui/skeleton';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
@@ -188,6 +189,7 @@ const PROCESS_STEPS: Record<ProcessTab, { name: string; isMaterialInput: boolean
|
||||
|
||||
export default function WorkerScreen() {
|
||||
// ===== 상태 관리 =====
|
||||
const { sidebarCollapsed } = useMenuStore();
|
||||
const [workOrders, setWorkOrders] = useState<WorkOrder[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [activeTab, setActiveTab] = useState<ProcessTab>('screen');
|
||||
@@ -509,7 +511,7 @@ export default function WorkerScreen() {
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-6 pb-20">
|
||||
{/* 완료 토스트 */}
|
||||
{toastInfo && <CompletionToast info={toastInfo} />}
|
||||
|
||||
@@ -656,8 +658,8 @@ export default function WorkerScreen() {
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
{/* 하단 고정 버튼 */}
|
||||
<div className="sticky bottom-0 border-t border-gray-200 pt-4 pb-2 z-10">
|
||||
{/* 하단 고정 버튼 - DetailActions 패턴 적용 */}
|
||||
<div className={`fixed bottom-4 left-4 right-4 px-4 py-3 bg-background/95 backdrop-blur rounded-xl border shadow-lg z-50 transition-all duration-300 md:bottom-6 md:px-6 md:right-[48px] ${sidebarCollapsed ? 'md:left-[156px]' : 'md:left-[316px]'}`}>
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
|
||||
Reference in New Issue
Block a user