From 7a9c7a93ff4a8df5a6ebb82deab30b4edfcef898 Mon Sep 17 00:00:00 2001 From: PhuocNTB Date: Wed, 1 Jul 2026 15:04:56 +0700 Subject: [PATCH] fix --- landing_page/index.html | 4 +- management/src/App.tsx | 2 +- management/src/components/ApplicationsTab.tsx | 616 +++++++++++++----- management/src/components/DashboardTab.tsx | 246 +------ 4 files changed, 463 insertions(+), 405 deletions(-) diff --git a/landing_page/index.html b/landing_page/index.html index 64b35d3..2722ffb 100644 --- a/landing_page/index.html +++ b/landing_page/index.html @@ -299,7 +299,7 @@ return ``; } - const host = window.location.origin; + const host = 'https://sv.rikkeiraia.org'; async function loadApps() { const loader = document.getElementById('apps-loading'); @@ -331,7 +331,7 @@
${app.description || 'Hỗ trợ quá trình kiểm tra và học tập.'}
- ${app.platform} + ${app.platform === 'macOS' ? 'MacOS' : app.platform} diff --git a/management/src/App.tsx b/management/src/App.tsx index 738cdbb..6235cc6 100644 --- a/management/src/App.tsx +++ b/management/src/App.tsx @@ -184,7 +184,7 @@ function App() { onClick={() => setActiveTab('dashboard')} > - Tổng quan + Tổng quan & Tài Liệu diff --git a/management/src/components/ApplicationsTab.tsx b/management/src/components/ApplicationsTab.tsx index e50268a..0986717 100644 --- a/management/src/components/ApplicationsTab.tsx +++ b/management/src/components/ApplicationsTab.tsx @@ -1,9 +1,10 @@ import React, { useEffect, useState } from 'react'; -import { api, type AppDownloadItem } from '../api'; +import { api, type AppDownloadItem, type GuideLinkItem } from '../api'; import { useAuth } from '../auth/AuthContext'; +// Icons const IconDownload = () => ( - + @@ -46,75 +47,110 @@ export const ApplicationsTab: React.FC = () => { const { staff } = useAuth(); const isSuperAdmin = staff?.email === 'phuocntb@rikkeiacademy.com'; - const [apps, setApps] = useState([]); - const [loading, setLoading] = useState(true); + const [activeSubTab, setActiveSubTab] = useState<'apps' | 'guides'>('apps'); - // Modal states - const [showModal, setShowModal] = useState(false); + // Apps state + const [apps, setApps] = useState([]); + const [appsLoading, setAppsLoading] = useState(true); + + // Guides state + const [guides, setGuides] = useState([]); + const [guidesLoading, setGuidesLoading] = useState(true); + + // App Modal states + const [showAppModal, setShowAppModal] = useState(false); const [editingApp, setEditingApp] = useState(null); - const [name, setName] = useState(''); - const [description, setDescription] = useState(''); - const [downloadUrl, setDownloadUrl] = useState(''); - const [platform, setPlatform] = useState('Windows'); - const [saving, setSaving] = useState(false); + const [appName, setAppName] = useState(''); + const [appDescription, setAppDescription] = useState(''); + const [appDownloadUrl, setAppDownloadUrl] = useState(''); + const [appPlatform, setAppPlatform] = useState('Windows'); + const [appSaving, setAppSaving] = useState(false); + + // Guide Modal states + const [showGuideModal, setShowGuideModal] = useState(false); + const [editingGuide, setEditingGuide] = useState(null); + const [guideTitle, setGuideTitle] = useState(''); + const [guideUrl, setGuideUrl] = useState(''); + const [guideSaving, setGuideSaving] = useState(false); const fetchApps = async () => { try { - setLoading(true); + setAppsLoading(true); const res = await api.listAppDownloads(); setApps(res.data || []); } catch (err) { console.error('Không thể tải danh sách ứng dụng:', err); } finally { - setLoading(false); + setAppsLoading(false); + } + }; + + const fetchGuides = async () => { + try { + setGuidesLoading(true); + const res = await api.listGuideLinks(); + setGuides(res.data || []); + } catch (err) { + console.error('Không thể tải danh sách hướng dẫn:', err); + } finally { + setGuidesLoading(false); } }; useEffect(() => { fetchApps(); + fetchGuides(); }, []); - const handleOpenAdd = () => { + // App Modal Handlers + const handleOpenAddApp = () => { setEditingApp(null); - setName(''); - setDescription(''); - setDownloadUrl(''); - setPlatform('Windows'); - setShowModal(true); + setAppName(''); + setAppDescription(''); + setAppDownloadUrl(''); + setAppPlatform('Windows'); + setShowAppModal(true); }; - const handleOpenEdit = (app: AppDownloadItem) => { + const handleOpenEditApp = (app: AppDownloadItem) => { setEditingApp(app); - setName(app.name); - setDescription(app.description); - setDownloadUrl(app.downloadUrl); - setPlatform(app.platform); - setShowModal(true); + setAppName(app.name); + setAppDescription(app.description); + setAppDownloadUrl(app.downloadUrl); + // Standardize representation to 'MacOS' + setAppPlatform(app.platform === 'macOS' ? 'MacOS' : app.platform); + setShowAppModal(true); }; - const handleSave = async () => { - if (!name.trim() || !downloadUrl.trim()) { + const handleSaveApp = async () => { + if (!appName.trim() || !appDownloadUrl.trim()) { alert('Vui lòng nhập tên ứng dụng và đường dẫn tải.'); return; } + // Force write first letters uppercase + let finalPlatform = appPlatform; + if (finalPlatform.toLowerCase() === 'macos') { + finalPlatform = 'MacOS'; + } + try { - setSaving(true); + setAppSaving(true); if (editingApp) { - await api.updateAppDownload(editingApp.id, name.trim(), description.trim(), downloadUrl.trim(), platform); + await api.updateAppDownload(editingApp.id, appName.trim(), appDescription.trim(), appDownloadUrl.trim(), finalPlatform); } else { - await api.createAppDownload(name.trim(), description.trim(), downloadUrl.trim(), platform); + await api.createAppDownload(appName.trim(), appDescription.trim(), appDownloadUrl.trim(), finalPlatform); } - setShowModal(false); + setShowAppModal(false); await fetchApps(); } catch (err: any) { alert(err.message || 'Lưu ứng dụng thất bại'); } finally { - setSaving(false); + setAppSaving(false); } }; - const handleDelete = async (id: number, appName: string) => { - if (window.confirm(`Bạn có chắc chắn muốn xóa liên kết tải cho ứng dụng "${appName}" không?`)) { + const handleDeleteApp = async (id: number, name: string) => { + if (window.confirm(`Bạn có chắc chắn muốn xóa liên kết tải cho ứng dụng "${name}" không?`)) { try { await api.deleteAppDownload(id); await fetchApps(); @@ -124,139 +160,338 @@ export const ApplicationsTab: React.FC = () => { } }; + // Guide Modal Handlers + const handleOpenAddGuide = () => { + setEditingGuide(null); + setGuideTitle(''); + setGuideUrl(''); + setShowGuideModal(true); + }; + + const handleOpenEditGuide = (guide: GuideLinkItem) => { + setEditingGuide(guide); + setGuideTitle(guide.title); + setGuideUrl(guide.url); + setShowGuideModal(true); + }; + + const handleSaveGuide = async () => { + if (!guideTitle.trim() || !guideUrl.trim()) { + alert('Vui lòng điền đầy đủ tiêu đề và đường dẫn hướng dẫn'); + return; + } + try { + setGuideSaving(true); + if (editingGuide) { + await api.updateGuideLink(editingGuide.id, guideTitle.trim(), guideUrl.trim()); + } else { + await api.createGuideLink(guideTitle.trim(), guideUrl.trim()); + } + setShowGuideModal(false); + await fetchGuides(); + } catch (err: any) { + alert(err.message || 'Lưu hướng dẫn thất bại'); + } finally { + setGuideSaving(false); + } + }; + + const handleDeleteGuide = async (id: number, title: string) => { + if (window.confirm(`Bạn có chắc chắn muốn xóa tài liệu hướng dẫn "${title}"?`)) { + try { + await api.deleteGuideLink(id); + await fetchGuides(); + } catch (err: any) { + alert(err.message || 'Xóa hướng dẫn thất bại'); + } + } + }; + return (
-

Quản Lý Ứng Dụng

-

Quản lý link tải các công cụ học tập hỗ trợ sinh viên.

+

Ứng Dụng & Hướng Dẫn

+

Quản lý các công cụ, link tải ứng dụng và các tài liệu hướng dẫn cho sinh viên.

{isSuperAdmin && ( - +
+ {activeSubTab === 'apps' ? ( + + ) : ( + + )} +
)}
-
-
- {loading && ( -
-
-

Đang tải danh sách ứng dụng...

-
- )} - - {!loading && apps.length === 0 && ( -
- - - - -

Chưa có ứng dụng nào được cấu hình tải.

- {isSuperAdmin && ( - - )} -
- )} - - {!loading && apps.length > 0 && ( -
- - - - - - - - {isSuperAdmin && } - - - - {apps.map((app) => ( - - - - - - {isSuperAdmin && ( - - )} - - ))} - -
Nền tảngỨng dụngMô tảĐường dẫn tảiThao tác
-
- - {app.platform} -
-
- {app.name} - - {app.description || '—'} - - e.currentTarget.style.textDecoration = 'underline'} - onMouseLeave={(e) => e.currentTarget.style.textDecoration = 'none'} - > - - Tải ứng dụng - - -
- - -
-
-
- )} + {/* Sub-tab Switch Header */} +
+
+ +
- {showModal && ( -
setShowModal(false)}> +
+ {activeSubTab === 'apps' && ( +
+ {appsLoading && ( +
+
+

Đang tải danh sách ứng dụng...

+
+ )} + + {!appsLoading && apps.length === 0 && ( +
+ + + + +

Chưa có ứng dụng nào được cấu hình tải.

+ {isSuperAdmin && ( + + )} +
+ )} + + {!appsLoading && apps.length > 0 && ( +
+ + + + + + + + {isSuperAdmin && } + + + + {apps.map((app) => ( + + + + + + {isSuperAdmin && ( + + )} + + ))} + +
Nền tảngỨng dụngMô tảĐường dẫn tảiThao tác
+
+ + + {app.platform === 'macOS' ? 'MacOS' : app.platform} + +
+
+ {app.name} + + {app.description || '—'} + + e.currentTarget.style.textDecoration = 'underline'} + onMouseLeave={(e) => e.currentTarget.style.textDecoration = 'none'} + > + + Tải ứng dụng + + +
+ + +
+
+
+ )} +
+ )} + + {activeSubTab === 'guides' && ( +
+ {guidesLoading && ( +
+
+

Đang tải danh sách tài liệu hướng dẫn...

+
+ )} + + {!guidesLoading && guides.length === 0 && ( +
+ + + + +

Chưa có tài liệu hướng dẫn nào.

+ {isSuperAdmin && ( + + )} +
+ )} + + {!guidesLoading && guides.length > 0 && ( +
+ + + + + + + {isSuperAdmin && } + + + + {guides.map((guide) => { + const isVideo = guide.url.toLowerCase().includes('youtube.com') || guide.url.toLowerCase().includes('youtu.be') || guide.url.toLowerCase().includes('drive.google.com/file'); + return ( + + + + + {isSuperAdmin && ( + + )} + + ); + })} + +
Loại tài liệuTiêu đề hướng dẫnĐường dẫn liên kếtThao tác
+ + {isVideo ? 'VIDEO' : 'TÀI LIỆU'} + + + {guide.title} + + e.currentTarget.style.textDecoration = 'underline'} + onMouseLeave={(e) => e.currentTarget.style.textDecoration = 'none'} + > + Xem liên kết → + + +
+ + +
+
+
+ )} +
+ )} +
+ + {/* App Modal */} + {showAppModal && ( +
setShowAppModal(false)}>
e.stopPropagation()}>

