Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,093 changes: 1,092 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
"preview": "vite preview"
},
"dependencies": {
"@radix-ui/react-collapsible": "^1.1.11",
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"antd": "^5.26.1",
"axios": "^1.6.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.0.0",
Expand Down
32 changes: 28 additions & 4 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import { Routes, Route, Navigate } from 'react-router-dom'
import { Routes, Route} from 'react-router-dom'
import LoginPage from './app/Authentication/LoginPage';
import RegisterPage from './app/Authentication/RegisterPage';
import CourseListPage from './app/Common/CourseListPage'
import CourseDetail from './components/sections/course-detail';
import CourseDetail from './components/sections/common/course-detail';
import HomePage from './app/page';
import ChatbotPage from './app/Common/ChatBot';
import SlotSkills from './components/sections/slot-skills';
import Vocabulary from './components/sections/vocabulary';
import SlotSkills from './components/sections/common/slot-skills';
import Vocabulary from './components/sections/common/vocabulary';
import JapaneseLearningPage from './components/sections/common/japanese-learning-page';
import Dashboard from './app/Admin/Dashboard';
import StaffViewListCourse from './app/Staff/ViewListCourse';
import AddNewCourse from './app/Staff/AddNewCourse';
import ViewCourseDetail from './app/Staff/CourseDetail';
import AddNewChapter from './app/Staff/AddNewChapter';
import AddNewUnit from './app/Staff/AddNewUnit';
import { FeedbackManagerPage } from './components/sections/staff/feedback-manager-page';
import SimpleVerify from './components/auth/Verify';
import ChangePassPage from './app/Authentication/ChangePassPage';
import FogotPassPage from './app/Authentication/FogotPassPage';
import ResetPassPage from './app/Authentication/ResetPassPage';

function App() {
const isAuthenticated = false;
Expand All @@ -17,11 +29,23 @@ function App() {
{/* Thay vì AuthPage, bạn có thể render LoginPage hoặc một component khác */}
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route path="/verify" element={<SimpleVerify />} />
<Route path="/courselist" element={<CourseListPage />} />
<Route path="/coursedetail/:id" element={<CourseDetail />} />
<Route path="/chatbot" element={<ChatbotPage />} />
<Route path="/slot/:id" element={<SlotSkills />} />
<Route path="/vocabulary" element={<Vocabulary />} />
<Route path="/materials/slot/:id" element={<JapaneseLearningPage />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/viewlistcourse" element={<StaffViewListCourse />} />
<Route path="/addnew" element={<AddNewCourse />} />
<Route path="/detail/:courseId" element={<ViewCourseDetail />} />
<Route path="/addchapter/:courseId" element={<AddNewChapter />} />
<Route path="/addunit/:courseId/:chapterId" element={<AddNewUnit />} />
<Route path="/feedback" element={<FeedbackManagerPage />} />
<Route path="/changepass" element={<ChangePassPage />} />
<Route path="/fogotpass" element={<FogotPassPage />} />
<Route path="/resetpass" element={<ResetPassPage />} />
<Route path="*" element={<HomePage />} />
</Routes>
)
Expand Down
65 changes: 65 additions & 0 deletions src/app/Admin/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import AutoLayout from "@/components/layout/AutoLayout";
import { CourseListPage } from "@/components/sections/staff/course-list";
import { Subject } from "../../components/sections/entity";

// Dữ liệu mock để test
const mockCourses: Subject[] = [
{
id: 1,
title: "Tiếng Nhật sơ cấp N5",
topic: "Ngôn ngữ",
description: "Học Hiragana, Katakana và ngữ pháp cơ bản.",
level: "Sơ cấp",
estimatedDuration: "6 tuần",
creatorId: "admin123",
image: "https://example.com/japanese-n5.jpg",
createdAt: "2025-06-01T10:00:00Z",
updatedAt: "2025-06-20T15:30:00Z",
status: "Published",
orderNumber: 1,
studentCount: 1200,
lessonCount: 24,
rating: 4.7,
chapters: [],
},
{
id: 2,
title: "Luyện giao tiếp tiếng Nhật N5",
topic: "Giao tiếp",
description: "Rèn luyện kỹ năng hội thoại đơn giản trong cuộc sống hàng ngày.",
level: "Sơ cấp",
estimatedDuration: "4 tuần",
creatorId: "teacher001",
image: "https://example.com/conversation-n5.jpg",
createdAt: "2025-06-10T12:00:00Z",
updatedAt: "2025-06-21T08:30:00Z",
status: "Draft",
orderNumber: 2,
studentCount: 850,
lessonCount: 16,
rating: 4.5,
chapters: [],
}
];

const Dashboard: React.FC = () => {
const handleViewDetails = (course: Subject) => {
console.log("Chi tiết khóa học:", course.title);
};

const handleAddCourse = () => {
console.log("Thêm khóa học mới");
};

return (
<AutoLayout>
<CourseListPage
courses={mockCourses}
onViewDetails={handleViewDetails}
onAddCourse={handleAddCourse}
/>
</AutoLayout>
);
};

export default Dashboard;
11 changes: 11 additions & 0 deletions src/app/Authentication/ChangePassPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Header } from "@/components/layout/header"
import { ChangePasswordForm } from "@/components/auth/ChangePass-form"

export default function ChangePassPage() {
return (
<>
<Header/>
<ChangePasswordForm/>
</>
)
}
11 changes: 11 additions & 0 deletions src/app/Authentication/FogotPassPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Header } from "@/components/layout/header"
import { ForgotPasswordForm } from "@/components/auth/FogotPass-form"

export default function FogotPassPage() {
return (
<>
<Header/>
<ForgotPasswordForm/>
</>
)
}
11 changes: 11 additions & 0 deletions src/app/Authentication/ResetPassPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Header } from "@/components/layout/header"
import { ResetPasswordForm } from "@/components/auth/ResetPass-form"

