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