Files
issue-sample/cost-pdf.md

60 KiB

Cost PDF Update

이 파일은 test.html의 업데이트 버전인 cost-pdf.html의 내용을 설명하기 위해 생성되었습니다.

업데이트 설명

cost-pdf.html은 기존 test.html (v3.3)에서 v5.5로 업데이트된 버전으로, 주요 변경 사항은 다음과 같습니다:

  • UI 디자인 개선 (Pretendard 폰트 및 유리 스타일 네비게이션 적용)
  • 전문 보고서 인쇄 스타일 최적화 (A4 가로 너비)
  • 인사 관리 기능 강화 (FactoryWorker.xls 연동 및 팀별 필터링)
  • 제품 형식별 제조 원가 분석 및 단위당 원가 계산 기능 추가

cost-pdf.html 소스 코드

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>(주)장헌 통합 원가 정산 시스템 v5.5</title>

  <!-- React / ReactDOM -->
  <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
  <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

  <!-- Babel -->
  <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

  <!-- Tailwind CSS -->
  <script src="https://cdn.tailwindcss.com"></script>

  <!-- SheetJS -->
  <script src="https://cdn.sheetjs.com/xlsx-0.20.1/package/dist/xlsx.full.min.js"></script>

  <!-- Lucide Icons -->
  <script src="https://unpkg.com/lucide@latest"></script>

  <style>
    @import url('https://fonts.googleapis.com/css2?family=Pretendard:wght@400;600;700;800&display=swap');
    
    html, body { 
      height: 100%; 
      font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, system-ui, Roboto, sans-serif;
      background-color: #f1f5f9; 
      color: #334155;
    }
    
    .animate-fade-in { animation: fadeIn 0.2s ease-out; }
    @keyframes fadeIn { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } }
    
    .custom-scrollbar::-webkit-scrollbar { width: 5px; height: 5px; }
    .custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
    .custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }

    .glass-nav {
      background: rgba(255, 255, 255, 0.95);
      backdrop-filter: blur(8px);
      border-bottom: 1px solid #e2e8f0;
    }
    
    /* 대시보드 테이블 가독성 스타일 */
    .dashboard-table th {
      background-color: #f8fafc;
      font-size: 12px;
      font-weight: 700;
      color: #64748b;
      text-transform: uppercase;
      padding: 12px 16px;
      border-bottom: 1px solid #e2e8f0;
    }

    /* 보고서 표 공통 규격(화면) */
    .report-table {
      width: 100%;
      border-collapse: collapse;
      table-layout: fixed;
      border: 1px solid #e2e8f0;
      background-color: #fff;
    }
    .report-table th,
    .report-table td {
      border: 1px solid #e2e8f0;
      padding: 10px 12px;
      font-size: 13px;
      line-height: 1.35;
      vertical-align: middle;
    }
    .report-table th {
      background-color: #f8fafc;
      color: #475569;
      font-weight: 800;
      text-align: center;
    }

    /* 전문 보고서 인쇄 스타일 (A4 가로 너비 100% 최적화) */
    @media print {
      @page { size: A4; margin: 10mm; }
      .no-print { display: none !important; }
      body { background-color: white !important; padding: 0 !important; color: black !important; }
      .print-container { padding: 0 !important; margin: 0 !important; width: 100% !important; max-width: none !important; }
      .report-page { border: none !important; box-shadow: none !important; padding: 0 !important; width: 100% !important; }
      
      .report-table { 
        width: 100% !important; 
        border-collapse: collapse !important; 
        table-layout: fixed !important; 
        border: 1px solid #000 !important; 
        margin-bottom: 1.5rem !important; 
      }
      .report-table th, .report-table td { 
        border: 0.5px solid #000 !important; 
        padding: 8px 10px !important; 
        font-size: 9pt !important; 
        line-height: 1.2 !important;
        word-break: break-all !important; 
      }
      .report-table th { background-color: #f1f5f9 !important; font-weight: bold !important; text-align: center !important; }
      .page-break { page-break-before: always; }
      
      .summary-box { 
        display: table !important; 
        width: 100% !important; 
        border: 1px solid #000 !important;
        border-collapse: collapse;
      }
      .summary-row { display: table-row !important; }
      .summary-cell { 
        display: table-cell !important; 
        border: 1px solid #000 !important; 
        padding: 12px !important; 
        width: 50% !important;
      }
      .text-blue-600 { color: #1e40af !important; }
      .text-emerald-700 { color: #065f46 !important; }
    }

    input[type="number"]::-webkit-inner-spin-button,
    input[type="number"]::-webkit-outer-spin-button {
      -webkit-appearance: none;
      margin: 0;
    }
  </style>
</head>
<body>
  <div id="root"></div>

  <script type="text/babel" data-type="module">
    import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
    import { getAuth, onAuthStateChanged, signInAnonymously, signInWithCustomToken } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
    import { getFirestore, doc, setDoc, getDoc, collection, onSnapshot } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

    const { useState, useMemo, useEffect, useRef } = React;

    // --- Firebase 및 설정 ---
    let db, auth, appId;
    try {
      const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : null;
      appId = typeof __app_id !== 'undefined' ? __app_id : 'jangheon-v55';
      if (firebaseConfig) {
        const app = initializeApp(firebaseConfig);
        auth = getAuth(app);
        db = getFirestore(app);
      }
    } catch(e) { console.warn("Firebase not available."); }

    const POOL_A_PROJECTS = ['총무 [26-관리-03]', '부서 공통 [26-관리-06]', '공통']; 
    const POOL_B_PROJECTS = ['관리', '생산']; 
    const TEAM_RATIOS = { '일반경비': { '철근팀': 0.45, '제작팀': 0.30, '공무팀': 0.25 } };
    const FACTORY_WORKER_FALLBACK = [
      { name: '강병흔', rate: 3065880, regularType: '정규직' }, { name: '곽병목', rate: 2313800, regularType: '정규직' },
      { name: '김경수', rate: 2674500, regularType: '계약직' }, { name: '김용정', rate: 2889740, regularType: '계약직' },
      { name: '김인식', rate: 3007160, regularType: '정규직' }, { name: '김종옥', rate: 2400520, regularType: '계약직' },
      { name: '민인기', rate: 2400520, regularType: '계약직' }, { name: '박혁수', rate: 2400520, regularType: '정규직' },
      { name: '석길원', rate: 2889750, regularType: '계약직' }, { name: '손순기', rate: 2674480, regularType: '계약직' },
      { name: '양시용', rate: 2868000, regularType: '계약직' }, { name: '원종명', rate: 2557100, regularType: '계약직' },
      { name: '윤승근', rate: 2615760, regularType: '계약직' }, { name: '이상진', rate: 2459220, regularType: '정규직' },
      { name: '이신영', rate: 2557060, regularType: '정규직' }, { name: '이은재', rate: 2615770, regularType: '계약직' },
      { name: '이호성', rate: 3281180, regularType: '정규직' }, { name: '임성학', rate: 2791910, regularType: '계약직' },
      { name: '장기홍', rate: 2557060, regularType: '정규직' }, { name: '장래철', rate: 2948460, regularType: '계약직' },
      { name: '장만순', rate: 2948460, regularType: '계약직' }, { name: '정승정', rate: 2948440, regularType: '정규직' },
      { name: '조성근', rate: 2889750, regularType: '계약직' }, { name: '조성태', rate: 2615770, regularType: '계약직' },
      { name: '최정희', rate: 2283100, regularType: '계약직' }, { name: '최천환', rate: 3007160, regularType: '계약직' },
      { name: '한덕현', rate: 3007170, regularType: '정규직' }
    ];

    const Icon = ({ name, size = 16, className = "" }) => {
      useEffect(() => { if (window.lucide) window.lucide.createIcons(); }, [name]);
      return <i data-lucide={name} className={className} style={{width: size, height: size}}></i>;
    };

    const App = () => {
      const [user, setUser] = useState(null);
      const [activeTab, setActiveTab] = useState('analysis'); 
      const [viewMode, setViewMode] = useState('project'); 
      const [isLoading, setIsLoading] = useState(true);
      const [showReport, setShowReport] = useState(false);
      const [selectedDetail, setSelectedDetail] = useState(null);
      const [detailTab, setDetailTab] = useState('account');

      const [expenses, setExpenses] = useState([]);
      const [laborRows, setLaborRows] = useState([]);
      const [wageSettings, setWageSettings] = useState({});
      const [factoryWorkers, setFactoryWorkers] = useState([]);
      const [workerTeamFilter, setWorkerTeamFilter] = useState('ALL');
      const [formVolumes, setFormVolumes] = useState({});
      const [mgmtPoolAAccounts, setMgmtPoolAAccounts] = useState(['(복리)식대비', '(복리)회식비', '(복리)간식비']);
      
      const [allocPoolA, setAllocPoolA] = useState(true);
      const [allocPoolB, setAllocPoolB] = useState(true);
      const [startDate, setStartDate] = useState('');
      const [endDate, setEndDate] = useState('');

      useEffect(() => {
        if (!auth) { setIsLoading(false); return; }
        const initAuth = async () => {
          if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
            await signInWithCustomToken(auth, __initial_auth_token);
          } else { await signInAnonymously(auth); }
        };
        initAuth();
        return onAuthStateChanged(auth, setUser);
      }, []);

      useEffect(() => {
        if (!user || !db) { setIsLoading(false); return; }
        const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'settings', 'masterData');
        return onSnapshot(docRef, (snap) => {
          if (snap.exists()) {
            const data = snap.data();
            setExpenses(data.expenses || []);
            setLaborRows(data.laborRows || []);
            setWageSettings(data.wageSettings || {});
            setFormVolumes(data.formVolumes || {});
            setMgmtPoolAAccounts(data.mgmtPoolAAccounts || ['(복리)식대비', '(복리)회식비', '(복리)간식비']);
            if (data.allocPoolA !== undefined) setAllocPoolA(data.allocPoolA);
            if (data.allocPoolB !== undefined) setAllocPoolB(data.allocPoolB);
          }
          setIsLoading(false);
        }, () => setIsLoading(false));
      }, [user]);

      const saveData = async (updates) => {
        if (!user || !db) return;
        const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'settings', 'masterData');
        await setDoc(docRef, { ...updates, updatedAt: new Date().toISOString() }, { merge: true });
      };

      const parseFactoryWorkerHtml = (htmlText) => {
        const doc = new DOMParser().parseFromString(String(htmlText || ''), 'text/html');
        const rows = [...doc.querySelectorAll('tr')];
        const teamMfg = new Set(['이호성', '이신영', '곽병목', '최정희', '장래철']);
        const teamAdmin = new Set(['양시용', '원종명', '김용정', '조성태', '강병흔', '장기홍', '정승정']);
        const resolveTeam = (name = '', regularType = '') => {
          const n = String(name || '').trim();
          const r = String(regularType || '').trim();
          if (r.includes('일용')) return '일용직';
          if (teamMfg.has(n)) return '제작팀';
          if (teamAdmin.has(n)) return '공무팀';
          return '철근팀';
        };
        const toNum = (v) => {
          const n = parseFloat(String(v || '').replace(/[^0-9.-]/g, ''));
          return Number.isFinite(n) ? n : 0;
        };

        const workers = [];
        rows.forEach(tr => {
          const nameCell = tr.querySelector('td#Worker_name');
          const payCell = tr.querySelector('td#BasicPay');
          const bonusCell = tr.querySelector('td#Bonus');
          const regularCell = tr.querySelector('td#regularname');
          if (!nameCell || !payCell) return;
          const name = String(nameCell.textContent || '').trim();
          if (!name) return;
          const basicPay = toNum(payCell.textContent);
          const bonusPay = toNum(bonusCell ? bonusCell.textContent : 0);
          const regularType = String(regularCell ? regularCell.textContent : '').trim();
          const team = resolveTeam(name, regularType);
          workers.push({ name, team, rate: basicPay + bonusPay });
        });

        const uniq = {};
        workers.forEach(w => { if (!uniq[w.name]) uniq[w.name] = w; });
        return Object.values(uniq);
      };

      const applyFactoryDefaults = (workers) => {
        if (!workers || workers.length === 0) return;
        setWageSettings(prev => {
          const next = { ...prev };
          workers.forEach(w => {
            const prevType = (next[w.name] && next[w.name].type) ? next[w.name].type : 'monthly';
            next[w.name] = { rate: w.rate || 0, type: prevType };
          });
          saveData({ wageSettings: next });
          return next;
        });
      };

      const loadFactoryDefaultsFromText = (text) => {
        const parsed = parseFactoryWorkerHtml(text);
        setFactoryWorkers(parsed);
        applyFactoryDefaults(parsed);
      };

      useEffect(() => {
        // 프로젝트 루트의 FactoryWorker.xls(HTML 형식)에서 기본 인원/단가를 로드
        fetch('./FactoryWorker.xls')
          .then(r => r.text())
          .then(loadFactoryDefaultsFromText)
          .catch(() => {
            const teamMfg = new Set(['이호성', '이신영', '곽병목', '최정희', '장래철']);
            const teamAdmin = new Set(['양시용', '원종명', '김용정', '조성태', '강병흔', '장기홍', '정승정']);
            const fallbackWorkers = FACTORY_WORKER_FALLBACK.map(w => ({
              name: w.name,
              rate: w.rate,
              team: String(w.regularType || '').includes('일용')
                ? '일용직'
                : teamMfg.has(w.name) ? '제작팀'
                : teamAdmin.has(w.name) ? '공무팀'
                : '철근팀'
            }));
            setFactoryWorkers(fallbackWorkers);
            applyFactoryDefaults(fallbackWorkers);
          });
      }, []);

      const utils = {
        formatWon: (v) => `₩${Math.round(v || 0).toLocaleString()}`,
        formatHr: (v) => `${Number(v || 0).toLocaleString(undefined, { minimumFractionDigits: 1, maximumFractionDigits: 1 })}`,
        parseNum: (v) => { const n = parseFloat(String(v).replace(/,/g, '')); return isNaN(n) ? 0 : n; },
        parseDate: (val) => {
          if (!val) return '';
          if (val instanceof Date) return val.toISOString().split('T')[0];
          if (typeof val === 'number') {
            const d = new Date(Math.round((val - 25569) * 86400 * 1000));
            return d.toISOString().split('T')[0];
          }
          return String(val).trim().substring(0, 10);
        }
      };

      const onUpload = (e, type) => {
        const file = e.target.files[0];
        if (!file) return;
        if (type === 'hr') {
          const reader = new FileReader();
          reader.onload = (evt) => {
            loadFactoryDefaultsFromText(evt.target.result);
          };
          reader.readAsText(file);
          return;
        }
        const reader = new FileReader();
        reader.onload = (evt) => {
          const wb = XLSX.read(evt.target.result, { type: 'binary', cellDates: true });
          const data = XLSX.utils.sheet_to_json(wb.Sheets[wb.SheetNames[0]]);
          if (type === 'expense') {
            const mapped = data.flatMap((r, i) => {
              const account = String(r['계정'] || r['소계정명'] || '미분류').trim();
              const team = String(r['팀'] || r['팀명'] || '기타').trim();
              const projectName = String(r['교량명'] || r['사업명'] || r['사업코드'] || '미지정').trim();
              const amount = utils.parseNum(r['공급가액'] || r['공급가'] || r['합계']);
              const date = utils.parseDate(r['거래일'] || r['날짜']);
              const desc = r['적요'] || '';
              const form = String(r['형식'] || '미분류').trim();
              return { id: `e-${Date.now()}-${i}`, date, account, team, projectName, amount, description: desc, form };
            });
            setExpenses(mapped); saveData({ expenses: mapped });
          } else {
            const lData = data.flatMap((r, i) => {
              const date = utils.parseDate(r['근무일'] || r['날짜']);
              const worker = r['근무자명'] || r['성명'] || '';
              const team = String(r['근무팀'] || r['소속팀'] || '기타').trim();
              const projectName = String(r['교량명'] || r['사업명'] || '미지정').trim();
              const hours = utils.parseNum(r['근무시간'] || r['시간']);
              const form = String(r['형식'] || '미분류').trim();
              return { id: `l-${Date.now()}-${i}`, date, worker, team, projectName, hours, form };
            });
            setLaborRows(lData);
            const newWages = { ...wageSettings };
            [...new Set(lData.map(l => l.worker))].filter(Boolean).forEach(n => { if (!newWages[n]) newWages[n] = { rate: 0, type: 'monthly' }; });
            setWageSettings(newWages); saveData({ laborRows: lData, wageSettings: newWages });
          }
        };
        reader.readAsBinaryString(file);
      };

      const calculateSettlement = (mode) => {
        const isB = (v) => POOL_B_PROJECTS.includes(v);
        const isA = (v) => POOL_A_PROJECTS.includes(v);
        const fEx = expenses.filter(i => (!startDate || i.date >= startDate) && (!endDate || i.date <= endDate));
        const fLb = laborRows.filter(i => (!startDate || i.date >= startDate) && (!endDate || i.date <= endDate));
        const workerTotalHours = {};
        fLb.forEach(r => { workerTotalHours[r.worker] = (workerTotalHours[r.worker] || 0) + r.hours; });
        const laborWithCost = fLb.map(r => {
          const cfg = wageSettings[r.worker] || { rate: 0, type: 'monthly' };
          let cost = cfg.type === 'hourly' ? r.hours * cfg.rate : (workerTotalHours[r.worker] > 0 ? (r.hours / workerTotalHours[r.worker]) * cfg.rate : 0);
          return { ...r, cost };
        });

        let poolAVal = 0; let poolBVal = 0; let revenueHrs = 0;
        const projectMap = {};

        fEx.forEach(e => {
          if (isB(e.projectName)) poolBVal += e.amount;
          else if (isA(e.projectName)) poolAVal += e.amount;
          else if (e.team === '관리팀' && !mgmtPoolAAccounts.some(acc => e.account.includes(acc))) poolBVal += e.amount;
          else if (e.team === '관리팀') poolAVal += e.amount;
          else {
            if (!projectMap[e.projectName]) projectMap[e.projectName] = { name: e.projectName, direct: 0, hours: 0, byAccount: {}, byTeam: {}, byForm: {} };
            projectMap[e.projectName].direct += e.amount;
            projectMap[e.projectName].byAccount[e.account] = (projectMap[e.projectName].byAccount[e.account] || 0) + e.amount;
            projectMap[e.projectName].byTeam[e.team] = (projectMap[e.projectName].byTeam[e.team] || 0) + e.amount;
            const f = e.form || '미분류';
            projectMap[e.projectName].byForm[f] = (projectMap[e.projectName].byForm[f] || 0) + e.amount;
          }
        });

        laborWithCost.forEach(l => {
          if (isB(l.projectName)) poolBVal += l.cost;
          else if (isA(l.projectName)) poolAVal += l.cost;
          else {
            if (!projectMap[l.projectName]) projectMap[l.projectName] = { name: l.projectName, direct: 0, hours: 0, byAccount: {}, byTeam: {}, byForm: {} };
            projectMap[l.projectName].direct += l.cost;
            projectMap[l.projectName].hours += l.hours;
            projectMap[l.projectName].byAccount['인건비(직접)'] = (projectMap[l.projectName].byAccount['인건비(직접)'] || 0) + l.cost;
            projectMap[l.projectName].byTeam[l.team] = (projectMap[l.projectName].byTeam[l.team] || 0) + l.cost;
            const f = l.form || '미분류';
            projectMap[l.projectName].byForm[f] = (projectMap[l.projectName].byForm[f] || 0) + l.cost;
            revenueHrs += l.hours;
          }
        });

        const settledProjects = Object.values(projectMap).map(p => {
          const allocA = (allocPoolA && revenueHrs > 0) ? (p.hours / revenueHrs) * poolAVal : 0;
          const allocB = (allocPoolB && revenueHrs > 0) ? (p.hours / revenueHrs) * poolBVal : 0;
          return { ...p, allocA, allocB, final: p.direct + allocA + allocB };
        });

        const customSort = (a, b) => {
          const invalidNames = ['미분류', '미지정'];
          if (invalidNames.includes(a.name) && !invalidNames.includes(b.name)) return 1;
          if (!invalidNames.includes(a.name) && invalidNames.includes(b.name)) return -1;
          return b.final - a.final;
        };

        if (mode === 'project') return settledProjects.sort(customSort);
        if (mode === 'type') {
          const formMap = {};
          settledProjects.forEach(p => {
            Object.entries(p.byForm).forEach(([fName, val]) => {
              if (!formMap[fName]) formMap[fName] = { name: fName, direct: 0, allocA: 0, allocB: 0, final: 0, hours: 0, breakdown: {} };
              formMap[fName].direct += val;
              const ratio = p.direct > 0 ? val / p.direct : 0;
              formMap[fName].allocA += p.allocA * ratio;
              formMap[fName].allocB += p.allocB * ratio;
              formMap[fName].hours += p.hours * ratio; 
              formMap[fName].breakdown[p.name] = (formMap[fName].breakdown[p.name] || 0) + val;
            });
          });
          return Object.values(formMap).map(t => {
            const finalVal = t.direct + t.allocA + t.allocB;
            const volInfo = formVolumes[t.name] || { value: 0, unit: 'ton' };
            return { ...t, final: finalVal, volInfo, unitCost: volInfo.value > 0 ? finalVal / volInfo.value : 0 };
          }).sort(customSort);
        }
        if (mode === 'team') {
          const tmMap = {};
          settledProjects.forEach(p => {
            Object.entries(p.byTeam).forEach(([tm, val]) => {
              if (!tmMap[tm]) tmMap[tm] = { name: tm, direct: 0, allocA: 0, allocB: 0, final: 0, hours: 0, breakdown: {} };
              tmMap[tm].direct += val;
              const ratio = p.direct > 0 ? val / p.direct : 0;
              tmMap[tm].allocA += p.allocA * ratio;
              tmMap[tm].allocB += p.allocB * ratio;
              tmMap[tm].hours += p.hours * ratio;
              tmMap[tm].breakdown[p.name] = (tmMap[tm].breakdown[p.name] || 0) + val;
            });
          });
          return Object.values(tmMap).map(t => ({ ...t, final: t.direct + t.allocA + t.allocB })).sort(customSort);
        }
        return { poolAVal, poolBVal, grandTotalHrs: fLb.reduce((s,x)=>s+x.hours, 0), revenueHrs };
      };

      const results = useMemo(() => {
        const meta = calculateSettlement('meta');
        const project = calculateSettlement('project');
        const type = calculateSettlement('type');
        const team = calculateSettlement('team');
        const displayData = { project, type, team }[viewMode] || project;
        const totalDirect = project.reduce((s, x) => s + x.direct, 0);
        const total = totalDirect + (allocPoolA ? meta.poolAVal : 0) + (allocPoolB ? meta.poolBVal : 0);
        return { displayData, poolA: meta.poolAVal, poolB: meta.poolBVal, total, grandTotalHrs: meta.grandTotalHrs, revenueHrs: meta.revenueHrs, all: { project, type, team } };
      }, [expenses, laborRows, wageSettings, startDate, endDate, viewMode, allocPoolA, allocPoolB, formVolumes]);

      const workerTeamMap = useMemo(() => {
        const teamMfg = new Set(['이호성', '이신영', '곽병목', '최정희', '장래철']);
        const teamAdmin = new Set(['양시용', '원종명', '김용정', '조성태', '강병흔', '장기홍', '정승정']);
        const dailyWorkers = new Set(
          (laborRows || [])
            .filter(r => String(r.team || '').includes('일용'))
            .map(r => String(r.worker || '').trim())
            .filter(Boolean)
        );
        const map = {};
        factoryWorkers.forEach(w => { map[w.name] = w.team; });
        Object.keys(wageSettings || {}).forEach(name => {
          if (map[name]) return;
          if (dailyWorkers.has(name) || String(name).includes('일용')) {
            map[name] = '일용직';
          } else {
            map[name] = teamMfg.has(name) ? '제작팀' : teamAdmin.has(name) ? '공무팀' : '철근팀';
          }
        });
        return map;
      }, [factoryWorkers, wageSettings, laborRows]);

      const factoryTeamCounts = useMemo(() => {
        const base = { '철근팀': 0, '제작팀': 0, '공무팀': 0, '일용직': 0 };
        Object.values(workerTeamMap || {}).forEach(team => {
          if (base[team] !== undefined) base[team] += 1;
        });
        return base;
      }, [workerTeamMap]);

      const visibleWorkerNames = useMemo(() => {
        const names = Object.keys(wageSettings).sort();
        if (workerTeamFilter === 'ALL') return names;
        return names.filter(n => workerTeamMap[n] === workerTeamFilter);
      }, [wageSettings, workerTeamFilter, workerTeamMap]);

      useEffect(() => {
        if (!selectedDetail) return;
        if (selectedDetail.byAccount) setDetailTab('account');
        else setDetailTab('breakdown');
      }, [selectedDetail]);

      if (isLoading) return <div className="h-screen flex items-center justify-center bg-slate-50 font-bold text-slate-400 uppercase tracking-widest animate-pulse">Syncing v5.5 Engine...</div>;

      if (showReport) {
        return (
          <div className="min-h-screen bg-slate-200 p-10 print-container animate-fade-in text-slate-900">
            <div className="max-w-5xl mx-auto space-y-6 no-print mb-8">
              <div className="flex justify-between items-center bg-white p-4 rounded-2xl shadow-sm border border-slate-300">
                <button onClick={() => setShowReport(false)} className="px-5 py-2 rounded-lg font-bold bg-slate-100 hover:bg-slate-200 flex items-center gap-2 transition-all">
                  <Icon name="arrow-left" size={16}/> 대시보드로 돌아가기
                </button>
                <button onClick={() => window.print()} className="px-8 py-3 rounded-lg font-black bg-blue-600 text-white shadow-lg hover:bg-blue-700 active:scale-95 transition-all flex items-center gap-2">
                  <Icon name="printer" size={20}/> 보고서 인쇄 (PDF 저장 가능)
                </button>
              </div>
            </div>

            <div className="bg-white p-14 shadow-2xl report-page mx-auto w-full max-w-5xl border border-slate-300">
              <div className="border-b-4 border-slate-900 pb-8 mb-12 flex justify-between items-end">
                <div>
                  <h2 className="text-3xl font-black italic tracking-tighter text-slate-900 uppercase underline decoration-blue-500 decoration-4 underline-offset-[12px] mb-4">통합 원가 정산 결과 보고서</h2>
                  <p className="text-slate-500 font-bold uppercase text-[10px] tracking-[0.4em]">()장헌 프로젝트 관리 시스템</p>
                </div>
                <div className="text-right">
                  <p className="text-xs font-bold text-slate-400 mb-1 italic">발행일: {new Date().toLocaleDateString()}</p>
                  <p className="text-[10px] font-black text-slate-900 uppercase tracking-widest bg-slate-100 px-3 py-1 rounded">Official Report</p>
                </div>
              </div>

              <div className="space-y-16">
                <section>
                  <div className="flex items-center gap-4 mb-6">
                    <div className="w-1.5 h-6 bg-slate-900 rounded-full" />
                    <h3 className="text-xl font-black uppercase italic tracking-tight">01. 정산 총괄 요약</h3>
                  </div>
                  <div className="summary-box">
                    <div className="summary-row">
                      <div className="summary-cell">
                        <span className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-1 block">조회 기간  발생 원가</span>
                        <span className="text-xl font-black text-slate-900">{utils.formatWon(results.total)}</span>
                      </div>
                      <div className="summary-cell">
                        <span className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-1 block"> 투입 누적  공수</span>
                        <span className="text-xl font-black text-slate-900">{utils.formatHr(results.grandTotalHrs)} HR</span>
                      </div>
                    </div>
                    {(allocPoolA || allocPoolB) && (
                      <div className="summary-row">
                        {allocPoolA && (
                          <div className="summary-cell">
                            <span className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-1 block">운영비 배분 총액 (POOL A)</span>
                            <span className="text-xl font-black text-blue-700">{utils.formatWon(results.poolA)}</span>
                          </div>
                        )}
                        {allocPoolB && (
                          <div className="summary-cell">
                            <span className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-1 block">일반 관리비 배분액 (POOL B)</span>
                            <span className="text-xl font-black text-orange-700">{utils.formatWon(results.poolB)}</span>
                          </div>
                        )}
                      </div>
                    )}
                  </div>
                </section>

                <section>
                  <div className="flex items-center gap-4 mb-6">
                    <div className="w-1.5 h-6 bg-slate-900 rounded-full" />
                    <h3 className="text-xl font-black uppercase italic tracking-tight">02. 교량별 정산 상세 내역</h3>
                  </div>
                  <table className="report-table">
                    <thead>
                      <tr>
                        <th style={{ width: '28%' }}>교량(프로젝트) 명칭</th>
                        <th style={{ width: '15%' }} className="text-right">직접비</th>
                        {allocPoolA && <th style={{ width: '15%' }} className="text-right text-blue-700">운영비 추가(A)</th>}
                        {allocPoolB && <th style={{ width: '15%' }} className="text-right text-orange-700">관리비 추가(B)</th>}
                        <th style={{ width: '17%' }} className="text-right font-black">총액</th>
                        <th style={{ width: '10%' }} className="text-center">공수(HR)</th>
                      </tr>
                    </thead>
                    <tbody>
                      {results.all.project.map(p => (
                        <tr key={p.name} className={['미분류', '미지정'].includes(p.name) ? 'bg-red-50 text-red-700' : ''}>
                          <td className="font-bold">{p.name}</td>
                          <td className="text-right">{Math.round(p.direct).toLocaleString()}</td>
                          {allocPoolA && <td className="text-right text-blue-700">{Math.round(p.allocA).toLocaleString()}</td>}
                          {allocPoolB && <td className="text-right text-orange-700">{Math.round(p.allocB).toLocaleString()}</td>}
                          <td className="text-right font-black">{Math.round(p.final).toLocaleString()}</td>
                          <td className="text-center font-bold text-slate-500">{utils.formatHr(p.hours)}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </section>

                <section className="page-break">
                  <div className="flex items-center gap-4 mb-6">
                    <div className="w-1.5 h-6 bg-slate-900 rounded-full" />
                    <h3 className="text-xl font-black uppercase italic tracking-tight">03. 제품 형식별 제조 원가 분석</h3>
                  </div>
                  <table className="report-table">
                    <thead>
                      <tr>
                        <th style={{ width: '24%' }}>제품 형식</th>
                        <th style={{ width: '14%' }} className="text-right">직접비</th>
                        {allocPoolA && <th style={{ width: '14%' }} className="text-right text-blue-700">운영비 추가(A)</th>}
                        {allocPoolB && <th style={{ width: '14%' }} className="text-right text-orange-700">관리비 추가(B)</th>}
                        <th style={{ width: '14%' }} className="text-right font-black">총액</th>
                        <th style={{ width: '10%' }} className="text-right">생산량/단위</th>
                        <th style={{ width: '10%' }} className="text-right text-emerald-700 font-black">단위당 원가()</th>
                      </tr>
                    </thead>
                    <tbody>
                      {results.all.type.map(t => (
                        <tr key={t.name} className={['미분류', '미지정'].includes(t.name) ? 'bg-red-50' : ''}>
                          <td className="font-bold">{t.name}</td>
                          <td className="text-right">{Math.round(t.direct).toLocaleString()}</td>
                          {allocPoolA && <td className="text-right text-blue-700">{Math.round(t.allocA).toLocaleString()}</td>}
                          {allocPoolB && <td className="text-right text-orange-700">{Math.round(t.allocB).toLocaleString()}</td>}
                          <td className="text-right font-black">{Math.round(t.final).toLocaleString()}</td>
                          <td className="text-right italic text-slate-500">{t.volInfo.value.toLocaleString()} {t.volInfo.unit}</td>
                          <td className="text-right font-black text-emerald-700">{Math.round(t.unitCost).toLocaleString()}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </section>

                <section>
                  <div className="flex items-center gap-4 mb-6">
                    <div className="w-1.5 h-6 bg-slate-900 rounded-full" />
                    <h3 className="text-xl font-black uppercase italic tracking-tight">04. 팀별 원가 귀속 현황</h3>
                  </div>
                  <table className="report-table">
                    <thead>
                      <tr>
                        <th style={{ width: '30%' }}>담당 부서/</th>
                        <th style={{ width: '17%' }} className="text-right">직접 투입비</th>
                        {allocPoolA && <th style={{ width: '17%' }} className="text-right text-blue-700">운영비 추가(A)</th>}
                        {allocPoolB && <th style={{ width: '17%' }} className="text-right text-orange-700">관리비 추가(B)</th>}
                        <th style={{ width: '19%' }} className="text-right font-black">총액</th>
                      </tr>
                    </thead>
                    <tbody>
                      {results.all.team.map(tm => (
                        <tr key={tm.name}>
                          <td className="font-bold">{tm.name} ({utils.formatHr(tm.hours)} HR)</td>
                          <td className="text-right">{Math.round(tm.direct).toLocaleString()}</td>
                          {allocPoolA && <td className="text-right text-blue-700">{Math.round(tm.allocA).toLocaleString()}</td>}
                          {allocPoolB && <td className="text-right text-orange-700">{Math.round(tm.allocB).toLocaleString()}</td>}
                          <td className="text-right font-black">{Math.round(tm.final).toLocaleString()}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </section>
              </div>

              <div className="mt-20 pt-10 border-t border-slate-100 text-center space-y-4">
                <p className="text-[9px] font-bold text-slate-300 uppercase tracking-[0.6em] italic">JANGHEON COST ANALYSIS ENGINE v5.5</p>
                <div className="flex justify-center gap-10 py-6 opacity-20 grayscale">
                  <div className="border-2 border-slate-900 p-2 px-8 rounded-full font-black text-slate-900 text-lg uppercase italic">Approved</div>
                  <div className="border-2 border-slate-900 p-2 px-8 rounded-full font-black text-slate-900 text-lg uppercase italic">JANGHEON</div>
                </div>
              </div>
            </div>
          </div>
        );
      }

      return (
        <div className="min-h-screen flex flex-col text-sm">
          <header className="glass-nav h-14 px-6 flex items-center justify-between sticky top-0 z-50">
            <div className="flex items-center gap-3">
              <div className="bg-slate-900 p-1.5 rounded-lg text-white shadow-sm font-black italic"><Icon name="cpu" size={16} /></div>
              <h1 className="font-bold text-lg tracking-tight text-slate-800 italic uppercase">장헌 Integrated COST v5.5</h1>
            </div>
            <div className="flex gap-1 bg-slate-100 p-0.5 rounded-xl shrink-0 border border-slate-200">
              {[{id:'analysis', label:'정산 분석', icon:'layout'}, {id:'settings', label:'인사 관리', icon:'settings'}, {id:'data', label:'데이터&보고서', icon:'database'}].map(t => (
                <button key={t.id} onClick={() => setActiveTab(t.id)} className={`flex items-center gap-2 px-4 py-1.5 rounded-lg font-bold transition-all ${activeTab === t.id ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500 hover:text-slate-800'}`}>
                  <Icon name={t.icon} size={13} /> {t.label}
                </button>
              ))}
            </div>
          </header>

          <main className="flex-1 p-6 max-w-[1400px] mx-auto w-full animate-fade-in space-y-6 text-slate-700">
            <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
              {[
                { label: '조회 총 발생 원가', val: results.total, color: 'text-slate-900' },
                { label: '운영비 (POOL A)', val: results.poolA, color: 'text-blue-600' },
                { label: '관리비 (POOL B)', val: results.poolB, color: 'text-orange-600' },
                { label: '실 투입 총 공수', val: results.grandTotalHrs, unit: 'HR', color: 'text-emerald-600' }
              ].map((c, i) => (
                <div key={i} className="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm transition-all hover:bg-slate-50/50">
                  <p className="text-xs font-bold text-slate-400 uppercase tracking-widest mb-1">{c.label}</p>
                  <p className={`text-xl font-extrabold ${c.color} tracking-tighter leading-none`}>
                    {c.unit ? (c.unit === 'HR' ? utils.formatHr(c.val) : c.val.toLocaleString()) : utils.formatWon(c.val)}
                    {c.unit && <span className="text-sm font-bold ml-1 opacity-50">{c.unit}</span>}
                  </p>
                </div>
              ))}
            </div>

            {activeTab === 'analysis' && (
              <div className="space-y-4">
                <div className="bg-white px-6 py-2.5 rounded-2xl border border-slate-200 flex items-center justify-between gap-4 shadow-sm">
                  <div className="flex items-center gap-10 shrink-0">
                    <div className="flex items-center gap-8 font-bold text-xs text-slate-500 uppercase italic">
                       <label className="flex items-center gap-2 cursor-pointer group">
                          <input type="checkbox" className="w-4 h-4 rounded border-slate-300" checked={allocPoolA} onChange={e => setAllocPoolA(e.target.checked)} />
                          <span className={`${allocPoolA ? 'text-blue-600' : ''}`}>배분 (A)</span>
                       </label>
                       <label className="flex items-center gap-2 cursor-pointer group">
                          <input type="checkbox" className="w-4 h-4 rounded border-slate-300" checked={allocPoolB} onChange={e => setAllocPoolB(e.target.checked)} />
                          <span className={`${allocPoolB ? 'text-orange-600' : ''}`}>배분 (B)</span>
                       </label>
                    </div>
                    <div className="h-4 w-px bg-slate-200"></div>
                    <div className="flex items-center gap-2 shrink-0">
                       <input type="date" className="bg-slate-50 border rounded-lg text-xs font-bold px-3 py-1.5 focus:ring-1 focus:ring-blue-300 outline-none" value={startDate} onChange={e => setStartDate(e.target.value)} />
                       <span className="text-slate-300 text-xs font-bold">~</span>
                       <input type="date" className="bg-slate-50 border rounded-lg text-xs font-bold px-3 py-1.5 focus:ring-1 focus:ring-blue-300 outline-none" value={endDate} onChange={e => setEndDate(e.target.value)} />
                    </div>
                  </div>
                  <div className="flex bg-slate-100 p-0.5 rounded-lg gap-0.5 shrink-0 border">
                    {[{id:'project', label:'교량별'}, {id:'type', label:'형식별'}, {id:'team', label:'팀별'}].map(v => (
                      <button key={v.id} onClick={() => setViewMode(v.id)} className={`px-4 py-1.5 rounded-md text-xs font-bold transition-all ${viewMode === v.id ? 'bg-slate-800 text-white shadow-sm' : 'text-slate-500 hover:text-slate-800'}`}>
                        {v.label}
                      </button>
                    ))}
                  </div>
                </div>

                <div className="bg-white rounded-2xl border border-slate-200 shadow-sm overflow-hidden overflow-x-auto custom-scrollbar">
                  <table className="w-full text-left min-w-[1000px] dashboard-table">
                    <thead>
                      <tr>
                        <th>정산 대상 명칭</th>
                        <th className="text-right">직접비</th>
                        <th className="text-right">배분(A)</th>
                        <th className="text-right">배분(B)</th>
                        <th className="text-right">최종 원가</th>
                        {viewMode === 'type' && <th className="text-center">생산량 / 단위원가</th>}
                      </tr>
                    </thead>
                    <tbody className="divide-y divide-slate-100 font-semibold text-sm">
                      {results.displayData.map(item => {
                        const isInvalid = item.name === '미분류' || item.name === '미지정';
                        return (
                          <tr key={item.name} className={`transition-all group ${isInvalid ? 'bg-red-50 hover:bg-red-100/30' : 'hover:bg-blue-50/20'}`}>
                            <td className="px-8 py-4 cursor-pointer" onClick={() => setSelectedDetail(item)}>
                              <div className="flex flex-col">
                                 <span className={`font-bold block transition-colors text-sm leading-tight ${isInvalid ? 'text-red-700' : 'text-slate-800 group-hover:text-blue-600'}`}>{item.name}</span>
                                 <span className="text-xs text-slate-400 font-bold mt-1 inline-flex items-center gap-1.5 bg-slate-100 px-1.5 py-0.5 rounded-md w-max">
                                    <Icon name="clock" size={10} /> {utils.formatHr(item.hours || 0)} HR
                                 </span>
                              </div>
                            </td>
                            <td className="px-5 py-4 text-right text-slate-500 italic text-sm">{Math.round(item.direct).toLocaleString()}</td>
                            <td className="px-5 py-4 text-right text-blue-500 text-sm italic">{Math.round(item.allocA).toLocaleString()}</td>
                            <td className="px-5 py-4 text-right text-orange-500 text-sm italic">{Math.round(item.allocB).toLocaleString()}</td>
                            <td className="px-8 py-4 text-right text-base font-black text-slate-900 tracking-tighter cursor-pointer" onClick={() => setSelectedDetail(item)}>{Math.round(item.final).toLocaleString()}</td>
                            {viewMode === 'type' && (
                              <td className="px-8 py-4 bg-slate-50/50">
                                <div className="flex flex-col items-center gap-2">
                                  <div className="flex items-center gap-1.5">
                                    <input 
                                      type="number" 
                                      className="w-20 bg-white border border-slate-300 rounded-lg px-2 py-1 text-right font-bold text-blue-700 text-xs outline-none focus:ring-2 focus:ring-blue-100"
                                      placeholder="물량"
                                      value={item.volInfo.value || ''}
                                      onChange={(e) => {
                                        const next = { ...formVolumes, [item.name]: { ...item.volInfo, value: utils.parseNum(e.target.value) } };
                                        setFormVolumes(next); saveData({ formVolumes: next });
                                      }}
                                    />
                                    <select className="bg-white border border-slate-300 rounded-lg px-1 py-1 text-xs font-bold outline-none cursor-pointer" value={item.volInfo.unit} onChange={(e) => {
                                        const next = { ...formVolumes, [item.name]: { ...item.volInfo, unit: e.target.value } };
                                        setFormVolumes(next); saveData({ formVolumes: next });
                                      }}>
                                      <option value="ton">ton</option><option value="m">m</option><option value="EA">EA</option>
                                    </select>
                                  </div>
                                  <span className="text-xs font-black text-blue-800 tracking-tight">{Math.round(item.unitCost).toLocaleString()} <span className="text-[9px] opacity-40">/ {item.volInfo.unit}</span></span>
                                </div>
                              </td>
                            )}
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
                </div>
              </div>
            )}

            {activeTab === 'data' && (
              <div className="space-y-6 animate-fade-in text-center">
                <div className="grid grid-cols-1 md:grid-cols-2 gap-4 py-4">
                  {[{id:'expense', title:'지출 원장 업로드', icon:'file-text', count: expenses.length, color:'text-blue-600'}, {id:'labor', title:'근무 기록 업로드', icon:'users', count: laborRows.length, color:'text-emerald-600'}, {id:'hr', title:'인사 관리 업로드', icon:'user-round', count: factoryWorkers.length, color:'text-violet-600'}].map(u => (
                    <div key={u.id} className="bg-white p-8 rounded-2xl border border-slate-200 shadow-sm group hover:border-slate-800 transition-all">
                      <div className={`mx-auto p-5 rounded-2xl bg-slate-50 ${u.color} mb-4 shadow-sm w-max`}><Icon name={u.icon} size={36} /></div>
                      <h3 className="text-lg font-bold text-slate-800 mb-4 uppercase">{u.title}</h3>
                      <label className="bg-slate-800 text-white px-8 py-2 rounded-xl text-sm font-bold cursor-pointer hover:bg-slate-700 transition-all inline-block shadow-md">
                        <Icon name="upload" className="inline-block mr-2" size={14} /> 엑셀 파일 로드 <input type="file" className="hidden" accept={u.id === 'hr' ? '.xls,.xlsx,.html' : '.xls,.xlsx'} onChange={e => onUpload(e, u.id)} />
                      </label>
                      <p className="mt-4 text-xs font-bold text-slate-300 uppercase tracking-widest">{u.count} RECORDS SYNCED</p>
                    </div>
                  ))}
                </div>
                <p className="text-sm font-bold text-slate-500">기본적인 인사 정보는 시스템에 저장되어 있으며, 인사 업로드  최신 파일 기준으로 /단가 정보가 갱신됩니다.</p>

                <div className="bg-slate-900 p-12 rounded-[40px] text-white shadow-2xl relative overflow-hidden text-center space-y-6 group">
                  <div className="absolute top-0 right-0 w-80 h-80 bg-blue-500/10 blur-[100px] rounded-full" />
                  <Icon name="file-text" size={60} className="mx-auto text-blue-400 mb-2 transition-all duration-500 group-hover:scale-110" />
                  <div>
                    <h3 className="text-3xl font-black tracking-tighter uppercase italic">Professional Settlement Report</h3>
                    <p className="text-slate-400 font-semibold max-w-xl mx-auto text-sm mt-2 opacity-80 leading-relaxed italic">
                      모든 정산 결과(교량, 형식, ) 페이지 너비에 최적화된 통합 보고서 양식으로 생성합니다. <br/>
                      생성된 결과는 정식 문서 규격으로 인쇄하거나 PDF로 저장할  있습니다.
                    </p>
                  </div>
                  <button onClick={() => setShowReport(true)} className="relative z-10 bg-blue-600 hover:bg-blue-500 text-white px-12 py-4 rounded-2xl text-lg font-black transition-all hover:scale-105 active:scale-95 shadow-[0_15px_40px_rgba(37,99,235,0.3)] flex items-center gap-3 mx-auto uppercase italic">
                    <Icon name="printer" size={24} /> 전문 통합 보고서 생성
                  </button>
                </div>
              </div>
            )}

            {activeTab === 'settings' && (
              <div className="max-w-3xl mx-auto space-y-8 py-6 animate-fade-in text-slate-900">
                <div className="bg-white p-10 rounded-3xl border border-slate-200 shadow-sm text-slate-900">
                  <h3 className="text-xl font-black uppercase tracking-tight mb-8 border-b pb-6 italic">인사 관리</h3>
                  <div className="grid grid-cols-1 md:grid-cols-3 gap-3 mb-6">
                    {['철근팀', '제작팀', '공무팀', '일용직'].map(team => (
                      <div key={team} className="bg-slate-50 rounded-2xl border border-slate-100 p-4">
                        <div className="text-xs font-black text-slate-500">{team}</div>
                        <div className="text-2xl font-black text-slate-800 mt-1">{factoryTeamCounts[team] || 0}<span className="text-sm ml-1 text-slate-400"></span></div>
                      </div>
                    ))}
                  </div>
                  <div className="mb-6 flex items-center gap-2">
                    {[
                      { id: 'ALL', label: '전체' },
                      { id: '철근팀', label: '철근팀' },
                      { id: '제작팀', label: '제작팀' },
                      { id: '공무팀', label: '공무팀' },
                      { id: '일용직', label: '일용직' }
                    ].map(opt => (
                      <button
                        key={opt.id}
                        type="button"
                        onClick={() => setWorkerTeamFilter(opt.id)}
                        className={`px-3 py-1.5 rounded-lg text-xs font-bold border transition-all ${workerTeamFilter === opt.id ? 'bg-slate-800 text-white border-slate-800' : 'bg-white text-slate-500 border-slate-200 hover:text-slate-700'}`}
                      >
                        {opt.label}
                      </button>
                    ))}
                  </div>
                  <div className="grid grid-cols-1 gap-4 text-slate-900">
                    {visibleWorkerNames.map(n => (
                      <div key={n} className="bg-slate-50 p-5 rounded-2xl border border-slate-100 flex justify-between items-center group hover:bg-white transition-all shadow-sm">
                        <div className="flex items-center gap-2">
                          <span className="font-bold text-base underline underline-offset-8 decoration-slate-200 tracking-tight italic">{n}</span>
                          <span className="px-2 py-0.5 rounded-full text-[10px] font-black bg-slate-200 text-slate-700">{workerTeamMap[n] || '철근팀'}</span>
                        </div>
                        <div className="flex items-center gap-4">
                          <select className="bg-white border text-sm font-bold rounded-xl px-4 py-2.5 outline-none cursor-pointer focus:ring-4 focus:ring-blue-100 transition-all border-slate-200" value={wageSettings[n].type} onChange={e => {
                              const next = {...wageSettings, [n]: {...wageSettings[n], type: e.target.value}};
                              setWageSettings(next); saveData({ wageSettings: next });
                            }}>
                            <option value="monthly">월급제</option><option value="hourly">시급제</option>
                          </select>
                          <div className="relative">
                            <span className="absolute left-4 top-1/2 -translate-y-1/2 text-slate-300 font-bold text-lg italic"></span>
                            <input type="number" className="w-48 bg-white border border-slate-200 rounded-2xl pl-9 pr-6 py-2.5 text-right font-black text-lg outline-none focus:ring-4 focus:ring-blue-100 transition-all shadow-sm" value={wageSettings[n].rate} onChange={e => {
                                const next = {...wageSettings, [n]: {...wageSettings[n], rate: utils.parseNum(e.target.value)}};
                                setWageSettings(next); saveData({ wageSettings: next });
                              }} />
                          </div>
                        </div>
                      </div>
                    ))}
                  </div>
                </div>
              </div>
            )}
          </main>
          
          {selectedDetail && (
            <div className="fixed inset-0 z-[100] flex items-center justify-center p-6 bg-slate-900/70 backdrop-blur-md animate-fade-in no-print text-slate-900">
              <div className={`bg-white w-full max-w-2xl rounded-[40px] shadow-2xl overflow-hidden flex flex-col max-h-[85vh] border-2 ${['미분류', '미지정'].includes(selectedDetail.name) ? 'border-red-400' : 'border-white/20'}`}>
                <div className={`p-8 text-white flex justify-between items-center shrink-0 ${['미분류', '미지정'].includes(selectedDetail.name) ? 'bg-red-800' : 'bg-slate-800'}`}>
                  <h3 className="text-xl font-black italic tracking-tighter uppercase leading-tight">{selectedDetail.name} ANALYTICS</h3>
                  <button onClick={() => setSelectedDetail(null)} className="p-2 hover:bg-white/10 rounded-full transition-colors"><Icon name="x" size={20} /></button>
                </div>
                <div className="flex-1 overflow-y-auto custom-scrollbar p-10 space-y-10 text-slate-800 text-center">
                   <div className="space-y-1">
                     <p className="text-[10px] font-bold text-slate-400 uppercase tracking-widest leading-none">Settlement Sum</p>
                     <p className="text-4xl font-black tracking-tight italic underline decoration-slate-100 underline-offset-8">{Math.round(selectedDetail.final).toLocaleString()}</p>
                   </div>
                   
                   <div className="flex justify-center gap-12 pt-4 font-bold border-y py-6 border-slate-100">
                     <div className="flex flex-col gap-1 text-center"><span className="text-[9px] text-slate-400 uppercase tracking-widest">Direct Input</span><span className="text-xl tracking-tighter italic">{utils.formatWon(selectedDetail.direct)}</span></div>
                     <div className="flex flex-col gap-1 text-center"><span className="text-[9px] text-blue-500 uppercase tracking-widest">Alloc(A+B)</span><span className="text-xl tracking-tighter italic">{utils.formatWon(selectedDetail.allocA + selectedDetail.allocB)}</span></div>
                   </div>

                   <div className="space-y-6 text-left">
                       <div className="flex items-center gap-3 mb-4">
                          <div className={`w-1.5 h-6 rounded-full ${['미분류', '미지정'].includes(selectedDetail.name) ? 'bg-red-600' : 'bg-slate-800'}`}></div>
                          <h4 className="font-bold text-slate-800 text-base uppercase tracking-tighter italic">Breakdown Analysis</h4>
                       </div>
                       {selectedDetail.byAccount && (
                         <div className="flex items-center gap-2 mb-3">
                           {[
                             { id: 'account', label: '계정별' },
                             { id: 'form', label: '형식별' },
                             { id: 'team', label: '팀별' }
                           ].map(tab => (
                             <button
                               key={tab.id}
                               type="button"
                               onClick={() => setDetailTab(tab.id)}
                               className={`px-3 py-1.5 rounded-lg text-xs font-bold border transition-all ${detailTab === tab.id ? 'bg-slate-800 text-white border-slate-800' : 'bg-white text-slate-500 border-slate-200 hover:text-slate-800'}`}
                             >
                               {tab.label}
                             </button>
                           ))}
                         </div>
                       )}
                       <div className="space-y-3">
                          {(() => {
                            const groups = selectedDetail.byAccount
                              ? (detailTab === 'form'
                                  ? selectedDetail.byForm
                                  : detailTab === 'team'
                                    ? selectedDetail.byTeam
                                    : selectedDetail.byAccount)
                              : selectedDetail.breakdown;
                            if (!groups) return <p className="text-center py-10 text-slate-300 font-bold uppercase italic text-xs tracking-widest">No Breakdown Data</p>;
                            return Object.entries(groups).sort((a,b) => b[1]-a[1]).map(([name, val], i) => {
                             const p = selectedDetail.direct > 0 ? (val / selectedDetail.direct * 100).toFixed(1) : 0;
                             const isErrParent = ['미분류', '미지정'].includes(selectedDetail.name);
                             return (
                               <div key={i} className={`bg-slate-50 p-5 rounded-2xl border transition-all ${isErrParent ? 'hover:border-red-400' : 'hover:border-blue-200 hover:bg-white'}`}>
                                  <div className="flex justify-between text-xs font-bold mb-3 text-slate-900">
                                     <span className="text-slate-600 italic uppercase tracking-tighter">{name}</span>
                                     <span className="text-slate-900">{utils.formatWon(val)} <span className={`${isErrParent ? 'text-red-600' : 'text-blue-600'} font-black ml-1.5`}>({p}%)</span></span>
                                  </div>
                                  <div className="w-full bg-slate-200 h-1 rounded-full overflow-hidden">
                                     <div className={`h-full transition-all duration-[1000ms] ease-out ${isErrParent ? 'bg-red-600' : 'bg-slate-800'}`} style={{ width: `${p}%` }}></div>
                                  </div>
                               </div>
                             );
                           });
                         })()}
                      </div>
                   </div>

                   <div className="text-center pt-8">
                     <button onClick={() => setSelectedDetail(null)} className={`w-full max-w-xs py-4 text-white font-bold rounded-2xl text-sm uppercase shadow-lg transition-all hover:scale-105 active:scale-95 ${['미분류', '미지정'].includes(selectedDetail.name) ? 'bg-red-800 hover:bg-red-900' : 'bg-slate-800 hover:bg-slate-700'}`}>확인 완료</button>
                   </div>
                </div>
              </div>
            </div>
          )}
        </div>
      );
    };

    const root = ReactDOM.createRoot(document.getElementById("root"));
    root.render(<App />);
  </script>
</body>
</html>