feat: 솔루션 포털 홈페이지 이식 + 솔루션 소개 최신화 + 통합 메신저앱 QR 페이지 추가
This commit is contained in:
parent
b63a01acd1
commit
cc6e3b2c2a
@ -22,6 +22,7 @@ import AiTools from './pages/AiTools'
|
||||
import UserManagement from './pages/UserManagement'
|
||||
import AuditLog from './pages/AuditLog'
|
||||
import SystemSettings from './pages/SystemSettings'
|
||||
import MobileApp from './pages/MobileApp'
|
||||
// UIWS 이식 업무 모듈(업무일지·일정·쪽지·통계) — 기존 라우트 보존, 신규 메뉴만 추가
|
||||
import UiwsLayout from './components/uiws/UiwsLayout'
|
||||
import WorklogList from './pages/uiws/WorklogList'
|
||||
@ -64,6 +65,7 @@ export default function App() {
|
||||
<Route path="/admin/settings" element={
|
||||
<ProtectedRoute min="EDITOR"><SystemSettings /></ProtectedRoute>
|
||||
} />
|
||||
<Route path="/admin/mobile-app" element={<MobileApp />} />
|
||||
|
||||
{/* 업무(UIWS 이식) — 인증 사용자 전체(Viewer+). UiwsLayout = .uiws-scope 다크/라이트 토큰 */}
|
||||
<Route path="/uiws/worklog" element={<UiwsLayout><WorklogList /></UiwsLayout>} />
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
LayoutDashboard, FileText, Image, Package, Megaphone, ListTree, Palette,
|
||||
Languages, Search, FileInput, MessageSquareWarning, Share2, UsersRound,
|
||||
Sparkles, BarChart3, ShieldCheck, ScrollText, Settings, FileStack,
|
||||
BookOpen, CalendarDays, Mail,
|
||||
BookOpen, CalendarDays, Mail, Smartphone,
|
||||
} from 'lucide-react'
|
||||
import { currentRole, hasRole } from './rbac'
|
||||
import ThemeToggle from './uiws/ThemeToggle'
|
||||
@ -44,6 +44,10 @@ const uiwsLinks: Link[] = [
|
||||
{ to: '/uiws/message', label: '쪽지', icon: Mail },
|
||||
{ to: '/uiws/stats', label: '업무통계', icon: BarChart3 },
|
||||
]
|
||||
// 통합 메신저 앱(읽기전용 QR 설치) — 인증 사용자 전체
|
||||
const appLinks: Link[] = [
|
||||
{ to: '/admin/mobile-app', label: '모바일 앱 설치', icon: Smartphone },
|
||||
]
|
||||
|
||||
const linkClass = ({ isActive }: { isActive: boolean }) =>
|
||||
`flex items-center gap-3 px-5 py-2 text-sm transition-colors ${
|
||||
@ -98,6 +102,9 @@ export default function Sidebar() {
|
||||
<div className="border-t border-edge mt-3">
|
||||
<Section title="업무(UIWS)" links={uiwsLinks} />
|
||||
</div>
|
||||
<div className="border-t border-edge mt-3">
|
||||
<Section title="모바일" links={appLinks} />
|
||||
</div>
|
||||
{visibleAdmin.length > 0 && (
|
||||
<div className="border-t border-edge mt-3">
|
||||
<Section title="관리자" links={visibleAdmin} />
|
||||
|
||||
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