feat: 솔루션 포털 홈페이지 이식 + 솔루션 소개 최신화 + 통합 메신저앱 QR 페이지 추가
This commit is contained in:
parent
6d2d6840d9
commit
0c5436291c
@ -38,6 +38,7 @@ import AiTools from './pages/AiTools'
|
|||||||
import UserManagement from './pages/UserManagement'
|
import UserManagement from './pages/UserManagement'
|
||||||
import AuditLog from './pages/AuditLog'
|
import AuditLog from './pages/AuditLog'
|
||||||
import SystemSettings from './pages/SystemSettings'
|
import SystemSettings from './pages/SystemSettings'
|
||||||
|
import MobileApp from './pages/MobileApp'
|
||||||
// 업무 (UIWS 이식) — 업무일지/일정/쪽지/업무통계
|
// 업무 (UIWS 이식) — 업무일지/일정/쪽지/업무통계
|
||||||
import UiwsWorklog from './pages/uiws/WorklogList'
|
import UiwsWorklog from './pages/uiws/WorklogList'
|
||||||
import UiwsSchedule from './pages/uiws/ScheduleCalendar'
|
import UiwsSchedule from './pages/uiws/ScheduleCalendar'
|
||||||
@ -102,6 +103,7 @@ export default function App() {
|
|||||||
<Route path="/admin/settings" element={
|
<Route path="/admin/settings" element={
|
||||||
<ProtectedRoute min="MANAGER"><SystemSettings /></ProtectedRoute>
|
<ProtectedRoute min="MANAGER"><SystemSettings /></ProtectedRoute>
|
||||||
} />
|
} />
|
||||||
|
<Route path="/admin/mobile-app" element={<MobileApp />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
<Route path="*" element={<Navigate to="/dashboard" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
Gauge, Network, CalendarRange, PackagePlus, Truck, Boxes, ClipboardCheck, Barcode,
|
Gauge, Network, CalendarRange, PackagePlus, Truck, Boxes, ClipboardCheck, Barcode,
|
||||||
ScanSearch, Ruler, AlertTriangle, ListChecks, LineChart, FileBadge, Package, ListTree,
|
ScanSearch, Ruler, AlertTriangle, ListChecks, LineChart, FileBadge, Package, ListTree,
|
||||||
Route as RouteIcon, Building2, Warehouse, BarChart3, Sparkles, ShieldCheck, ScrollText,
|
Route as RouteIcon, Building2, Warehouse, BarChart3, Sparkles, ShieldCheck, ScrollText,
|
||||||
Settings, Cpu, NotebookPen, CalendarDays, Mail, PieChart,
|
Settings, Cpu, NotebookPen, CalendarDays, Mail, PieChart, Smartphone,
|
||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { currentRole, hasRole } from './rbac'
|
import { currentRole, hasRole } from './rbac'
|
||||||
import ThemeToggle from './uiws/ThemeToggle'
|
import ThemeToggle from './uiws/ThemeToggle'
|
||||||
@ -63,6 +63,10 @@ const adminLinks: Link[] = [
|
|||||||
{ to: '/admin/audit', label: '감사 로그', icon: ScrollText, min: 'MANAGER' },
|
{ to: '/admin/audit', label: '감사 로그', icon: ScrollText, min: 'MANAGER' },
|
||||||
{ to: '/admin/settings', label: '시스템 설정', icon: Settings, min: 'MANAGER' },
|
{ to: '/admin/settings', label: '시스템 설정', icon: Settings, min: 'MANAGER' },
|
||||||
]
|
]
|
||||||
|
// 통합 메신저 앱(읽기전용 QR 설치) — 인증 사용자 전체
|
||||||
|
const mobileLinks: Link[] = [
|
||||||
|
{ to: '/admin/mobile-app', label: '모바일 앱 설치', icon: Smartphone },
|
||||||
|
]
|
||||||
|
|
||||||
const linkClass = ({ isActive }: { isActive: boolean }) =>
|
const linkClass = ({ isActive }: { isActive: boolean }) =>
|
||||||
`flex items-center gap-3 px-5 py-1.5 text-sm transition-colors ${
|
`flex items-center gap-3 px-5 py-1.5 text-sm transition-colors ${
|
||||||
@ -120,6 +124,9 @@ export default function Sidebar() {
|
|||||||
<Section title="기준정보" links={masterLinks} role={role} />
|
<Section title="기준정보" links={masterLinks} role={role} />
|
||||||
<Section title="공통" links={commonLinks} role={role} />
|
<Section title="공통" links={commonLinks} role={role} />
|
||||||
<Section title="업무 (UIWS)" links={uiwsLinks} role={role} />
|
<Section title="업무 (UIWS)" links={uiwsLinks} role={role} />
|
||||||
|
<div className="border-t border-edge mt-3">
|
||||||
|
<Section title="모바일" links={mobileLinks} role={role} />
|
||||||
|
</div>
|
||||||
{visibleAdmin.length > 0 && (
|
{visibleAdmin.length > 0 && (
|
||||||
<div className="border-t border-edge mt-3">
|
<div className="border-t border-edge mt-3">
|
||||||
<Section title="관리자" links={visibleAdmin} role={role} />
|
<Section title="관리자" links={visibleAdmin} role={role} />
|
||||||
|
|||||||
123
frontend/src/pages/MobileApp.tsx
Normal file
123
frontend/src/pages/MobileApp.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user