feat: 레이아웃/출하/생산/회계/대시보드 전반 개선

- HeaderFavoritesBar 대폭 개선
- Sidebar/AuthenticatedLayout 소폭 수정
- ShipmentCreate, VehicleDispatch 출하 관련 개선
- WorkOrderCreate/Edit, WorkerScreen 생산 관련 개선
- InspectionCreate 자재 입고검사 개선
- DailyReport, VendorDetail 회계 수정
- CEO 대시보드: CardManagement/DailyProduction/DailyAttendance 섹션 개선
- useCEODashboard, expense transformer 정비
- DocumentViewer, PDF generate route 소폭 수정
- bill-prototype 개발 페이지 추가
- mockData 불필요 데이터 제거
This commit is contained in:
유병철
2026-03-05 13:35:48 +09:00
parent c18c68b6b7
commit 00a6209347
23 changed files with 1689 additions and 517 deletions

View File

@@ -348,15 +348,28 @@ export default function WorkerScreen() {
// 작업지시별 단계 진행 캐시: { [workOrderId]: StepProgressItem[] }
const [stepProgressMap, setStepProgressMap] = useState<Record<string, StepProgressItem[]>>({});
// 데이터 로드
// 데이터 로드 (작업목록 + 공정목록 + 부서목록 병렬)
const loadData = useCallback(async () => {
setIsLoading(true);
try {
const result = await getMyWorkOrders();
if (result.success) {
setWorkOrders(result.data);
const [workOrderResult, processResult, deptResult] = await Promise.all([
getMyWorkOrders(),
getProcessList({ size: 100 }),
getDepartments(),
]);
if (workOrderResult.success) {
setWorkOrders(workOrderResult.data);
} else {
toast.error(result.error || '작업 목록 조회에 실패했습니다.');
toast.error(workOrderResult.error || '작업 목록 조회에 실패했습니다.');
}
if (processResult.success && processResult.data?.items) {
setProcessListCache(processResult.data.items);
}
if (deptResult.success) {
setDepartmentList(deptResult.data);
}
} catch (error) {
if (isNextRedirectError(error)) throw error;
@@ -369,10 +382,6 @@ export default function WorkerScreen() {
useEffect(() => {
loadData();
// 부서 목록 로드
getDepartments().then((res) => {
if (res.success) setDepartmentList(res.data);
});
}, [loadData]);
// 부서 선택 시 해당 부서 사용자 목록 로드
@@ -455,21 +464,6 @@ export default function WorkerScreen() {
// 공정 목록 캐시
const [processListCache, setProcessListCache] = useState<Process[]>([]);
// 공정 목록 조회 (최초 1회)
useEffect(() => {
const fetchProcessList = async () => {
try {
const result = await getProcessList({ size: 100 });
if (result.success && result.data?.items) {
setProcessListCache(result.data.items);
}
} catch (error) {
console.error('Failed to fetch process list:', error);
}
};
fetchProcessList();
}, []);
// 활성 공정 목록 (탭용) - 공정관리에서 등록된 활성 공정만
const processTabs = useMemo(() => {
return processListCache.filter((p) => p.status === '사용중');
@@ -1478,6 +1472,9 @@ export default function WorkerScreen() {
</div>
{/* 공정별 탭 (공정관리 API 기반 동적 생성) */}
{isLoading ? (
<ContentSkeleton type="list" rows={1} />
) : (
<Tabs
value={activeTab}
onValueChange={(v) => setActiveTab(v)}
@@ -1708,11 +1705,12 @@ export default function WorkerScreen() {
</TabsContent>
))}
</Tabs>
)}
</div>
{/* 하단 고정 버튼 */}
{(hasWipItems || activeProcessSettings.needsWorkLog || activeProcessSettings.hasDocumentTemplate) && (
<div className={`fixed bottom-4 left-3 right-3 px-3 py-3 bg-background/95 backdrop-blur rounded-xl border shadow-lg z-50 transition-all duration-300 md:bottom-6 md:left-auto md:right-[24px] md:px-6 ${sidebarCollapsed ? 'md:left-[113px]' : 'md:left-[304px]'}`}>
<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-[24px] ${sidebarCollapsed ? 'md:left-[120px]' : 'md:left-[280px]'}`}>
<div className="flex gap-2 md:gap-3">
{hasWipItems ? (
<Button