Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
d893ab6
[FLN-133][Hoang][UI] Update UI for add course [07.10.2025] (#110)
hofang42 Oct 15, 2025
cdb86cd
[FLN-134][Duc][UI] Apply API for Adding Course [15.10.2025] (#111)
justduck25 Oct 15, 2025
a24a97b
[FLN-136][Hoang][UI] Add UI for instructor [15.10.2025] (#112)
hofang42 Oct 16, 2025
a943356
[FLN-143][Duc][UI] Apply Quiz API to WatchCourse [17.10.2025] (#113)
justduck25 Oct 17, 2025
3c0fc0c
[FLN-144] instructor registration (#114)
Ryuseikaiz Oct 17, 2025
9c09b15
[Fix] Remove biography & bank from instructor profile (#115)
Ryuseikaiz Oct 17, 2025
7bb966f
Refactor auth routes into with/without layout groups (#116)
Ryuseikaiz Oct 17, 2025
00e25df
[FLN-148][Hoang][UI] Add function verify instructor [19.10.2025] (#117)
hofang42 Oct 18, 2025
186c15b
[FLN][Duc][UI] Reapply APIs for instructor [18.10.2025] (#118)
justduck25 Oct 18, 2025
960990a
[FLN-130][Thien][UI] Implement PayOs payment code [18.10.2025] (#119)
Nomsociuu Oct 18, 2025
ef4ecf8
Add after payment logic and something else (#120)
Nomsociuu Oct 18, 2025
024446f
[FLN-156][Dat][FE] Instructor Profile Edit Page [19.10.2025] (#122)
Ryuseikaiz Oct 19, 2025
64898d1
[FLN-157][Dat][FE] Instructor Public Profile Page with Figma Design […
Ryuseikaiz Oct 19, 2025
f1e34ae
[FLN-158][Dat][FE] Instructor Info Card on Course Details [19.10.2025…
Ryuseikaiz Oct 19, 2025
26bdb15
[FLN-159][Dat][FE] Routing and Navigation with API Integration [19.10…
Ryuseikaiz Oct 19, 2025
2e820e3
[FLN-160][Dat][FE] Admin Censor Instructor and UI Polish [19.10.2025]…
Ryuseikaiz Oct 19, 2025
6ba68ea
[FLN-162][Thien][UI] Send welcome message when user enrolled an cours…
Nomsociuu Oct 19, 2025
17dddf4
[FLN-163][UI] Update Instructor Rating Display on Course Details Page…
Ryuseikaiz Oct 19, 2025
1f3362a
[FLN-161][Hoang][UI] Add censor course for admin [19.10.2025] (#129)
hofang42 Oct 19, 2025
39e58a8
[FLN-152][UI] Apply Discount API for Instructor (#121)
justduck25 Oct 19, 2025
329dfe5
[FLN-164][Thien][UI] Fix view top categories [19.10.2025] (#131)
Nomsociuu Oct 19, 2025
0339a45
[FLN-165][Duc][UI] Add Quiz Template, Fix API [19.10.2025] (#132)
justduck25 Oct 19, 2025
10ba428
[FLN-166][FLN-167][FE] Instructor Register Button & Active Course Fil…
Ryuseikaiz Oct 19, 2025
d60f7b1
Merge branch 'main' into FLearning
Ryuseikaiz Oct 19, 2025
99f7b84
[FLN-129][Hoang][UI] Add modal for AI explain [25.10.2025] (#135)
hofang42 Oct 27, 2025
3ec069b
[FLN-172][Duc][UI] Update Upload Quiz for Instructor [30.10.2025] (#136)
justduck25 Oct 29, 2025
5ddde36
[FLN-171][Duc][UI] Add AI summaries Video&Article [30.10.2025] (#137)
justduck25 Oct 30, 2025
b08949b
[FLN-176][Hoang][UI] Tạo Learning Path cho học viên bằng AI [11.01.20…
hofang42 Nov 1, 2025
7e6d7be
[FLN-170][Thien][UI] Auto creating cerficate [01.11.2025] (#139)
Nomsociuu Nov 1, 2025
3f06812
[UI][fix] real-time fetch certificate (#140)
Nomsociuu Nov 1, 2025
6d8ea37
[FLN-178][Thien][UI] Fix categories label icon and text size card [02…
Nomsociuu Nov 2, 2025
49e3821
[FLN-179][Thien][UI] Sync data in course detail page [02.11.2025] (#142)
Nomsociuu Nov 2, 2025
f69a8a8
[FLN-180][Duc][Bug] Fix quiz result saving [02.11.2025] (#143)
justduck25 Nov 2, 2025
5175b1f
[FLN-181][Hoang][UI] Update instructor dashboard [02.11.2025] (#144)
hofang42 Nov 2, 2025
eb30a93
[FLN-183][Thien][UI] Hide search bar in some screen avoid duplicate […
Nomsociuu Nov 2, 2025
a59b6f9
[FLN-185][Duc][UI] Sửa bug không fetch đúng data ở nhiều màn [02.11.2…
justduck25 Nov 2, 2025
1f25147
[FLN-174][UI] ai quiz generation (#148)
Ryuseikaiz Nov 2, 2025
1970e18
[FLN-186][Duc][UI] Update Instructor my course chart [03.11.2025] (#150)
justduck25 Nov 2, 2025
c664ffa
[FLN-175] [FE] Update ban/unban user (#151)
gianglamnguyen Nov 2, 2025
aa673e4
banned update (#152)
gianglamnguyen Nov 2, 2025
cca5c9c
[FLN-188][Duc][UI] Thêm prefix css [03.11.2025] (#153)
justduck25 Nov 2, 2025
44384a4
[FLN-189][FE] Fix quiz proctoring system violations and face detectio…
Ryuseikaiz Nov 2, 2025
84727ca
[FLN-190][Thien][API] Format Card detail attribute in Home Screen [03…
Nomsociuu Nov 3, 2025
e52aebb
FLN-191][Thien][UI] Enhance logic search bar and add to wishlist requ…
Nomsociuu Nov 3, 2025
aee06ba
[FLN-192][Thien][UI] Add Withdraw function for instructor [03.11.2025…
Nomsociuu Nov 3, 2025
c2033af
[FLN-175] [FE] AI for instructor's application (#159)
gianglamnguyen Nov 6, 2025
03ec9d5
[UI] Sửa Face Verification chỉ hiện 1 lần (#160)
Ryuseikaiz Nov 16, 2025
4526803
Fix modal handling on quiz completion and isActive logic (#161)
Ryuseikaiz Nov 16, 2025
6f94a6d
Make quiz lockout user-specific in QuizContent (#162)
Ryuseikaiz Nov 16, 2025
6e589b4
[FLN-195][Thien][UI] Add UI for Notification function[18.11.2025] (#164)
Nomsociuu Nov 18, 2025
e4b5eea
[FLN-194][Hoang][UI] Fix chat and AI [17.11.2025] (#163)
hofang42 Nov 18, 2025
3803815
[FLN-196][Duc][UI] Fix Student my course card, allow download summari…
justduck25 Nov 18, 2025
e9dd8b6
Merge branch 'main' into FLearning
justduck25 Nov 18, 2025
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
6 changes: 6 additions & 0 deletions FrontEnd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
"@fortawesome/react-fontawesome": "^0.2.2",
"@react-oauth/google": "^0.12.2",
"@reduxjs/toolkit": "^2.8.2",
"@tensorflow-models/blazeface": "^0.1.0",
"@tensorflow-models/coco-ssd": "^2.2.3",
"@tensorflow/tfjs": "^4.22.0",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
Expand All @@ -21,11 +24,14 @@
"bootstrap-icons": "^1.13.1",
"chart.js": "^4.4.9",
"dayjs": "^1.11.13",
"face-api.js": "^0.22.2",
"firebase": "^11.9.1",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.513.0",
"react": "^19.1.0",
"react-bootstrap-icons": "^1.11.6",
"react-chartjs-2": "^5.3.0",
"react-confetti": "^6.4.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"react-infinite-scroll-component": "^6.1.0",
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"weights":[{"name":"dense0/conv0/filters","shape":[3,3,3,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004853619781194949,"min":-0.5872879935245888}},{"name":"dense0/conv0/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004396426443960153,"min":-0.7298067896973853}},{"name":"dense0/conv1/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00635151559231328,"min":-0.5589333721235686}},{"name":"dense0/conv1/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009354315552057004,"min":-1.2628325995276957}},{"name":"dense0/conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0029380727048013726,"min":-0.5846764682554731}},{"name":"dense0/conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0049374802439820535,"min":-0.6171850304977566}},{"name":"dense0/conv2/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009941946758943446,"min":-1.3421628124573652}},{"name":"dense0/conv2/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030300481062309416,"min":-0.5272283704841838}},{"name":"dense0/conv3/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005672684837790097,"min":-0.7431217137505026}},{"name":"dense0/conv3/pointwise_filter","shape":[1,1,32,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010712201455060173,"min":-1.5639814124387852}},{"name":"dense0/conv3/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030966934035806097,"min":-0.3839899820439956}},{"name":"dense1/conv0/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0039155554537679636,"min":-0.48161332081345953}},{"name":"dense1/conv0/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01023082966898002,"min":-1.094698774580862}},{"name":"dense1/conv0/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0027264176630506327,"min":-0.3871513081531898}},{"name":"dense1/conv1/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004583378632863362,"min":-0.5454220573107401}},{"name":"dense1/conv1/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00915846403907327,"min":-1.117332612766939}},{"name":"dense1/conv1/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003091680419211294,"min":-0.5966943209077797}},{"name":"dense1/conv2/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005407439727409214,"min":-0.708374604290607}},{"name":"dense1/conv2/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00946493943532308,"min":-1.2399070660273235}},{"name":"dense1/conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004409168514550901,"min":-0.9788354102303}},{"name":"dense1/conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004478132958505668,"min":-0.6493292789833219}},{"name":"dense1/conv3/pointwise_filter","shape":[1,1,64,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011063695888893277,"min":-1.2501976354449402}},{"name":"dense1/conv3/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003909627596537272,"min":-0.6646366914113363}},{"name":"dense2/conv0/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003213915404151468,"min":-0.3374611174359041}},{"name":"dense2/conv0/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010917326048308728,"min":-1.4520043644250609}},{"name":"dense2/conv0/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002800439152063108,"min":-0.38085972468058266}},{"name":"dense2/conv1/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0050568851770139206,"min":-0.6927932692509071}},{"name":"dense2/conv1/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01074961213504567,"min":-1.3222022926106174}},{"name":"dense2/conv1/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0030654204242369708,"min":-0.5487102559384177}},{"name":"dense2/conv2/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00591809165244009,"min":-0.917304206128214}},{"name":"dense2/conv2/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.01092823346455892,"min":-1.366029183069865}},{"name":"dense2/conv2/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002681120470458386,"min":-0.36463238398234055}},{"name":"dense2/conv3/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0048311497650894465,"min":-0.5797379718107336}},{"name":"dense2/conv3/pointwise_filter","shape":[1,1,128,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.011227761062921263,"min":-1.4483811771168429}},{"name":"dense2/conv3/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0034643323982463162,"min":-0.3360402426298927}},{"name":"dense3/conv0/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003394978887894574,"min":-0.49227193874471326}},{"name":"dense3/conv0/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010051267287310432,"min":-1.2765109454884247}},{"name":"dense3/conv0/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003142924752889895,"min":-0.4588670139219247}},{"name":"dense3/conv1/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00448304671867221,"min":-0.5872791201460595}},{"name":"dense3/conv1/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.016063522357566685,"min":-2.3613377865623026}},{"name":"dense3/conv1/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.00287135781026354,"min":-0.47664539650374765}},{"name":"dense3/conv2/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006002906724518421,"min":-0.7923836876364315}},{"name":"dense3/conv2/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.017087187019048954,"min":-1.6061955797906016}},{"name":"dense3/conv2/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003124481205846749,"min":-0.46242321846531886}},{"name":"dense3/conv3/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006576311588287353,"min":-1.0193282961845398}},{"name":"dense3/conv3/pointwise_filter","shape":[1,1,256,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015590153955945782,"min":-1.99553970636106}},{"name":"dense3/conv3/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004453541601405424,"min":-0.6546706154065973}},{"name":"fc/weights","shape":[256,136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.010417488509533453,"min":-1.500118345372817}},{"name":"fc/bias","shape":[136],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0025084222648658005,"min":0.07683877646923065}}],"paths":["face_landmark_68_model-shard1"]}]
Binary file not shown.
6 changes: 6 additions & 0 deletions FrontEnd/public/models/face_recognition_model-shard2

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"weights":[{"name":"conv0/filters","shape":[3,3,3,16],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.009007044399485869,"min":-1.2069439495311063}},{"name":"conv0/bias","shape":[16],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.005263455241334205,"min":-0.9211046672334858}},{"name":"conv1/depthwise_filter","shape":[3,3,16,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.004001977630690033,"min":-0.5042491814669441}},{"name":"conv1/pointwise_filter","shape":[1,1,16,32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.013836609615999109,"min":-1.411334180831909}},{"name":"conv1/bias","shape":[32],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0015159862590771096,"min":-0.30926119685173037}},{"name":"conv2/depthwise_filter","shape":[3,3,32,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002666276225856706,"min":-0.317286870876948}},{"name":"conv2/pointwise_filter","shape":[1,1,32,64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.015265831292844286,"min":-1.6792414422128714}},{"name":"conv2/bias","shape":[64],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0020280554598453,"min":-0.37113414915168985}},{"name":"conv3/depthwise_filter","shape":[3,3,64,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006100742489683862,"min":-0.8907084034938438}},{"name":"conv3/pointwise_filter","shape":[1,1,64,128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.016276211832083907,"min":-2.0508026908425725}},{"name":"conv3/bias","shape":[128],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.003394414279975143,"min":-0.7637432129944072}},{"name":"conv4/depthwise_filter","shape":[3,3,128,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.006716050119961009,"min":-0.8059260143953211}},{"name":"conv4/pointwise_filter","shape":[1,1,128,256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.021875603993733724,"min":-2.8875797271728514}},{"name":"conv4/bias","shape":[256],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.0041141652009066415,"min":-0.8187188749804216}},{"name":"conv5/depthwise_filter","shape":[3,3,256,1],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008423839597141042,"min":-0.9013508368940915}},{"name":"conv5/pointwise_filter","shape":[1,1,256,512],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.030007277283014035,"min":-3.8709387695088107}},{"name":"conv5/bias","shape":[512],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.008402082966823203,"min":-1.4871686851277068}},{"name":"conv8/filters","shape":[1,1,512,25],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.028336129469030042,"min":-4.675461362389957}},{"name":"conv8/bias","shape":[25],"dtype":"float32","quantization":{"dtype":"uint8","scale":0.002268134028303857,"min":-0.41053225912299807}}],"paths":["tiny_face_detector_model-shard1"]}]
23 changes: 17 additions & 6 deletions FrontEnd/src/App.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import React, { useEffect } from "react";
import AppRouter from "./routes"; // Import AppRouter từ thư mục routes (routes/index.js)
import { ToastContainer } from "react-toastify";
import { ToastContainer } from "react-toastify"; // Chỉ import Container, không cần hàm toast nữa
import "react-toastify/dist/ReactToastify.css";
import "./App.css";
import { useDispatch, useSelector } from "react-redux";
import { fetchCurrentUser } from "./store/authSlice"; // Import action để lấy thông tin người dùng hiện tại

// === QUAN TRỌNG: Import component dịch vụ xử lý socket vừa tạo ===
// Đảm bảo đường dẫn này đúng với nơi bạn tạo file ở bước trước
import NotificationSocketHandler from "./utils/NotificationSocketHandler";

function App() {
const dispatch = useDispatch();
const { isAuthenticated } = useSelector((state) => state.auth);

// useEffect này sẽ chạy một lần khi ứng dụng tải,
// hoặc mỗi khi trạng thái isAuthenticated thay đổi.
// 1. Giữ nguyên logic lấy thông tin user khi load trang
useEffect(() => {
// Nếu có token (isAuthenticated = true), gọi API để lấy thông tin mới nhất
if (isAuthenticated) {
dispatch(fetchCurrentUser());
}
}, [isAuthenticated, dispatch]);

return (
<>
{/* Router điều hướng trang */}
<AppRouter />

{/* Container chứa Toast (Popup thông báo) */}
{/* Nó cần nằm ở root để hiển thị đè lên mọi thứ */}
<ToastContainer
position="top-right"
autoClose={3000}
position="bottom-left" // Vị trí giống Facebook
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
Expand All @@ -33,7 +39,12 @@ function App() {
draggable
pauseOnHover
theme="light"
icon={false}
/>

{/* === COMPONENT XỬ LÝ SOCKET === */}
{/* Chỉ render (và kết nối socket) khi user đã đăng nhập */}
{isAuthenticated && <NotificationSocketHandler />}
</>
);
}
Expand Down
299 changes: 299 additions & 0 deletions FrontEnd/src/assets/AdminDashboard/AdminDashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -699,3 +699,302 @@ body {
.retry-button:hover {
background-color: #ea580c;
}

/* Top Courses Section */
.top-courses-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1rem;
margin-top: 1rem;
}

.course-item {
display: flex;
gap: 1rem;
padding: 1rem;
border: 1px solid #e9eaf0;
border-radius: 8px;
transition: all 0.2s;
background: white;
}

.course-item:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}

.course-thumbnail {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 6px;
flex-shrink: 0;
}

.course-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}

.course-title {
font-size: 0.875rem;
font-weight: 600;
color: #1d2026;
margin: 0;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}

.course-stats-row {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.75rem;
}

.course-revenue {
color: #10b981;
font-weight: 500;
}

.course-enrollments {
color: #6b7280;
}

.course-rating-row {
font-size: 0.75rem;
color: #f59e0b;
}

/* Transactions Table */
.transactions-table-wrapper {
overflow-x: auto;
margin-top: 1rem;
}

.transactions-table {
width: 100%;
border-collapse: collapse;
font-size: 0.875rem;
}

.transactions-table thead {
background: #f9fafb;
}

.transactions-table th {
text-align: left;
padding: 0.75rem;
font-weight: 600;
color: #6b7280;
border-bottom: 2px solid #e9eaf0;
}

.transactions-table td {
padding: 1rem 0.75rem;
border-bottom: 1px solid #f3f4f6;
}

.course-title-cell {
max-width: 250px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.amount-cell {
font-weight: 600;
color: #10b981;
}

.date-cell {
color: #6b7280;
font-size: 0.8125rem;
}

.status-badge {
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
text-transform: capitalize;
}

.status-completed {
background: #d1fae5;
color: #065f46;
}

.status-pending {
background: #fef3c7;
color: #92400e;
}

.status-failed {
background: #fee2e2;
color: #991b1b;
}

/* Course Completion Section */
.completion-list {
display: flex;
flex-direction: column;
gap: 1.5rem;
margin-top: 1rem;
}

.completion-item {
padding: 1rem;
border: 1px solid #e9eaf0;
border-radius: 8px;
background: white;
}

.completion-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}

.completion-course-title {
font-weight: 600;
color: #1d2026;
font-size: 0.875rem;
}

.completion-rate {
font-size: 1.25rem;
font-weight: 700;
color: #8b5cf6;
}

.completion-progress-bar {
width: 100%;
height: 8px;
background: #f3f4f6;
border-radius: 4px;
overflow: hidden;
margin-bottom: 0.75rem;
}

.completion-progress-fill {
height: 100%;
background: linear-gradient(90deg, #10b981 0%, #059669 100%);
transition: width 0.3s ease;
}

.completion-stats {
display: flex;
gap: 1.5rem;
font-size: 0.8125rem;
color: #6b7280;
}

/* No Data Message */
.no-data-message {
text-align: center;
padding: 2rem;
color: #9ca3af;
font-style: italic;
}

/* Responsive */
@media (max-width: 768px) {
.top-courses-grid {
grid-template-columns: 1fr;
}

.course-item {
flex-direction: column;
}

.course-thumbnail {
width: 100%;
height: 180px;
}

.transactions-table {
font-size: 0.75rem;
}

.transactions-table th,
.transactions-table td {
padding: 0.5rem;
}

.completion-stats {
flex-direction: column;
gap: 0.5rem;
}
}

/* Dashboard Header with Filters */
.dashboard-header-section {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 2rem;
flex-wrap: wrap;
gap: 1rem;
}

.dashboard-header-left {
flex: 1;
}

.dashboard-subtitle {
color: #6b7280;
font-size: 0.875rem;
margin-top: 0.25rem;
}

.dashboard-filters {
display: flex;
gap: 1rem;
align-items: center;
}

.filter-select {
padding: 0.625rem 1rem;
border: 1px solid #e9eaf0;
border-radius: 8px;
background: white;
font-size: 0.875rem;
font-weight: 500;
color: #1d2026;
cursor: pointer;
transition: all 0.2s;
min-width: 140px;
}

.filter-select:hover {
border-color: #8b5cf6;
box-shadow: 0 2px 8px rgba(139, 92, 246, 0.1);
}

.filter-select:focus {
outline: none;
border-color: #8b5cf6;
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1);
}

/* Responsive Filters */
@media (max-width: 768px) {
.dashboard-header-section {
flex-direction: column;
align-items: stretch;
}

.dashboard-filters {
flex-direction: column;
}

.filter-select {
width: 100%;
}
}
Loading