feat: 솔루션 포털 홈페이지 이식 + 솔루션 소개 최신화 + 통합 메신저앱 QR 페이지 추가

This commit is contained in:
GUARDiA 2026-06-21 10:28:17 +09:00
parent 95f8106f82
commit bb6bc69c68
3 changed files with 127 additions and 1 deletions

View File

@ -18,6 +18,7 @@ import TagBindingList from './pages/TagBindingList'
import UpdateQueueList from './pages/UpdateQueueList'
import AuditLog from './pages/AuditLog'
import SystemSettings from './pages/SystemSettings'
import MobileApp from './pages/MobileApp'
import WorklogList from './pages/uiws/WorklogList'
import ScheduleCalendar from './pages/uiws/ScheduleCalendar'
import MessageBox from './pages/uiws/MessageBox'
@ -52,6 +53,7 @@ export default function App() {
<Route path="/schedules" element={<ScheduleCalendar />} />
<Route path="/messages" element={<MessageBox />} />
<Route path="/work-stats" element={<StatsPivot />} />
<Route path="/mobile-app" element={<MobileApp />} />
</Route>
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>

View File

@ -3,7 +3,7 @@ import {
LayoutDashboard, Store, FileText, RefreshCw,
Bell, Cpu, ClipboardList, Zap, Users, Package, Building2, Brain,
Link2, ListOrdered, NotebookPen, CalendarDays, Mail, BarChart3,
ScrollText, Settings
ScrollText, Settings, Smartphone
} from 'lucide-react'
const nav = [
@ -27,6 +27,7 @@ const nav = [
{ to: '/schedules', icon: CalendarDays, label: '일정 관리' },
{ to: '/messages', icon: Mail, label: '쪽지' },
{ to: '/work-stats', icon: BarChart3, label: '업무 통계' },
{ to: '/mobile-app', icon: Smartphone, label: '모바일 앱 설치' },
]
export default function Sidebar() {

View File

@ -0,0 +1,123 @@
import { useEffect, useState } from 'react'
import { Smartphone, Download, Copy, Check, ExternalLink, RefreshCw } from 'lucide-react'
// 중앙 APK 저장소(ITSM)의 공개 엔드포인트 — 인증 불필요·CORS 허용 (읽기전용)
// 업로드·버전 관리는 GUARDiA Manager/ITSM에서 일원화. 본 화면은 표시 전용.
const ITSM_APP_BASE = 'https://zioinfo.co.kr:8443'
interface AppInfo {
has_version: boolean
app_name?: string
version?: string
platform?: string
file_size_mb?: number
release_notes?: string
download_count?: number
qr_url?: string
landing_url?: string
download_url?: string
updated_at?: string
}
export default function MobileApp() {
const [info, setInfo] = useState<AppInfo | null>(null)
const [loading, setLoading] = useState(true)
const [err, setErr] = useState('')
const [copied, setCopied] = useState(false)
const load = () => {
setLoading(true); setErr('')
fetch(`${ITSM_APP_BASE}/api/app/public-latest`)
.then(r => r.json())
.then((d: AppInfo) => setInfo(d))
.catch(() => setErr('버전 정보를 불러올 수 없습니다. (중앙 앱 저장소 연결 실패)'))
.finally(() => setLoading(false))
}
useEffect(() => { load() }, [])
const copyLink = async () => {
if (!info?.landing_url) return
try {
await navigator.clipboard.writeText(info.landing_url)
setCopied(true); setTimeout(() => setCopied(false), 2000)
} catch { /* clipboard 권한 없을 때 무시 */ }
}
return (
<div>
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<Smartphone className="text-brand" size={22} />
<h1 className="text-xl font-bold">GUARDiA </h1>
</div>
<button onClick={load} className="flex items-center gap-1.5 px-3 py-2 rounded-lg bg-card border border-edge text-sm text-slate-300 hover:text-brand">
<RefreshCw size={15} />
</button>
</div>
<p className="text-sm text-slate-400 mb-6">
QR <span className="text-brand font-semibold">GUARDiA Messenger</span> .
, · GUARDiA Manager에서 .
</p>
{loading && <div className="text-slate-400 text-sm"> </div>}
{err && <div className="bg-card border border-edge rounded-xl p-6 text-rose-400 text-sm">{err}</div>}
{!loading && !err && info && !info.has_version && (
<div className="bg-card border border-edge rounded-xl p-8 text-center text-slate-400">
<Smartphone size={36} className="mx-auto mb-3 text-slate-600" />
.<br />
<span className="text-xs">GUARDiA Manager에서 APK를 QR이 .</span>
</div>
)}
{!loading && !err && info?.has_version && (
<div className="bg-card border border-edge rounded-xl p-6 grid md:grid-cols-[200px_1fr] gap-6 items-start">
<div className="bg-white rounded-xl p-3 flex items-center justify-center">
<img src={info.qr_url} alt="앱 설치 QR" className="w-44 h-44" />
</div>
<div>
<div className="flex items-center gap-2 mb-1">
<span className="text-lg font-bold">{info.app_name || 'GUARDiA Messenger'}</span>
<span className="px-2 py-0.5 rounded-md bg-brand2 text-white text-xs font-semibold">v{info.version}</span>
</div>
<div className="text-xs text-slate-400 mb-4">
{info.platform} · {info.file_size_mb ? `${info.file_size_mb}MB` : ''}
{info.download_count != null && ` · 다운로드 ${info.download_count}`}
</div>
{info.release_notes && (
<div className="mb-4">
<div className="text-[11px] font-semibold text-slate-500 uppercase tracking-wider mb-1"></div>
<div className="text-sm text-slate-300 whitespace-pre-line bg-ink border border-edge rounded-lg p-3 max-h-32 overflow-auto">
{info.release_notes}
</div>
</div>
)}
<div className="flex flex-wrap gap-2">
<a href={info.landing_url} target="_blank" rel="noreferrer"
className="flex items-center gap-1.5 px-3 py-2 rounded-lg bg-brand text-ink text-sm font-semibold">
<ExternalLink size={15} />
</a>
<a href={info.download_url} target="_blank" rel="noreferrer"
className="flex items-center gap-1.5 px-3 py-2 rounded-lg bg-card border border-edge text-sm text-slate-300 hover:text-brand">
<Download size={15} /> APK
</a>
<button onClick={copyLink}
className="flex items-center gap-1.5 px-3 py-2 rounded-lg bg-card border border-edge text-sm text-slate-300 hover:text-brand">
{copied ? <Check size={15} className="text-accent" /> : <Copy size={15} />}
{copied ? '복사됨' : '다운로드 링크 복사'}
</button>
</div>
<div className="mt-4 text-[12px] text-slate-500 leading-relaxed">
📱 Android: APK "알 수 없는 소스" .
.
</div>
</div>
</div>
)}
</div>
)
}