chore(요구): 제안 372 대시보드 가벼운 판 — 홈 = 지금 하는 프로젝트 큰 카드 · 칸반 · 집계는 산림청 · 시스템만

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 17:45:19 +09:00
co-authored by Claude Opus 5.5
parent b14c170b8e
commit 6c56fddfdd
@@ -0,0 +1,239 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>Aislo 대시보드 — 가벼운 판</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css">
<style>
:root { --ac: #1f7a4d; --acs: #eaf5ee; --bg: #f6f7f9; --card: #fff; --bd: #e6e8ec; --fg: #1f2328; --mut: #6b7280; --road: #1f7a4d; --plan: #2f5fbf; --stab: #c26a0f; --red: #e5484d; --amb: #f5a524; }
* { box-sizing: border-box; }
body { margin: 0; font-family: Pretendard, "Malgun Gothic", sans-serif; color: var(--fg); font-size: 13.5px; line-height: 1.5; background: var(--background, #fff); }
.intro { max-width: 1120px; margin: 0 auto; padding: 16px 18px 4px; color: var(--foreground, #1f2328); }
.intro h1 { font-size: 20px; margin: 0 0 2px; }
.intro p { margin: 0 0 8px; color: var(--muted-foreground, #6b7280); }
.flow { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-bottom: 10px; }
.flow b { background: var(--acs); color: var(--ac); border-radius: 6px; padding: 2px 8px; font-weight: 600; }
.ctl { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.seg { display: inline-flex; border: 1px solid var(--bd); border-radius: 8px; overflow: hidden; background: #fff; }
.seg button { border: 0; background: #fff; padding: 5px 11px; font: inherit; cursor: pointer; color: #3b434b; }
.seg button.on { background: var(--ac); color: #fff; }
.app { max-width: 1120px; margin: 8px auto 30px; border: 1px solid var(--bd); border-radius: 14px; overflow: hidden; background: var(--bg); }
.hd { display: flex; align-items: center; gap: 10px; height: 48px; padding: 0 16px; background: #fff; border-bottom: 1px solid var(--bd); }
.logo { font-weight: 800; letter-spacing: .6px; }
.org { border: 1px solid var(--bd); border-radius: 6px; padding: 2px 8px; color: #57606a; }
.search { margin-left: 6px; width: 240px; border: 1px solid var(--bd); border-radius: 7px; padding: 4px 10px; color: #8b949e; background: #fafbfc; }
.sp { flex: 1; }
.ib { width: 28px; height: 28px; border: 1px solid var(--bd); border-radius: 7px; display: grid; place-items: center; color: #57606a; position: relative; background: #fff; }
.ib i { position: absolute; top: -5px; right: -5px; background: var(--red); color: #fff; font-style: normal; font-size: 9.5px; border-radius: 8px; padding: 0 4px; }
.av { width: 28px; height: 28px; border-radius: 50%; background: #cfe8da; color: var(--ac); display: grid; place-items: center; font-weight: 700; font-size: 12px; }
.bodyx { display: grid; grid-template-columns: 208px 1fr; min-height: 640px; }
.nav { background: #fff; border-right: 1px solid var(--bd); padding: 10px 8px; }
.grp { font-size: 11px; color: #8b949e; margin: 12px 8px 4px; }
.it { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 7px; color: #3b434b; cursor: pointer; }
.it:hover { background: #f3f4f6; }
.it.on { background: var(--acs); color: var(--ac); font-weight: 600; }
.it svg { width: 15px; height: 15px; flex: 0 0 15px; }
.it .n { margin-left: auto; font-size: 11px; color: #8b949e; }
.it .n.r { color: #fff; background: var(--red); border-radius: 8px; padding: 0 5px; }
.main { padding: 18px 20px 24px; }
.ttl { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.ttl h2 { font-size: 19px; margin: 0; }
.crumb { color: #8b949e; font-size: 12px; margin-bottom: 4px; cursor: pointer; }
.btnp { background: var(--ac); color: #fff; border-radius: 7px; padding: 6px 12px; font-weight: 600; cursor: pointer; }
.btn { border: 1px solid var(--bd); background: #fff; border-radius: 7px; padding: 5px 10px; cursor: pointer; }
.screen { display: none; } .screen.on { display: block; }
/* board */
.board { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.tile { background: var(--card); border: 1px solid var(--bd); border-radius: 12px; padding: 14px 16px; cursor: pointer; transition: box-shadow .15s, border-color .15s; }
.tile:hover { border-color: #cfd6dd; box-shadow: 0 2px 10px rgba(16,24,40,.06); }
.tile.w2 { grid-column: span 2; }
.th { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.th .ic { width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center; }
.th b { font-size: 14px; }
.th .more { margin-left: auto; color: #8b949e; font-size: 12px; }
.kpis { display: flex; gap: 18px; flex-wrap: wrap; }
.kpi .v { font-size: 24px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; }
.kpi .l { color: var(--mut); font-size: 12px; }
.kpi .d { font-size: 11px; margin-left: 4px; }
.up { color: #16a34a; } .dn { color: var(--red); }
.row { display: flex; gap: 14px; align-items: center; }
.thumbs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 12px; }
.pthumb { border: 1px solid var(--bd); border-radius: 9px; overflow: hidden; background: #fff; }
.pthumb svg { display: block; width: 100%; height: 56px; }
.pthumb .c { padding: 5px 8px; font-size: 12px; }
.badge { display: inline-block; font-size: 10.5px; border-radius: 5px; padding: 0 6px; margin-right: 4px; font-weight: 600; }
.b-road { background: #e7f3ec; color: var(--road); } .b-plan { background: #e8effc; color: var(--plan); } .b-stab { background: #fdf0e3; color: var(--stab); } .b-sabang { background: #f1f1f4; color: #6b7280; border: 1px dashed #c9ccd3; }
.bar { height: 6px; border-radius: 4px; background: #edf0f2; overflow: hidden; }
.bar i { display: block; height: 100%; background: var(--ac); }
.legend { display: grid; gap: 4px; font-size: 12px; }
.legend span { display: flex; align-items: center; gap: 6px; }
.sw { width: 9px; height: 9px; border-radius: 3px; display: inline-block; }
.mini { font-size: 12px; color: var(--mut); }
.todo { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-top: 1px solid #f0f1f3; font-size: 12.5px; }
.todo:first-child { border-top: 0; }
.dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px; }
.avs { display: flex; } .avs span { width: 24px; height: 24px; border-radius: 50%; border: 2px solid #fff; margin-left: -6px; display: grid; place-items: center; font-size: 10px; font-weight: 700; color: #fff; }
.avs span:first-child { margin-left: 0; }
[data-roles] { }
.hide { display: none !important; }
/* list */
.tool { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; flex-wrap: wrap; }
.chip { border: 1px solid var(--bd); border-radius: 14px; padding: 2px 10px; background: #fff; cursor: pointer; font-size: 12.5px; }
.chip.on { border-color: var(--ac); color: var(--ac); background: var(--acs); }
.kan { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; align-items: start; }
.col { background: #eef0f3; border-radius: 12px; padding: 10px; }
.col h4 { margin: 0 0 8px; font-size: 13px; display: flex; align-items: center; gap: 6px; }
.col h4 .n { color: #8b949e; font-weight: 400; }
.kc { background: #fff; border: 1px solid var(--bd); border-radius: 10px; margin-bottom: 8px; overflow: hidden; cursor: pointer; }
.kc:hover { border-color: #cfd6dd; box-shadow: 0 2px 8px rgba(16,24,40,.06); }
.kc svg { display: block; width: 100%; height: 50px; }
.kc .c { padding: 7px 9px; }
.kc .meta { display: flex; justify-content: space-between; color: #8b949e; font-size: 11.5px; margin-top: 5px; }
.col.done .kc { opacity: .75; }
.tbl { background: #fff; border: 1px solid var(--bd); border-radius: 12px; overflow: hidden; }
table { border-collapse: collapse; width: 100%; }
th, td { padding: 9px 12px; text-align: left; border-bottom: 1px solid #f0f1f3; font-size: 13px; }
th { color: #8b949e; font-weight: 600; font-size: 12px; background: #fafbfc; }
tr.click { cursor: pointer; } tr.click:hover td { background: #f8faf9; }
/* detail */
.dgrid { display: grid; grid-template-columns: 1.55fr 1fr; gap: 14px; }
.pane { background: #fff; border: 1px solid var(--bd); border-radius: 12px; padding: 14px 16px; margin-bottom: 14px; }
.pane h3 { margin: 0 0 10px; font-size: 14px; }
.steps { display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px; }
.st { border: 1px solid var(--bd); border-radius: 9px; padding: 7px 6px; text-align: center; font-size: 11.5px; cursor: pointer; background: #fff; }
.st .s { width: 20px; height: 20px; border-radius: 50%; margin: 0 auto 4px; display: grid; place-items: center; font-size: 11px; font-weight: 700; }
.st.ok .s { background: var(--ac); color: #fff; } .st.cur { border-color: var(--ac); background: var(--acs); } .st.cur .s { background: #fff; color: var(--ac); border: 2px solid var(--ac); } .st.no .s { background: #edf0f2; color: #8b949e; }
.kv { display: grid; grid-template-columns: 96px 1fr; gap: 6px 10px; font-size: 13px; } .kv span { color: var(--mut); }
.link { display: flex; align-items: center; gap: 8px; padding: 7px 0; border-top: 1px solid #f0f1f3; } .link:first-child { border-top: 0; }
.tl { border-left: 2px solid #e6e8ec; margin-left: 4px; padding-left: 12px; }
.tl div { position: relative; padding: 4px 0; font-size: 12.5px; } .tl div::before { content: ""; position: absolute; left: -17px; top: 10px; width: 8px; height: 8px; border-radius: 50%; background: var(--ac); }
.note { max-width: 1120px; margin: 0 auto; padding: 0 18px 20px; color: var(--muted-foreground, #6b7280); font-size: 12.5px; }
@media (max-width: 900px) { .board { grid-template-columns: 1fr 1fr; } .tile.w2 { grid-column: span 2; } .kan { grid-template-columns: 1fr 1fr; } .dgrid { grid-template-columns: 1fr; } .bodyx { grid-template-columns: 1fr; } .nav { display: none; } }
</style>
</head>
<body>
<div class="intro">
<h1>Aislo 대시보드 — 가벼운 판(프로젝트 1~2 개 기준)</h1>
<p>요구 372 · 사용자 1 개 · 회사 관리자 1~2 개 진행이면 칸반 · 집계 그래프는 거창 → <b>「지금 하는 프로젝트」 가 첫 화면의 주인</b> · 가짜 숫자 · 2026-10-26</p>
<div class="flow"><b>홈</b> 지금 프로젝트 큰 카드 하나(둘) · 옆에 처리할 일 → <b>프로젝트 상세</b> = 그 카드의 펼친 판 → <b>[이어서 하기]</b> 작업 화면</div>
<div class="ctl"><span style="color:var(--muted-foreground,#6b7280)">보는 사람 :</span>
<span class="seg" id="roleSeg"><button data-r="user" class="on">설계 엔지니어(1 개)</button><button data-r="admin">회사 관리자(2 개)</button><button data-r="many">산림청 · 시스템(많음)</button></span>
</div>
</div>
<div class="app">
<div class="hd"><span class="logo">AISLO</span><span class="org">예시건설</span><span class="sp"></span><span class="ib">?</span><span class="ib">🔔<i>2</i></span><span class="av">홍</span></div>
<div class="bodyx" style="grid-template-columns:186px 1fr">
<div class="nav">
<div class="it on"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 11l9-7 9 7v9a1 1 0 0 1-1 1h-5v-6h-6v6H4a1 1 0 0 1-1-1z"/></svg>홈</div>
<div class="it"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 9h18"/></svg>프로젝트</div>
<div class="it"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16v12H5l-1 4z"/></svg>받은 예정노선<span class="n r">1</span></div>
<div class="it"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h16v13H4zM8 7V4h8v3"/></svg>임시 보관함</div>
<div class="it" data-roles="admin many"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="8" r="3"/><path d="M3 20a6 6 0 0 1 12 0"/></svg>사람<span class="n r">1</span></div>
<div class="it" data-roles="admin many"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 21V5l8-2 8 2v16"/></svg>회사</div>
<div class="grp">도움</div>
<div class="it"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="9"/><path d="M12 17h.01M9.5 9a2.5 2.5 0 1 1 3.5 2.3c-.7.3-1 .9-1 1.7"/></svg>사용자 가이드</div>
</div>
<div class="main">
<div class="ttl"><h2>홈</h2><span class="sp"></span><span class="btn">+ 새 프로젝트</span></div>
<div style="display:grid;grid-template-columns:1fr 290px;gap:14px;align-items:start" class="homegrid">
<div>
<!-- 지금 하는 프로젝트 1 -->
<div class="pane" style="padding:0;overflow:hidden">
<div style="display:grid;grid-template-columns:1.1fr 1fr">
<svg viewBox="0 0 400 230" width="100%" height="230" style="display:block;background:#e3efe6">
<path d="M0 180 Q90 130 160 150 T300 120 T400 160" fill="none" stroke="#cfe3d4" stroke-width="20"/>
<path d="M15 205 C 110 185, 150 60, 230 95 S 340 175, 390 30" fill="none" stroke="#c0392b" stroke-width="3.5"/>
<g fill="#fff" stroke="#c0392b" stroke-width="2"><circle cx="15" cy="205" r="5"/><circle cx="390" cy="30" r="5"/></g>
<rect x="10" y="10" width="92" height="22" rx="6" fill="#fff" opacity=".9"/><text x="18" y="25" font-size="12" fill="#3b434b">연장 1,141 m</text>
</svg>
<div style="padding:14px 16px">
<div class="mini">지금 하는 프로젝트</div>
<div style="display:flex;align-items:center;gap:8px;margin:2px 0 6px"><b style="font-size:17px">용화 임도</b><span class="badge b-road">임도설계</span></div>
<div class="mini">충북 보은 용화 · 간선임도 · 마지막 수정 오늘 14:20</div>
<div style="margin:12px 0 4px;font-size:12.5px"><b>4 / 7 단계 · 횡단설계</b></div>
<div style="display:flex;gap:4px">
<i class="pip ok"></i><i class="pip ok"></i><i class="pip ok"></i><i class="pip cur"></i><i class="pip"></i><i class="pip"></i><i class="pip"></i>
</div>
<div class="mini" style="display:flex;justify-content:space-between;margin-top:3px"><span>파일</span><span>원가</span></div>
<div class="nextbox">다음 할 일 — 측점 12 곳 횡단 확인 · 구조물 2 개 위치 맞춤</div>
<div style="display:flex;gap:6px;margin-top:12px"><span class="btnp">이어서 하기 — 횡단설계 ›</span><span class="btn" data-go="detail">자세히</span></div>
</div>
</div>
<div class="kstrip">
<div><b>57</b><span>측점</span></div><div><b>12</b><span>구조물</span></div><div><b>8.2 %</b><span>최대 기울기</span></div><div><b>8,420 / 6,010</b><span>절토 / 성토 m³</span></div><div><b>도면 11 장</b><span>PDF 받기 ›</span></div>
</div>
</div>
<!-- 회사 관리자 = 두 번째 프로젝트 -->
<div class="pane" data-roles="admin" style="padding:12px 14px">
<div style="display:flex;align-items:center;gap:10px">
<svg viewBox="0 0 120 60" width="120" height="60" style="border-radius:8px;background:#e3efe6;flex:0 0 120px"><path d="M5 50 C 40 45, 60 10, 115 18" fill="none" stroke="#c0392b" stroke-width="2.5"/></svg>
<div style="flex:1">
<div style="display:flex;align-items:center;gap:8px"><b>미원 임도</b><span class="badge b-road">임도설계</span><span class="mini">김영희</span></div>
<div class="mini" style="margin:4px 0">7 / 7 단계 · 원가 · <b style="color:var(--amb)">확정 전</b></div>
<div class="bar"><i style="width:100%"></i></div>
</div>
<span class="btn">열기 ›</span>
</div>
</div>
<div class="pane" style="padding:10px 14px">
<div style="display:flex;align-items:center"><b style="font-size:13px">다른 프로젝트</b><span class="sp"></span><span class="mini">전체 보기 ›</span></div>
<div class="link" style="font-size:12.5px"><span class="badge b-road">임도</span>운수 임도 · 완료 10-12<span class="sp" style="flex:1"></span><span class="mini">보관</span></div>
<div class="link" style="font-size:12.5px"><span class="badge b-stab">안정성</span>용화 안정성평가 · 2D 해석 2/6<span class="sp" style="flex:1"></span><span class="mini">열기 ›</span></div>
</div>
</div>
<!-- 오른쪽 좁은 칸 -->
<div>
<div class="pane" style="padding:12px 14px">
<b style="font-size:13px">처리할 일</b>
<div class="todo"><span class="dot" style="background:#2f5fbf"></span>새 예정노선 1 건 — 2027 용화<span class="sp"></span><span class="btn">보기</span></div>
<div class="todo" data-roles="admin"><span class="dot" style="background:var(--red)"></span>가입 요청 — 김철수<span class="sp"></span><span class="btn">처리</span></div>
<div class="todo"><span class="dot" style="background:var(--amb)"></span>보관 파일 1 개 · 4 일 남음<span class="sp"></span><span class="btn">열기</span></div>
</div>
<div class="pane" style="padding:12px 14px">
<b style="font-size:13px">임시 보관함</b>
<div class="mini" style="margin:6px 0 4px">2 개 · 380 MB</div>
<div class="row"><span class="mini" style="width:92px">용화_LAS.zip</span><div class="bar" style="flex:1"><i style="width:15%;background:var(--amb)"></i></div><span class="mini">4 일</span></div>
</div>
<div class="pane" data-roles="admin" style="padding:12px 14px">
<b style="font-size:13px">우리 회사</b>
<div class="kpis" style="margin-top:6px"><div class="kpi"><div class="v" style="font-size:20px">5</div><div class="l">팀원</div></div><div class="kpi"><div class="v" style="font-size:20px">2</div><div class="l">진행 중</div></div></div>
</div>
<div class="pane" style="padding:12px 14px;background:#fafbfc">
<b style="font-size:13px">처음이면</b>
<div class="mini" style="margin-top:4px">☑ 내 정보 · 서명<br>☐ 측량 파일 올리기<br>☐ 첫 프로젝트 만들기</div>
</div>
</div>
</div>
<div id="manyNote" class="pane hide" style="margin-top:4px;background:#f8faf9">
<b>산림청 · 시스템 관리자</b> — 프로젝트가 여러 회사 · 여러 연도로 많음 → 앞 시안(요약 보드 · 숫자 · 그래프 · 표/칸반 전환)이 그대로 맞음. 홈 맨 위에 상태별 수 · 업체 지정 대기 · 서버 상태.
</div>
</div>
</div>
</div>
<div class="note">
<b>달라진 점(앞 시안 대비)</b> — ① 홈 = 「지금 하는 프로젝트」 큰 카드(지도 · 단계 점 · 다음 할 일 · 핵심 숫자 · [이어서 하기]) · 회사 관리자는 둘째 프로젝트 한 줄 카드 ② 칸반 · 프로젝트 수 그래프는 뺌(1~2 개엔 볼 것이 없음) — 산림청 · 시스템 관리자만 ③ 프로젝트 페이지 = 카드 몇 장 + 표 전환(칸반 칸 없음) ④ 상세 페이지는 남김 — 홈 카드의 [자세히] = 펼친 판(기본 정보 · 연결된 것 · 산출물 · 최근 변경).
</div>
<style>
.pip { flex: 1; height: 8px; border-radius: 4px; background: #e6e9ed; display: block; }
.pip.ok { background: var(--ac); } .pip.cur { background: #fff; border: 2px solid var(--ac); }
.nextbox { margin-top: 12px; background: #fff8eb; border: 1px solid #f6dfb3; color: #8a5a00; border-radius: 8px; padding: 7px 10px; font-size: 12.5px; }
.kstrip { display: grid; grid-template-columns: repeat(5, 1fr); border-top: 1px solid var(--bd); }
.kstrip div { padding: 10px 12px; border-left: 1px solid #f0f1f3; } .kstrip div:first-child { border-left: 0; }
.kstrip b { display: block; font-size: 15px; font-variant-numeric: tabular-nums; } .kstrip span { color: var(--mut); font-size: 11.5px; }
@media (max-width: 900px) { .homegrid { grid-template-columns: 1fr !important; } .kstrip { grid-template-columns: repeat(2, 1fr); } }
</style>
<script>
const setRole = r => { document.querySelectorAll('#roleSeg button').forEach(x => x.classList.toggle('on', x.dataset.r === r));
document.querySelectorAll('[data-roles]').forEach(el => el.classList.toggle('hide', !el.dataset.roles.split(' ').includes(r)));
document.getElementById('manyNote').classList.toggle('hide', r !== 'many'); };
document.querySelectorAll('#roleSeg button').forEach(b => b.onclick = () => setRole(b.dataset.r));
setRole('user');
</script>
</body>
</html>