{editingApp ? 'Cập nhật ứng dụng' : 'Thêm ứng dụng tải'}

- +
-
+
- + +
+
+
+ )} + + {/* Guide Modal */} + {showGuideModal && ( +
setShowGuideModal(false)}> +
e.stopPropagation()}> +
+

{editingGuide ? 'Cập nhật hướng dẫn' : 'Thêm tài liệu hướng dẫn'}

+ +
+
+
+ + + +
+
+
+ +
diff --git a/management/src/components/DashboardTab.tsx b/management/src/components/DashboardTab.tsx index 656e39d..7b6710e 100644 --- a/management/src/components/DashboardTab.tsx +++ b/management/src/components/DashboardTab.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; -import { api, type GuideLinkItem } from '../api'; +import { api } from '../api'; import type { StatsResponse } from '../api'; -import { useAuth } from '../auth/AuthContext'; interface DashboardTabProps { onNavigate: (tab: string) => void; @@ -38,36 +37,10 @@ const IconAlert = () => ( ); export const DashboardTab: React.FC = ({ onNavigate }) => { - const { staff } = useAuth(); - const isSuperAdmin = staff?.email === 'phuocntb@rikkeiacademy.com'; - const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - // Guide links state - const [links, setLinks] = useState([]); - const [linksLoading, setLinksLoading] = useState(true); - - // Add/Edit modal state - const [showModal, setShowModal] = useState(false); - const [editingLink, setEditingLink] = useState(null); - const [modalTitle, setModalTitle] = useState(''); - const [modalUrl, setModalUrl] = useState(''); - const [modalSaving, setModalSaving] = useState(false); - - const fetchLinks = async () => { - try { - setLinksLoading(true); - const res = await api.listGuideLinks(); - setLinks(res.data || []); - } catch (err) { - console.error('Không thể tải tài liệu hướng dẫn:', err); - } finally { - setLinksLoading(false); - } - }; - useEffect(() => { const fetchStats = async () => { try { @@ -81,55 +54,8 @@ export const DashboardTab: React.FC = ({ onNavigate }) => { } }; fetchStats(); - fetchLinks(); }, []); - const handleOpenAddModal = () => { - setEditingLink(null); - setModalTitle(''); - setModalUrl(''); - setShowModal(true); - }; - - const handleOpenEditModal = (link: GuideLinkItem) => { - setEditingLink(link); - setModalTitle(link.title); - setModalUrl(link.url); - setShowModal(true); - }; - - const handleSaveLink = async () => { - if (!modalTitle.trim() || !modalUrl.trim()) { - alert('Vui lòng điền đầy đủ tiêu đề và đường dẫn'); - return; - } - try { - setModalSaving(true); - if (editingLink) { - await api.updateGuideLink(editingLink.id, modalTitle.trim(), modalUrl.trim()); - } else { - await api.createGuideLink(modalTitle.trim(), modalUrl.trim()); - } - setShowModal(false); - await fetchLinks(); - } catch (err: any) { - alert(err.message || 'Lưu tài liệu thất bại'); - } finally { - setModalSaving(false); - } - }; - - const handleDeleteLink = async (id: number, title: string) => { - if (window.confirm(`Bạn có chắc chắn muốn xóa tài liệu "${title}"?`)) { - try { - await api.deleteGuideLink(id); - await fetchLinks(); - } catch (err: any) { - alert(err.message || 'Xóa tài liệu thất bại'); - } - } - }; - return (
@@ -212,179 +138,9 @@ export const DashboardTab: React.FC = ({ onNavigate }) => {
- -
-
-

- - - - - - - Tài liệu hướng dẫn -

- {isSuperAdmin && ( - - )} -
- - {linksLoading && ( -
-
-
- )} - - {!linksLoading && links.length === 0 && ( -

Chưa có tài liệu hướng dẫn nào được cấu hình.

- )} - - {!linksLoading && links.length > 0 && ( -
- {links.map((link) => ( -
{ - e.currentTarget.style.borderColor = 'rgba(187, 33, 38, 0.25)'; - e.currentTarget.style.backgroundColor = '#fff'; - e.currentTarget.style.boxShadow = 'var(--shadow-sm)'; - }} - onMouseLeave={(e) => { - e.currentTarget.style.borderColor = 'var(--border-color)'; - e.currentTarget.style.backgroundColor = 'var(--bg-app)'; - e.currentTarget.style.boxShadow = 'none'; - }} - > - e.currentTarget.style.color = 'var(--accent)'} - onMouseLeave={(e) => e.currentTarget.style.color = 'var(--text-primary)'} - > - - - - - {link.title} - - {isSuperAdmin && ( -
- - -
- )} -
- ))} -
- )} -
)}
- - {showModal && ( -
setShowModal(false)}> -
e.stopPropagation()}> -
-

{editingLink ? 'Cập nhật tài liệu' : 'Thêm tài liệu hướng dẫn'}

- -
-
-
- - -
-
-
- - -
-
-
- )}
); };