export default function ResetPassPage() {
return (
<>
<Header/>
<ResetPasswordForm/>
</>
)
}
2 changes: 1 addition & 1 deletion src/app/Common/ChatBot.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Header } from "@/components/layout/header"
import Chatbot from "@/components/sections/chatbot"
import Chatbot from "@/components/sections/common/chatbot"

export default function ChatbotPage() {
return (
Expand Down
2 changes: 1 addition & 1 deletion src/app/Common/CourseListPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import JapaneseCourseList from "@/components/sections/japanese-course-list"
import JapaneseCourseList from "@/components/sections/common/japanese-course-list"
import { Header } from "@/components/layout/header"

export default function Page() {
Expand Down
34 changes: 34 additions & 0 deletions src/app/Common/DynamicBreadcrumb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react";
import { Breadcrumb } from "antd";
import { Link, useLocation } from "react-router-dom";

// Helper function to capitalize and transform path segments
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);

const DynamicBreadcrumb: React.FC = () => {
const location = useLocation();
const pathSnippets = location.pathname.split("/").filter((i) => i);

// Build the breadcrumb items dynamically based on the URL
const breadcrumbItems = [
...pathSnippets
.filter((segment) => segment.toLowerCase() !== "admin" && segment.toLowerCase() !== "organization")
.map((segment, index) => {
const url = `/${pathSnippets.slice(0, index + 1).join("/")}`;
const isLast = index === pathSnippets.length - 1;

const name = isNaN(Number(segment))
? capitalize(segment)
: `${segment}`;

return {
key: url,
title: isLast ? name : <Link to={url}>{name}</Link>,
};
}),
];

return <Breadcrumb style={{ margin: "16px 0" }} items={breadcrumbItems} />;
};

export default DynamicBreadcrumb;
64 changes: 64 additions & 0 deletions src/app/Staff/AddNewChapter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import AutoLayout from "@/components/layout/AutoLayout";
import { AddChapterPage } from "@/components/sections/staff/add-chapter-page";
import { CreateChapterDTO, Subject } from "@/components/sections/entity";
import { useAPI } from "@/hooks";
import { useNavigate, useParams } from "react-router-dom";
import { useEffect, useState } from "react";
import URLMapping from "@/utils/URLMapping";

const AddNewChapter: React.FC = () => {
const { API } = useAPI();
const navigate = useNavigate();
const { courseId } = useParams();
const [course, setCourse] = useState<Subject | null>(null);
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)

useEffect(() => {
const loadData = async () => {
try {
if (!course && courseId) {
const response = await API.get(URLMapping.SUBJECT_DETAIL + `/${courseId}`);
setCourse(response); // Gán course lấy từ API
}
} catch (err) {
setError("Không thể tải dữ liệu khóa học.");
} finally {
setLoading(false);
}
};
loadData();
}, [course, courseId]);

const handleBack = () => {
navigate(-1); // hoặc navigate(`/admin/subjects/${subjectId}`);
};

const handleCreateChapter = async (chapterData: CreateChapterDTO) => {
try {
console.log("Dữ liệu tạo chapter:", chapterData);
const response = await API.post(URLMapping.CHAPTER_CREATE, chapterData);
navigate(`/detail/${courseId}`, { state: { course } }); // quay lại chi tiết subject
} catch (error) {
console.error("Tạo chương thất bại:", error);
alert("Tạo chương thất bại.");
}
};

if (!course) {
return <p>Đang tải thông tin môn học...</p>;
} else {
return (
<AutoLayout>
<AddChapterPage
course={course}
onBack={handleBack}
onCreateChapter={handleCreateChapter}
/>
</AutoLayout>
);
}

};

export default AddNewChapter;
39 changes: 39 additions & 0 deletions src/app/Staff/AddNewCourse.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import AutoLayout from "@/components/layout/AutoLayout";
import { CreateCoursePage } from "@/components/sections/staff/create-course-page";
import { Subject } from "@/components/sections/entity";
import { useNavigate } from "react-router-dom";
import { useAPI } from "@/hooks"; // Giả sử bạn đã có useAPI hook
import URLMapping from "@/utils/URLMapping";

const AddNewCourse: React.FC = () => {
const navigate = useNavigate();
const { API } = useAPI();

const handleBack = () => {
navigate(-1); // hoặc navigate("/admin/subjects");
};

const handleCreateCourse = async (
courseData: Omit<Subject, "id" | "createdAt" | "updatedAt" | "chapters">
) => {
try {
const response = await API.post(URLMapping.SUBJECT_CREATE, courseData);
console.log("Tạo môn học thành công:", response.data);
navigate("/viewlistcourse"); // hoặc tới trang chi tiết
} catch (error) {
console.error("Tạo môn học thất bại:", error);
alert("Đã xảy ra lỗi khi tạo môn học.");
}
};

return (
<AutoLayout>
<CreateCoursePage
onBack={handleBack}
onCreateCourse={handleCreateCourse}
/>
</AutoLayout>
);
};

export default AddNewCourse;
Loading