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:
@@ -150,15 +150,15 @@ export function ApprovalLine({
|
||||
{/* 부서 행 (선택적) */}
|
||||
{showDepartment && (
|
||||
<tr>
|
||||
<td className="w-16 p-2 text-center bg-gray-50 border border-gray-300 text-[10px]">
|
||||
<td className="w-16 p-2 text-center bg-gray-50 border border-gray-300 text-[10px] whitespace-nowrap">
|
||||
{departmentLabels.writer}
|
||||
</td>
|
||||
{is4Col && (
|
||||
<td className="w-16 p-2 text-center bg-gray-50 border border-gray-300 text-[10px]">
|
||||
<td className="w-16 p-2 text-center bg-gray-50 border border-gray-300 text-[10px] whitespace-nowrap">
|
||||
{departmentLabels.reviewer}
|
||||
</td>
|
||||
)}
|
||||
<td className="w-16 p-2 text-center bg-gray-50 border border-gray-300 text-[10px]">
|
||||
<td className="w-16 p-2 text-center bg-gray-50 border border-gray-300 text-[10px] whitespace-nowrap">
|
||||
{departmentLabels.approver}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -96,16 +96,16 @@ export function ConstructionApprovalTable({
|
||||
|
||||
{/* 부서 행 */}
|
||||
<tr>
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500 whitespace-nowrap">
|
||||
{approvers.writer?.department || '부서명'}
|
||||
</td>
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500 whitespace-nowrap">
|
||||
{approvers.approver1?.department || '부서명'}
|
||||
</td>
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500 whitespace-nowrap">
|
||||
{approvers.approver2?.department || '부서명'}
|
||||
</td>
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500">
|
||||
<td className="border border-gray-300 px-3 py-1 text-center text-xs text-gray-500 whitespace-nowrap">
|
||||
{approvers.approver3?.department || '부서명'}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -213,7 +213,7 @@ export function DocumentHeader({
|
||||
<span className="text-2xl font-bold">{logo.text}</span>
|
||||
)}
|
||||
{logo.subtext && (
|
||||
<span className="text-xs text-gray-500">{logo.subtext}</span>
|
||||
<span className="text-xs text-gray-500 whitespace-nowrap">{logo.subtext}</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -74,7 +74,7 @@ export function QualityApprovalTable({
|
||||
</tbody>
|
||||
</table>
|
||||
{reportDate && (
|
||||
<div className="text-xs text-right mt-1">접고일자: {reportDate}</div>
|
||||
<div className="text-xs text-right mt-1 whitespace-nowrap">접고일자: {reportDate}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
FileOutput,
|
||||
Mail,
|
||||
MessageCircle,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -41,6 +42,9 @@ interface DocumentToolbarProps {
|
||||
onFax?: () => void;
|
||||
onKakao?: () => void;
|
||||
toolbarExtra?: ReactNode;
|
||||
|
||||
// 로딩 상태
|
||||
loadingAction?: ActionType | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,8 +69,10 @@ export function DocumentToolbar({
|
||||
onFax,
|
||||
onKakao,
|
||||
toolbarExtra,
|
||||
loadingAction,
|
||||
}: DocumentToolbarProps) {
|
||||
const showZoomControls = features.zoom !== false;
|
||||
const isAnyLoading = !!loadingAction;
|
||||
|
||||
// 액션 버튼 렌더링
|
||||
const renderActionButton = (action: ActionType) => {
|
||||
@@ -80,9 +86,14 @@ export function DocumentToolbar({
|
||||
size="sm"
|
||||
className="h-8 gap-1 text-xs px-2 sm:px-3"
|
||||
onClick={onPrint}
|
||||
disabled={isAnyLoading}
|
||||
>
|
||||
<Printer size={14} />
|
||||
<span className="hidden sm:inline">인쇄</span>
|
||||
{loadingAction === 'print' ? (
|
||||
<Loader2 size={14} className="animate-spin" />
|
||||
) : (
|
||||
<Printer size={14} />
|
||||
)}
|
||||
<span className="hidden sm:inline">{loadingAction === 'print' ? '인쇄 중...' : '인쇄'}</span>
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -94,9 +105,14 @@ export function DocumentToolbar({
|
||||
size="sm"
|
||||
className="h-8 bg-green-600 hover:bg-green-700 text-white gap-1 text-xs px-2 sm:px-3"
|
||||
onClick={onDownload}
|
||||
disabled={isAnyLoading}
|
||||
>
|
||||
<Download size={14} />
|
||||
<span className="hidden sm:inline">다운로드</span>
|
||||
{loadingAction === 'download' ? (
|
||||
<Loader2 size={14} className="animate-spin" />
|
||||
) : (
|
||||
<Download size={14} />
|
||||
)}
|
||||
<span className="hidden sm:inline">{loadingAction === 'download' ? '다운로드 중...' : '다운로드'}</span>
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -196,9 +212,14 @@ export function DocumentToolbar({
|
||||
size="sm"
|
||||
className="h-8 bg-red-600 hover:bg-red-700 text-white gap-1 text-xs px-2 sm:px-3"
|
||||
onClick={onPdf}
|
||||
disabled={isAnyLoading}
|
||||
>
|
||||
<Download size={14} />
|
||||
<span className="hidden sm:inline">PDF</span>
|
||||
{loadingAction === 'pdf' ? (
|
||||
<Loader2 size={14} className="animate-spin" />
|
||||
) : (
|
||||
<Download size={14} />
|
||||
)}
|
||||
<span className="hidden sm:inline">{loadingAction === 'pdf' ? 'PDF 생성 중...' : 'PDF'}</span>
|
||||
</Button>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import React, { useEffect, ReactNode, useCallback } from 'react';
|
||||
import React, { useEffect, useState, ReactNode, useCallback } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -102,11 +102,23 @@ export function DocumentViewer({
|
||||
const features: DocumentFeatures = merged.features;
|
||||
const actions: ActionType[] = merged.actions;
|
||||
|
||||
// 로딩 상태
|
||||
const [loadingAction, setLoadingAction] = useState<ActionType | null>(null);
|
||||
|
||||
// 줌 훅
|
||||
const zoom = useZoom();
|
||||
const zoomBase = useZoom();
|
||||
|
||||
// 드래그 훅
|
||||
const drag = useDrag({ enabled: features.drag !== false && zoom.zoom > 100 });
|
||||
const drag = useDrag({ enabled: features.drag !== false && zoomBase.zoom > 100 });
|
||||
|
||||
// 맞춤 클릭 시 드래그 위치도 함께 리셋
|
||||
const zoom = {
|
||||
...zoomBase,
|
||||
zoomReset: () => {
|
||||
zoomBase.zoomReset();
|
||||
drag.resetPosition();
|
||||
},
|
||||
};
|
||||
|
||||
// 모달 열릴 때 상태 초기화
|
||||
useEffect(() => {
|
||||
@@ -182,6 +194,7 @@ export function DocumentViewer({
|
||||
}
|
||||
|
||||
try {
|
||||
setLoadingAction('pdf');
|
||||
toast.loading('PDF 생성 중...', { id: 'pdf-generating' });
|
||||
|
||||
// print-area 영역 찾기
|
||||
@@ -234,6 +247,8 @@ export function DocumentViewer({
|
||||
} catch (error) {
|
||||
console.error('PDF 생성 오류:', error);
|
||||
toast.error('PDF 생성 중 오류가 발생했습니다.', { id: 'pdf-generating' });
|
||||
} finally {
|
||||
setLoadingAction(null);
|
||||
}
|
||||
}, [onPdf, title, pdfMeta]);
|
||||
|
||||
@@ -301,6 +316,7 @@ export function DocumentViewer({
|
||||
onFax={onFax}
|
||||
onKakao={onKakao}
|
||||
toolbarExtra={toolbarExtra}
|
||||
loadingAction={loadingAction}
|
||||
/>
|
||||
|
||||
{/* 콘텐츠 */}
|
||||
|
||||
Reference in New Issue
Block a user