diff --git a/src/components/MobileScreenList.js b/src/components/MobileScreenList.js index 1d093d2..b4081c8 100644 --- a/src/components/MobileScreenList.js +++ b/src/components/MobileScreenList.js @@ -1,14 +1,45 @@ -import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; -import { SolutionIcon } from './SolutionIcon'; +import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; const TYPE_LABEL = { customer: '사용자', admin: '관리자', field: '현장', }; +function guessLayout(name) { + const n = name; + if (/대시보드|현황|KPI|분석|통계|보드|히트맵|스코어/.test(n)) + return 'dashboard'; + if (/목록|리스트|조회|紐⑸줉/.test(n)) + return 'list'; + if (/차트|그래프|추이|예측|리포트|ROI/.test(n)) + return 'chart'; + if (/등록|작성|입력|설정|수정|관리/.test(n)) + return 'form'; + if (/칸반|보드|타임라인|스프린트/.test(n)) + return 'kanban'; + return 'list'; +} +const BAR_HEIGHTS = [60, 85, 45, 70, 55, 90, 40, 75]; +function PhoneMockup({ screen, color }) { + const layout = guessLayout(screen.name); + const alpha = (a) => `${color}${Math.round(a * 255).toString(16).padStart(2, '0')}`; + const headerBg = `linear-gradient(135deg, ${alpha(0.9)}, ${alpha(0.6)})`; + return (_jsxs("div", { className: "phone-card", children: [_jsxs("div", { className: "phone-frame", style: { borderColor: alpha(0.4) }, children: [_jsxs("div", { className: "phone-status", children: [_jsx("span", { children: "9:41" }), _jsx("div", { className: "phone-status-icons", children: _jsxs("svg", { width: "14", height: "8", viewBox: "0 0 14 8", fill: "none", children: [_jsx("rect", { x: "0.5", y: "0.5", width: "11", height: "7", rx: "1.5", stroke: "rgba(255,255,255,0.5)", strokeWidth: "1" }), _jsx("rect", { x: "1.5", y: "1.5", width: "7", height: "5", rx: "0.5", fill: color }), _jsx("path", { d: "M12 2.5v3", stroke: "rgba(255,255,255,0.5)", strokeWidth: "1", strokeLinecap: "round" })] }) })] }), _jsxs("div", { className: "phone-header", style: { background: headerBg }, children: [_jsx("div", { className: "phone-header-icon", style: { background: alpha(0.3) }, children: _jsxs("svg", { width: "10", height: "10", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2.5", strokeLinecap: "round", children: [_jsx("rect", { x: "5", y: "2", width: "14", height: "20", rx: "2" }), _jsx("line", { x1: "12", y1: "18", x2: "12.01", y2: "18" })] }) }), _jsx("span", { className: "phone-header-title", children: screen.name }), _jsx("div", { className: "phone-header-action", children: _jsxs("svg", { width: "8", height: "8", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2.5", strokeLinecap: "round", children: [_jsx("circle", { cx: "12", cy: "12", r: "1" }), _jsx("circle", { cx: "19", cy: "12", r: "1" }), _jsx("circle", { cx: "5", cy: "12", r: "1" })] }) })] }), _jsxs("div", { className: "phone-body", children: [layout === 'dashboard' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "phone-stat-row", children: [_jsxs("div", { className: "phone-stat", children: [_jsx("div", { className: "phone-stat-val", style: { color }, children: "24" }), _jsx("div", { className: "phone-stat-lbl", children: "\uC9C4\uD589\uC911" })] }), _jsxs("div", { className: "phone-stat", children: [_jsx("div", { className: "phone-stat-val", style: { color: '#10b981' }, children: "98%" }), _jsx("div", { className: "phone-stat-lbl", children: "\uC644\uB8CC\uC728" })] })] }), _jsxs("div", { className: "phone-stat-row", children: [_jsxs("div", { className: "phone-stat", children: [_jsx("div", { className: "phone-stat-val", style: { color: '#f59e0b' }, children: "3" }), _jsx("div", { className: "phone-stat-lbl", children: "\uACBD\uBCF4" })] }), _jsxs("div", { className: "phone-stat", children: [_jsx("div", { className: "phone-stat-val", style: { color: '#6366f1' }, children: "142" }), _jsx("div", { className: "phone-stat-lbl", children: "\uC11C\uBC84" })] })] }), _jsxs("div", { className: "phone-list-item", children: [_jsx("div", { className: "phone-list-dot", style: { background: color } }), _jsxs("div", { className: "phone-list-lines", children: [_jsx("div", { className: "phone-list-line", style: { width: '75%' } }), _jsx("div", { className: "phone-list-line", style: { width: '50%', opacity: 0.5 } })] })] })] })), layout === 'chart' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "phone-stat-row", children: [_jsxs("div", { className: "phone-stat", children: [_jsx("div", { className: "phone-stat-val", style: { color }, children: "87%" }), _jsx("div", { className: "phone-stat-lbl", children: "\uB2EC\uC131\uB960" })] }), _jsxs("div", { className: "phone-stat", children: [_jsx("div", { className: "phone-stat-val", style: { color: '#10b981' }, children: "\u219112%" }), _jsx("div", { className: "phone-stat-lbl", children: "\uC804\uC6D4\uBE44" })] })] }), _jsx("div", { className: "phone-chart", children: BAR_HEIGHTS.map((h, i) => (_jsx("div", { className: "phone-bar", style: { + height: `${h}%`, + background: i === 5 ? color : alpha(0.35 + (i % 3) * 0.1), + } }, i))) }), _jsxs("div", { className: "phone-list-item", children: [_jsx("div", { className: "phone-list-dot", style: { background: '#10b981' } }), _jsx("div", { className: "phone-list-lines", children: _jsx("div", { className: "phone-list-line", style: { width: '80%' } }) }), _jsx("div", { className: "phone-list-badge", style: { background: alpha(0.2), color }, children: "AI" })] })] })), (layout === 'list' || layout === 'kanban') && (_jsx(_Fragment, { children: [ + { dot: color, w1: '80%', w2: '55%', badge: '완료', bc: '#10b981' }, + { dot: '#f59e0b', w1: '65%', w2: '40%', badge: '진행', bc: '#f59e0b' }, + { dot: '#6366f1', w1: '90%', w2: '70%', badge: '대기', bc: '#6366f1' }, + ].map((row, i) => (_jsxs("div", { className: "phone-list-item", children: [_jsx("div", { className: "phone-list-dot", style: { background: row.dot } }), _jsxs("div", { className: "phone-list-lines", children: [_jsx("div", { className: "phone-list-line", style: { width: row.w1 } }), _jsx("div", { className: "phone-list-line", style: { width: row.w2, opacity: 0.5 } })] }), _jsx("div", { className: "phone-list-badge", style: { background: `${row.bc}22`, color: row.bc }, children: row.badge })] }, i))) })), layout === 'form' && (_jsx(_Fragment, { children: [70, 100, 85].map((w, i) => (_jsxs("div", { style: { + background: 'rgba(255,255,255,0.04)', + border: '1px solid rgba(255,255,255,0.08)', + borderRadius: 6, padding: '5px 8px', + }, children: [_jsx("div", { style: { height: 4, width: '35%', background: alpha(0.5), borderRadius: 2, marginBottom: 4 } }), _jsx("div", { style: { height: 5, width: `${w}%`, background: 'rgba(255,255,255,0.2)', borderRadius: 2 } })] }, i))) }))] }), _jsxs("div", { className: "phone-footer", children: [_jsx("div", { className: "phone-btn", style: { background: alpha(0.15), border: `1px solid ${alpha(0.3)}`, color }, children: "\uCDE8\uC18C" }), _jsx("div", { className: "phone-btn", style: { background: headerBg }, children: "\uD655\uC778" })] })] }), _jsxs("div", { className: "phone-label", children: [_jsx("span", { className: "phone-name", children: screen.name }), _jsx("span", { className: "phone-type", style: { color, borderColor: alpha(0.4) }, children: TYPE_LABEL[screen.type] || screen.type })] })] })); +} export const MobileScreenList = ({ screens, color }) => { if (!screens.length) { return _jsx("p", { className: "mobile-empty", children: "\uBAA8\uBC14\uC77C \uC571 \uD654\uBA74\uC740 \uC900\uBE44 \uC911\uC785\uB2C8\uB2E4." }); } - return (_jsx("div", { className: "mobile-screen-list", children: screens.map((s) => (_jsxs("div", { className: "phone-card", children: [_jsxs("div", { className: "phone-frame", style: { borderColor: `${color}55` }, children: [_jsx("div", { className: "phone-notch" }), _jsx("div", { className: "phone-screen", style: { background: `linear-gradient(160deg, ${color}22, #0f172a 70%)` }, children: _jsxs("div", { className: "phone-screen-inner", children: [_jsx("span", { className: "phone-icon", style: { color }, children: _jsx(SolutionIcon, { slug: "mobile", size: 40, color: color }) }), _jsx("span", { className: "phone-screen-name", children: s.name })] }) })] }), _jsxs("div", { className: "phone-label", children: [_jsx("span", { className: "phone-name", children: s.name }), _jsx("span", { className: "phone-type", style: { color, borderColor: `${color}55` }, children: TYPE_LABEL[s.type] || s.type })] })] }, s.name))) })); + return (_jsx("div", { className: "mobile-screen-list", children: screens.map((s) => (_jsx(PhoneMockup, { screen: s, color: color }, s.name))) })); }; export default MobileScreenList; diff --git a/src/components/MobileScreenList.tsx b/src/components/MobileScreenList.tsx index 7120081..3a35681 100644 --- a/src/components/MobileScreenList.tsx +++ b/src/components/MobileScreenList.tsx @@ -1,8 +1,12 @@ import React from 'react'; -import { SolutionIcon } from './SolutionIcon'; + +interface Screen { + name: string; + type: string; +} interface MobileScreenListProps { - screens: { name: string; type: string }[]; + screens: Screen[]; color: string; } @@ -12,6 +16,184 @@ const TYPE_LABEL: Record = { field: '현장', }; +/* --- 화면 이름 키워드로 UI 레이아웃 결정 --- */ +type Layout = 'dashboard' | 'list' | 'chart' | 'form' | 'kanban'; + +function guessLayout(name: string): Layout { + const n = name; + if (/대시보드|현황|KPI|분석|통계|보드|히트맵|스코어/.test(n)) return 'dashboard'; + if (/목록|리스트|조회|紐⑸줉/.test(n)) return 'list'; + if (/차트|그래프|추이|예측|리포트|ROI/.test(n)) return 'chart'; + if (/등록|작성|입력|설정|수정|관리/.test(n)) return 'form'; + if (/칸반|보드|타임라인|스프린트/.test(n)) return 'kanban'; + return 'list'; +} + +const BAR_HEIGHTS = [60, 85, 45, 70, 55, 90, 40, 75]; + +function PhoneMockup({ screen, color }: { screen: Screen; color: string }) { + const layout = guessLayout(screen.name); + const alpha = (a: number) => `${color}${Math.round(a * 255).toString(16).padStart(2, '0')}`; + + const headerBg = `linear-gradient(135deg, ${alpha(0.9)}, ${alpha(0.6)})`; + return ( +
+
+ {/* 상태바 */} +
+ 9:41 +
+ {/* 배터리 아이콘 (SVG) */} + + + + + +
+
+ + {/* 앱 헤더 */} +
+
+ + + + +
+ {screen.name} +
+ + + +
+
+ + {/* 본문 */} +
+ {layout === 'dashboard' && ( + <> +
+
+
24
+
진행중
+
+
+
98%
+
완료율
+
+
+
+
+
3
+
경보
+
+
+
142
+
서버
+
+
+
+
+
+
+
+
+
+ + )} + {layout === 'chart' && ( + <> +
+
+
87%
+
달성률
+
+
+
↑12%
+
전월비
+
+
+
+ {BAR_HEIGHTS.map((h, i) => ( +
+ ))} +
+
+
+
+
+
+
AI
+
+ + )} + {(layout === 'list' || layout === 'kanban') && ( + <> + {[ + { dot: color, w1: '80%', w2: '55%', badge: '완료', bc: '#10b981' }, + { dot: '#f59e0b', w1: '65%', w2: '40%', badge: '진행', bc: '#f59e0b' }, + { dot: '#6366f1', w1: '90%', w2: '70%', badge: '대기', bc: '#6366f1' }, + ].map((row, i) => ( +
+
+
+
+
+
+
+ {row.badge} +
+
+ ))} + + )} + {layout === 'form' && ( + <> + {[70, 100, 85].map((w, i) => ( +
+
+
+
+ ))} + + )} +
+ + {/* 하단 버튼 */} +
+
+ 취소 +
+
+ 확인 +
+
+
+ +
+ {screen.name} + + {TYPE_LABEL[screen.type] || screen.type} + +
+
+ ); +} + export const MobileScreenList: React.FC = ({ screens, color }) => { if (!screens.length) { return

모바일 앱 화면은 준비 중입니다.

; @@ -19,25 +201,7 @@ export const MobileScreenList: React.FC = ({ screens, col return (
{screens.map((s) => ( -
-
-
-
-
- - - - {s.name} -
-
-
-
- {s.name} - - {TYPE_LABEL[s.type] || s.type} - -
-
+ ))}
); diff --git a/src/styles/app.css b/src/styles/app.css index b6bde65..8967ad0 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -187,20 +187,77 @@ .step-guide-body p { font-size: 14px; color: var(--text-secondary); line-height: 1.6; } /* ============ Mobile Screens ============ */ -.mobile-screen-list { display: flex; gap: 24px; flex-wrap: wrap; } +.mobile-screen-list { display: flex; gap: 20px; flex-wrap: wrap; } .mobile-empty { font-size: 14px; color: var(--text-muted); } -.phone-card { display: flex; flex-direction: column; align-items: center; gap: 12px; } -.phone-frame { width: 150px; height: 300px; border-radius: 26px; } -.phone-notch { width: 64px; height: 18px; } -.phone-screen-inner { - width: 100%; height: 100%; display: flex; flex-direction: column; - align-items: center; justify-content: center; gap: 14px; padding: 16px; +.phone-card { display: flex; flex-direction: column; align-items: center; gap: 10px; } +.phone-frame { + width: 148px; height: 296px; border-radius: 24px; + border: 2px solid; + overflow: hidden; + background: #0a0f1e; + display: flex; flex-direction: column; + box-shadow: 0 12px 40px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.06); + position: relative; } -.phone-icon { display: inline-flex; } -.phone-screen-name { font-size: 13px; font-weight: 600; color: var(--text-secondary); text-align: center; } -.phone-label { display: flex; flex-direction: column; align-items: center; gap: 6px; } -.phone-name { font-size: 13px; font-weight: 600; } -.phone-type { font-size: 11px; font-weight: 600; border: 1px solid; border-radius: 20px; padding: 1px 8px; } +.phone-status { + display: flex; align-items: center; justify-content: space-between; + padding: 6px 12px 0; font-size: 9px; font-weight: 700; + color: rgba(255,255,255,0.5); flex-shrink: 0; +} +.phone-status-icons { display: flex; align-items: center; gap: 3px; } +.phone-header { + padding: 8px 12px 10px; flex-shrink: 0; + display: flex; align-items: center; gap: 8px; +} +.phone-header-icon { + width: 20px; height: 20px; border-radius: 6px; + display: grid; place-items: center; flex-shrink: 0; +} +.phone-header-title { + font-size: 12px; font-weight: 700; color: #fff; flex: 1; +} +.phone-header-action { + width: 18px; height: 18px; border-radius: 50%; + background: rgba(255,255,255,0.12); + display: grid; place-items: center; +} +.phone-body { flex: 1; overflow: hidden; padding: 0 10px 10px; display: flex; flex-direction: column; gap: 6px; } +.phone-stat-row { display: flex; gap: 5px; } +.phone-stat { + flex: 1; border-radius: 8px; background: rgba(255,255,255,0.06); + border: 1px solid rgba(255,255,255,0.08); + padding: 7px 8px; +} +.phone-stat-val { font-size: 13px; font-weight: 800; color: #fff; line-height: 1; } +.phone-stat-lbl { font-size: 8px; color: rgba(255,255,255,0.4); margin-top: 2px; } +.phone-list-item { + display: flex; align-items: center; gap: 7px; + padding: 7px 8px; border-radius: 8px; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(255,255,255,0.06); +} +.phone-list-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } +.phone-list-lines { flex: 1; display: flex; flex-direction: column; gap: 3px; } +.phone-list-line { height: 5px; border-radius: 3px; background: rgba(255,255,255,0.15); } +.phone-list-badge { + font-size: 8px; font-weight: 700; border-radius: 4px; + padding: 1px 5px; flex-shrink: 0; +} +.phone-chart { display: flex; align-items: flex-end; gap: 4px; height: 40px; padding: 0 2px; } +.phone-bar { flex: 1; border-radius: 3px 3px 0 0; } +.phone-footer { + flex-shrink: 0; padding: 8px 10px; + border-top: 1px solid rgba(255,255,255,0.06); + display: flex; gap: 5px; +} +.phone-btn { + flex: 1; height: 26px; border-radius: 7px; + display: grid; place-items: center; + font-size: 9px; font-weight: 700; color: #fff; +} +.phone-label { display: flex; flex-direction: column; align-items: center; gap: 5px; } +.phone-name { font-size: 12px; font-weight: 600; text-align: center; color: var(--text-primary); } +.phone-type { font-size: 10px; font-weight: 600; border: 1px solid; border-radius: 20px; padding: 1px 8px; } /* ============ Home Footer ============ */ .home-footer { border-top: 1px solid var(--border); padding: 40px 0; background: var(--bg-surface); }