From 883f399e75fd5fc70c19820c93db013f7f880707 Mon Sep 17 00:00:00 2001 From: MrFarrukhT Date: Thu, 13 Feb 2025 18:12:57 +0500 Subject: [PATCH 1/8] Add class management features including pages for classes, students, timetable, and payments --- .../layout/sidebar/MenuItems.tsx | 32 ++++++++ .../src/app/class-management/classes/page.tsx | 32 ++++++++ package/src/app/class-management/layout.tsx | 52 +++++++++++++ .../app/class-management/payments/page.tsx | 75 +++++++++++++++++++ .../app/class-management/students/page.tsx | 32 ++++++++ .../app/class-management/timetable/page.tsx | 73 ++++++++++++++++++ 6 files changed, 296 insertions(+) create mode 100644 package/src/app/class-management/classes/page.tsx create mode 100644 package/src/app/class-management/layout.tsx create mode 100644 package/src/app/class-management/payments/page.tsx create mode 100644 package/src/app/class-management/students/page.tsx create mode 100644 package/src/app/class-management/timetable/page.tsx diff --git a/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx b/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx index f6ab24d0..919002df 100644 --- a/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx +++ b/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx @@ -6,6 +6,10 @@ import { IconMoodHappy, IconTypography, IconUserPlus, + IconSchool, + IconUsers, + IconCalendar, + IconCash, } from "@tabler/icons-react"; import { uniqueId } from "lodash"; @@ -22,6 +26,34 @@ const Menuitems = [ icon: IconLayoutDashboard, href: "/", }, + { + navlabel: true, + subheader: "Class Management", + }, + { + id: uniqueId(), + title: "Classes", + icon: IconSchool, + href: "/class-management/classes", + }, + { + id: uniqueId(), + title: "Students", + icon: IconUsers, + href: "/class-management/students", + }, + { + id: uniqueId(), + title: "Timetable", + icon: IconCalendar, + href: "/class-management/timetable", + }, + { + id: uniqueId(), + title: "Payments", + icon: IconCash, + href: "/class-management/payments", + }, { navlabel: true, subheader: "Utilities", diff --git a/package/src/app/class-management/classes/page.tsx b/package/src/app/class-management/classes/page.tsx new file mode 100644 index 00000000..8c1ea6f2 --- /dev/null +++ b/package/src/app/class-management/classes/page.tsx @@ -0,0 +1,32 @@ +"use client"; +import { Grid, Card, CardContent, Typography, Button, Box } from "@mui/material"; +import PageContainer from "@/app/(DashboardLayout)/components/container/PageContainer"; + +const ClassesPage = () => { + return ( + + + + + + + + Classes + + + + This is a basic class management page. We will add class listings, creation forms, + and student management features here. + + + + + + + + ); +}; + +export default ClassesPage; \ No newline at end of file diff --git a/package/src/app/class-management/layout.tsx b/package/src/app/class-management/layout.tsx new file mode 100644 index 00000000..ae701245 --- /dev/null +++ b/package/src/app/class-management/layout.tsx @@ -0,0 +1,52 @@ +"use client"; +import { styled, Container, Box } from "@mui/material"; +import Header from "@/app/(DashboardLayout)/layout/header/Header"; +import Sidebar from "@/app/(DashboardLayout)/layout/sidebar/Sidebar"; +import { useState } from "react"; + +const MainWrapper = styled("div")(() => ({ + display: "flex", + minHeight: "100vh", + width: "100%", +})); + +const PageWrapper = styled("div")(() => ({ + display: "flex", + flexGrow: 1, + paddingBottom: "60px", + flexDirection: "column", + zIndex: 1, + backgroundColor: "transparent", +})); + +export default function ClassManagementLayout({ + children, +}: { + children: React.ReactNode; +}) { + const [isSidebarOpen, setSidebarOpen] = useState(true); + const [isMobileSidebarOpen, setMobileSidebarOpen] = useState(false); + + return ( + + {/* Sidebar */} + setMobileSidebarOpen(false)} + /> + {/* Main Content */} + +
setMobileSidebarOpen(true)} /> + + {children} + + + + ); +} \ No newline at end of file diff --git a/package/src/app/class-management/payments/page.tsx b/package/src/app/class-management/payments/page.tsx new file mode 100644 index 00000000..26892e39 --- /dev/null +++ b/package/src/app/class-management/payments/page.tsx @@ -0,0 +1,75 @@ +"use client"; +import { Grid, Card, CardContent, Typography, Box, Chip, Stack } from "@mui/material"; +import PageContainer from "@/app/(DashboardLayout)/components/container/PageContainer"; + +const PaymentsPage = () => { + // Sample data - will be replaced with real data later + const paymentStats = [ + { label: "Total Payments", value: "0", color: "primary" }, + { label: "Paid", value: "0", color: "success" }, + { label: "Pending", value: "0", color: "warning" }, + { label: "Overdue", value: "0", color: "error" }, + ]; + + return ( + + + + {/* Payment Statistics */} + {paymentStats.map((stat) => ( + + + + + + + {stat.label} + + {stat.value} + + + + + + + ))} + + {/* Payment History */} + + + + + Payment History + + + Payment history will be displayed here. We will add: +
    +
  • Payment records table
  • +
  • Payment status tracking
  • +
  • Payment processing functionality
  • +
  • Payment reports and analytics
  • +
+
+
+
+
+
+
+
+ ); +}; + +export default PaymentsPage; \ No newline at end of file diff --git a/package/src/app/class-management/students/page.tsx b/package/src/app/class-management/students/page.tsx new file mode 100644 index 00000000..ff2f165e --- /dev/null +++ b/package/src/app/class-management/students/page.tsx @@ -0,0 +1,32 @@ +"use client"; +import { Grid, Card, CardContent, Typography, Button, Box } from "@mui/material"; +import PageContainer from "@/app/(DashboardLayout)/components/container/PageContainer"; + +const StudentsPage = () => { + return ( + + + + + + + + Students + + + + This is a basic student management page. We will add student listings, + registration forms, and student profiles here. + + + + + + + + ); +}; + +export default StudentsPage; \ No newline at end of file diff --git a/package/src/app/class-management/timetable/page.tsx b/package/src/app/class-management/timetable/page.tsx new file mode 100644 index 00000000..f0f48887 --- /dev/null +++ b/package/src/app/class-management/timetable/page.tsx @@ -0,0 +1,73 @@ +"use client"; +import { Grid, Card, CardContent, Typography, Box, Paper } from "@mui/material"; +import PageContainer from "@/app/(DashboardLayout)/components/container/PageContainer"; + +const TimeSlots = ["9:00 - 10:30", "10:30 - 12:00", "14:00 - 15:30", "15:30 - 17:00", "17:00 - 18:30", "18:30 - 20:00"]; +const Days = ["Monday", "Wednesday", "Friday", "Tuesday", "Thursday", "Saturday"]; + +const TimetablePage = () => { + return ( + + + + + + + Class Timetable + + + {/* Header */} + + + Time + + + {Days.map((day) => ( + + + {day} + + + ))} + + {/* Time slots */} + {TimeSlots.map((time) => ( + <> + + + {time} + + + {Days.map((day) => ( + + + + Click to add class + + + + ))} + + ))} + + + + + + + + + ); +}; + +export default TimetablePage; \ No newline at end of file From fb5ea4fca28756b29f05e37e589ba764025e3fd3 Mon Sep 17 00:00:00 2001 From: MrFarrukhT Date: Fri, 14 Feb 2025 10:50:05 +0500 Subject: [PATCH 2/8] Add cabinets management page and mock data for classrooms --- landingpage/index.html | 300 +++++++-------- landingpage/style.css | 344 ++++++++++++------ .../layout/sidebar/MenuItems.tsx | 7 + .../app/class-management/cabinets/page.tsx | 90 +++++ .../src/app/class-management/classes/page.tsx | 136 +++++-- .../app/class-management/payments/page.tsx | 210 +++++++---- .../app/class-management/students/page.tsx | 153 ++++++-- .../app/class-management/timetable/page.tsx | 255 +++++++++---- package/src/mock/data.ts | 148 ++++++++ 9 files changed, 1177 insertions(+), 466 deletions(-) create mode 100644 package/src/app/class-management/cabinets/page.tsx create mode 100644 package/src/mock/data.ts diff --git a/landingpage/index.html b/landingpage/index.html index 4085292e..38bf9e9e 100644 --- a/landingpage/index.html +++ b/landingpage/index.html @@ -3,16 +3,12 @@ - Modernize NextJs Admin Template by Adminmart - - - + Innovative Centre - World-class Education in Uzbekistan -
-
-
-
+ +
+
+
+
+

+ Enabling World-Class Education in Uzbekistan +

+

+ We're dedicated to providing innovative educational solutions that empower students + and transform lives through quality learning experiences. +

+
+
+

1000+

+

Students Enrolled

+
+
+

50+

+

Expert Teachers

+
+
+

95%

+

Success Rate

+
+
+ +
+
+ Education Preview +
+
+
+
+ +
-

- Create Userfriendly Interface for your - Applications & - Products with - - Modernize NextJs Admin +

+ Choose Your Path to + Excellence

- - - +
-
+
-

Free Version

+

Standard Program

+
+ Most Popular +
Free version
Live PreviewLearn More Free DownloadEnroll Now

- If you are looking for an eye-catching and elegantly - designed free nextjs admin template that comes with several - added features, then look no more. Modernize Nextjs admin - is a free template that has everything you require to - develop an amazing web app. It has a very simple sleek - design which will give your next project a professional - and engaging look. + Our comprehensive standard program provides a solid foundation in key subjects with experienced teachers and proven methodologies.

  • - 1 Basic Dashboard -
  • -
  • - 7+ Pages Template -
  • -
  • - 5+ UI Components + Core Subjects
  • - Material Ui + + Group Classes
  • - Tabler Icons + + Weekly Assessments
  • - React 18+ + + Study Materials
  • - NextJs 14+ -
  • -
  • - No Support - Provided + + Progress Reports
  • - Code splitting -
  • -
  • - Organized Code Structure + + Online Resources
  • - SCSS Base CSS + + Practice Tests
  • - Easy To Customize + + Basic Support
  • - Fully Responsive Pages -
  • -
  • - No - Documentation -
  • -
  • - Backlink - Required + + Certificate
@@ -199,138 +203,82 @@

Free Version

- - - -
-
+ +
+
-

- Pro Version -

+

Premium Program

+
+ Advanced Learning +
Pro version
Live PreviewLearn More Buy Pro VersionEnroll Now
  • - 2+ Stunning Dashboards -
  • -
  • - 65+ Page Templates -
  • -
  • - 45+ UI Components -
  • -
  • - 15+ Integrated Plugins -
  • -
  • - NextJs Landing Page -
  • -
  • - 1 Year Premium Support -
  • -
  • - Axios Included -
  • -
  • - 3400+ Font Icons + + Advanced Curriculum
  • - Material Ui + + Small Group Classes
  • - Fully Responsive Pages + + 1-on-1 Tutoring
  • - NextJs 14 + + International Exams
  • - & lots more.. + + Career Guidance
  • - 4+ Unique Demos -
  • -
  • - 9+ Ready to Use App -
  • -
  • - i18 Included -
  • -
  • - React 18+ -
  • -
  • - Dashboard Figma Files -
  • -
  • - Documentation Provided -
  • -
  • - Slick Carousel Included + + Premium Materials
  • - Tons of Table Example + + Mock Interviews
  • - Variety of Forms Included + + 24/7 Support
  • - SCSS Base CSS + + International Certificate
  • - Easy to Customize + + University Counseling
  • -
@@ -342,9 +290,9 @@

- All Rights Reserved by Modernize React Admin. Designed and Developed - by - adminmart.com + © 2024 Innovative Centre. All Rights Reserved. +
+ Empowering Education in Uzbekistan
diff --git a/landingpage/style.css b/landingpage/style.css index b9d09082..5de1f84f 100644 --- a/landingpage/style.css +++ b/landingpage/style.css @@ -1,12 +1,11 @@ @import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@200;300;400;500;600;700;800&display=swap'); :root { - --bs-font-sans-serif: "Plus Jakarta Sans", - sans-serif; - --bs-primary: #5D87FF; - --bs-secondary: #49BEFF; - --bs-danger: #FA896B; - --bs-body-font-size: 0.875rem; + --bs-font-sans-serif: "Plus Jakarta Sans", sans-serif; + --bs-primary: #003366; /* Darker blue for education */ + --bs-secondary: #0066cc; /* Lighter blue for accents */ + --bs-success: #28a745; + --bs-body-font-size: 0.875rem; --bs-body-font-weight: 400; --bs-body-line-height: 1.5; --bs-body-color: #5A6A85; @@ -14,154 +13,275 @@ --bs-border-color: #ebf1f6; --bs-heading-color: #2A3547; --bs-border-radius: 7px; - --bs-gutter-x: 24px; - --bs-btn-font-size: 0.875rem; - --bs-card-spacer-y: 30px; - --bs-card-spacer-x: 30px; - --bs-card-border-width: 0px; - --bs-success-rgb: 19,222,185; - --bs-primary-rgb: 93,135,255; - --bs-light-rgb: 246,249,252; - --bs-dark-rgb: 42,53,71; + --bs-gutter-x: 24px; + --bs-btn-font-size: 0.875rem; + --bs-card-spacer-y: 30px; + --bs-card-spacer-x: 30px; + --bs-card-border-width: 0px; + --bs-success-rgb: 40,167,69; + --bs-primary-rgb: 0,51,102; + --bs-light-rgb: 246,249,252; + --bs-dark-rgb: 42,53,71; } +/* Buttons */ .btn { - --bs-btn-padding-x: 16px; - --bs-btn-padding-y: 7px; - --bs-btn-font-size: 0.875rem; + --bs-btn-padding-x: 16px; + --bs-btn-padding-y: 7px; + --bs-btn-font-size: 0.875rem; + transition: all 0.3s ease; + border-radius: 25px; +} + +.btn-lg { + --bs-btn-padding-x: 24px; + --bs-btn-padding-y: 12px; + --bs-btn-font-size: 1rem; } .btn-primary { - --bs-btn-bg: var(--bs-primary); - --bs-btn-border-color: var(--bs-primary); - --bs-btn-hover-bg: var(--bs-primary); - --bs-btn-hover-border-color: var(--bs-primary); - --bs-btn-focus-shadow-rgb: 49,132,253; - --bs-btn-active-bg: var(--bs-primary); - --bs-btn-active-border-color: var(--bs-primary); - --bs-btn-disabled-bg: var(--bs-primary); - --bs-btn-disabled-border-color: var(--bs-primary); + --bs-btn-bg: var(--bs-primary); + --bs-btn-border-color: var(--bs-primary); + --bs-btn-hover-bg: #004080; + --bs-btn-hover-border-color: #004080; + --bs-btn-focus-shadow-rgb: 0,51,102; + --bs-btn-active-bg: #004080; + --bs-btn-active-border-color: #004080; } .btn-secondary { - --bs-btn-bg: var(--bs-secondary); - --bs-btn-border-color: var(--bs-secondary); - --bs-btn-hover-bg: var(--bs-secondary); - --bs-btn-hover-border-color: var(--bs-secondary); - --bs-btn-focus-shadow-rgb: 49,132,253; - --bs-btn-active-bg: var(--bs-secondary); - --bs-btn-active-border-color: var(--bs-secondary); - --bs-btn-disabled-bg: var(--bs-secondary); - --bs-btn-disabled-border-color: var(--bs-secondary); -} - -.btn-danger { - --bs-btn-bg: var(--bs-danger); - --bs-btn-border-color: var(--bs-danger); - --bs-btn-hover-bg: var(--bs-danger); - --bs-btn-hover-border-color: var(--bs-danger); - --bs-btn-focus-shadow-rgb: 49,132,253; - --bs-btn-active-bg: var(--bs-danger); - --bs-btn-active-border-color: var(--bs-danger); - --bs-btn-disabled-bg: var(--bs-danger); - --bs-btn-disabled-border-color: var(--bs-danger); + --bs-btn-bg: var(--bs-secondary); + --bs-btn-border-color: var(--bs-secondary); + --bs-btn-hover-bg: #0052a3; + --bs-btn-hover-border-color: #0052a3; + --bs-btn-focus-shadow-rgb: 0,102,204; + --bs-btn-active-bg: #0052a3; + --bs-btn-active-border-color: #0052a3; } .btn-outline-primary { - --bs-btn-color: var(--bs-primary); - --bs-btn-border-color: var(--bs-primary); - --bs-btn-hover-color: #fff; - --bs-btn-hover-bg: var(--bs-primary); - --bs-btn-hover-border-color: var(--bs-primary); - --bs-btn-focus-shadow-rgb: 13,110,253; - --bs-btn-active-color: #fff; - --bs-btn-active-bg: var(--bs-primary); - --bs-btn-active-border-color: var(--bs-primary); - --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - --bs-btn-disabled-color: var(--bs-primary); - --bs-btn-disabled-bg: transparent; - --bs-btn-disabled-border-color: var(--bs-primary); -} - -/* background */ -.bg-light { - background-color: rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important; + --bs-btn-color: var(--bs-primary); + --bs-btn-border-color: var(--bs-primary); + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: var(--bs-primary); + --bs-btn-hover-border-color: var(--bs-primary); + --bs-btn-active-color: #fff; + --bs-btn-active-bg: var(--bs-primary); + --bs-btn-active-border-color: var(--bs-primary); +} + +.btn-outline-light { + --bs-btn-hover-color: var(--bs-primary); +} + +/* Header & Navigation */ +.navbar { + padding: 1rem 0; + box-shadow: 0 2px 15px rgba(0,0,0,0.05); +} + +.navbar-brand img { + height: 45px; + transition: transform 0.3s ease; +} + +.navbar-brand img:hover { + transform: scale(1.05); +} + +.nav-link { + font-weight: 500; + padding: 0.5rem 1rem; + color: var(--bs-primary) !important; + position: relative; +} + +.nav-link::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + width: 0; + height: 2px; + background: var(--bs-primary); + transition: all 0.3s ease; + transform: translateX(-50%); +} + +.nav-link:hover::after { + width: 80%; +} + +.dropdown-menu { + border: none; + box-shadow: 0 0 20px rgba(0,0,0,0.1); + border-radius: 10px; +} + +.dropdown-item { + padding: 0.7rem 1.2rem; + transition: all 0.2s ease; +} + +.dropdown-item:hover { + background-color: rgba(var(--bs-primary-rgb), 0.1); +} + +/* Hero Section */ +.hero-section { + background: linear-gradient(135deg, var(--bs-primary) 0%, #004d99 100%); + padding: 6rem 0; + position: relative; + overflow: hidden; } -/* text colors */ -.text-success { - color: rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important; +.hero-section::before { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: url("data:image/svg+xml,%3Csvg width='100' height='100' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z' fill='rgba(255,255,255,0.05)' fill-rule='evenodd'/%3E%3C/svg%3E"); } -.text-primary { - color: rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important; +.hero-section img { + transform: perspective(1000px) rotateY(-10deg); + transition: transform 0.5s ease; + border-radius: 15px; + box-shadow: 0 15px 30px rgba(0,0,0,0.2); } -.text-dark { - color: rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important; +.hero-section img:hover { + transform: perspective(1000px) rotateY(0deg); } -.text-muted { - color: #5a6a85!important; +.hero-stats { + color: rgba(255, 255, 255, 0.9); +} + +.hero-stats .stat-item { + padding: 1rem; + background: rgba(255, 255, 255, 0.1); + border-radius: 10px; + backdrop-filter: blur(5px); +} + +/* Cards and Sections */ +.bg-light { + background-color: #f8fafc !important; } -/* card */ .card { - --bs-card-spacer-y: 30px; - --bs-card-spacer-x: 30px; - --bs-card-title-color: #2A3547; - --bs-card-subtitle-color: #2A3547; - --bs-card-border-width: 0px; - --bs-card-box-shadow: rgba(145, 158, 171, 0.2) 0px 0px 2px 0px,rgba(145, 158, 171, 0.12) 0px 12px 24px -4px; - --bs-card-inner-border-radius: 7px; + --bs-card-spacer-y: 30px; + --bs-card-spacer-x: 30px; + border-radius: 15px; + border: none; + box-shadow: 0 10px 30px rgba(0,0,0,0.08); + transition: all 0.3s ease; +} + +.card:hover { + transform: translateY(-5px); + box-shadow: 0 15px 40px rgba(0,0,0,0.12); } .container { - max-width: 1150px; + max-width: 1150px; } -/* custom */ +/* Spacing and Custom Elements */ .spacer { - padding: 80px 0; + padding: 80px 0; } .pro-demo { - -webkit-box-shadow: 0px 4px 45px rgb(0 0 0 / 9%); - box-shadow: 0px 4px 45px rgb(0 0 0 / 9%); + border: 2px solid rgba(var(--bs-primary-rgb), 0.1); } .line-h33 { - line-height: 33px; + line-height: 33px; } .icon-circle { - background-color: rgb(var( --bs-dark-rgb)); - display: inline-block; - width: 15px; - height: 15px; - border-radius: 100%; - position: relative; + background-color: var(--bs-primary); + display: inline-block; + width: 24px; + height: 24px; + border-radius: 50%; + position: relative; + margin-right: 10px; } .icon-circle.circle-primary { - background-color: rgb(var( --bs-primary-rgb)) !important; + background-color: var(--bs-primary); } .icon-circle.circle-muted { - background-color: var(--bs-body-color) !important; - opacity: 0.8; -} - -.icon-circle:before { - content: ''; - position: absolute; - left: 0; - right: 0; - width: 5px; - height: 5px; - border-radius: 100%; - background-color: #fff; - display: block; - margin: 0 auto; - top: 5px; + background-color: #8da2b5; + opacity: 0.8; +} + +.icon-circle::before { + content: '✓'; + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + color: white; + font-size: 12px; +} + +/* Badges */ +.badge { + padding: 0.5rem 1rem; + font-weight: 500; + border-radius: 20px; +} + +/* Footer */ +footer { + background-color: var(--bs-light); + border-top: 1px solid var(--bs-border-color); + padding: 2rem 0; + color: var(--bs-primary); +} + +footer a { + color: var(--bs-primary); + text-decoration: none; + transition: color 0.2s ease; +} + +footer a:hover { + color: var(--bs-secondary); +} + +/* Responsive Adjustments */ +@media (max-width: 768px) { + .hero-section { + padding: 4rem 0; + } + + .hero-section img { + margin-top: 2rem; + transform: none; + } + + .navbar-collapse { + background: white; + padding: 1rem; + border-radius: var(--bs-border-radius); + box-shadow: 0 0 20px rgba(0,0,0,0.1); + } + + .hero-stats { + flex-direction: column; + gap: 1rem; + } + + .hero-stats .stat-item { + width: 100%; + text-align: center; + } } \ No newline at end of file diff --git a/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx b/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx index 919002df..0b3fe677 100644 --- a/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx +++ b/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx @@ -10,6 +10,7 @@ import { IconUsers, IconCalendar, IconCash, + IconDoor, } from "@tabler/icons-react"; import { uniqueId } from "lodash"; @@ -54,6 +55,12 @@ const Menuitems = [ icon: IconCash, href: "/class-management/payments", }, + { + id: uniqueId(), + title: "Cabinets", + icon: IconDoor, + href: "/class-management/cabinets", + }, { navlabel: true, subheader: "Utilities", diff --git a/package/src/app/class-management/cabinets/page.tsx b/package/src/app/class-management/cabinets/page.tsx new file mode 100644 index 00000000..b7b18b5e --- /dev/null +++ b/package/src/app/class-management/cabinets/page.tsx @@ -0,0 +1,90 @@ +'use client'; +import { Grid, Card, CardContent, Typography, Button, Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Chip, IconButton } from '@mui/material'; +import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; +import { mockCabinets } from '@/mock/data'; +import { IconEdit, IconTrash } from '@tabler/icons-react'; + +const Cabinets = () => { + const getStatusColor = (status: string) => { + switch (status) { + case 'available': + return 'success'; + case 'occupied': + return 'warning'; + case 'maintenance': + return 'error'; + default: + return 'default'; + } + }; + + return ( + + + + + + + Cabinets + + + + + + + + Name + Location + Capacity + Equipment + Status + Actions + + + + {mockCabinets.map((cabinet) => ( + + {cabinet.name} + {cabinet.location} + {cabinet.capacity} students + + {cabinet.equipment.map((item, index) => ( + + ))} + + + + + + + + + + + + + + ))} + +
+
+
+
+
+
+
+ ); +}; + +export default Cabinets; \ No newline at end of file diff --git a/package/src/app/class-management/classes/page.tsx b/package/src/app/class-management/classes/page.tsx index 8c1ea6f2..135fbac5 100644 --- a/package/src/app/class-management/classes/page.tsx +++ b/package/src/app/class-management/classes/page.tsx @@ -1,32 +1,118 @@ -"use client"; -import { Grid, Card, CardContent, Typography, Button, Box } from "@mui/material"; -import PageContainer from "@/app/(DashboardLayout)/components/container/PageContainer"; +'use client'; +import { + Grid, + Card, + CardContent, + Typography, + Button, + Box, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + IconButton, + Chip, +} from '@mui/material'; +import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; +import { mockClasses, mockCabinets } from '@/mock/data'; +import { IconEdit, IconTrash, IconCalendar } from '@tabler/icons-react'; + +const Classes = () => { + const getCabinetName = (cabinetId: string) => { + const cabinet = mockCabinets.find(c => c.id === cabinetId); + return cabinet ? cabinet.name : 'Not assigned'; + }; + + const formatSchedule = (schedule: { day: string; startTime: string; endTime: string }[]) => { + return schedule.map(s => `${s.day} ${s.startTime}-${s.endTime}`).join(', '); + }; -const ClassesPage = () => { return ( - - - - - - - - Classes - - - - This is a basic class management page. We will add class listings, creation forms, - and student management features here. - - - - + + + + + + + Classes + + + + + + + + Class Name + Teacher + Subject + Cabinet + Schedule + Students + Actions + + + + {mockClasses.map((class_) => ( + + + + {class_.name} + + + {class_.teacher} + + + + + + + + + + + {formatSchedule(class_.schedule)} + + + + + + + + + + + + + + + + ))} + +
+
+
+
-
+
); }; -export default ClassesPage; \ No newline at end of file +export default Classes; \ No newline at end of file diff --git a/package/src/app/class-management/payments/page.tsx b/package/src/app/class-management/payments/page.tsx index 26892e39..89ed52da 100644 --- a/package/src/app/class-management/payments/page.tsx +++ b/package/src/app/class-management/payments/page.tsx @@ -1,75 +1,153 @@ -"use client"; -import { Grid, Card, CardContent, Typography, Box, Chip, Stack } from "@mui/material"; -import PageContainer from "@/app/(DashboardLayout)/components/container/PageContainer"; +'use client'; +import { + Grid, + Card, + CardContent, + Typography, + Button, + Box, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Chip, + IconButton, +} from '@mui/material'; +import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; +import { mockPayments, mockStudents } from '@/mock/data'; +import { IconEdit, IconTrash, IconReceipt } from '@tabler/icons-react'; -const PaymentsPage = () => { - // Sample data - will be replaced with real data later - const paymentStats = [ - { label: "Total Payments", value: "0", color: "primary" }, - { label: "Paid", value: "0", color: "success" }, - { label: "Pending", value: "0", color: "warning" }, - { label: "Overdue", value: "0", color: "error" }, - ]; +const Payments = () => { + const getStatusColor = (status: string) => { + switch (status) { + case 'completed': + return 'success'; + case 'pending': + return 'warning'; + case 'failed': + return 'error'; + default: + return 'default'; + } + }; + + const getMethodColor = (method: string) => { + switch (method) { + case 'cash': + return 'success'; + case 'card': + return 'primary'; + case 'transfer': + return 'info'; + default: + return 'default'; + } + }; + + const getStudentName = (studentId: string) => { + const student = mockStudents.find(s => s.id === studentId); + return student ? student.name : 'Unknown Student'; + }; + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(amount); + }; return ( - - - - {/* Payment Statistics */} - {paymentStats.map((stat) => ( - - - - - - - {stat.label} - - {stat.value} - - - - - - - ))} + + + + + + + Payments + + - {/* Payment History */} - - - - - Payment History - - - Payment history will be displayed here. We will add: -
    -
  • Payment records table
  • -
  • Payment status tracking
  • -
  • Payment processing functionality
  • -
  • Payment reports and analytics
  • -
-
-
-
-
+ + + + + Payment ID + Student + Amount + Date + Method + Status + Description + Actions + + + + {mockPayments.map((payment) => ( + + + + + #{payment.id} + + + + + {getStudentName(payment.studentId)} + + + + + {formatCurrency(payment.amount)} + + + {new Date(payment.date).toLocaleDateString()} + + + + + + + + + {payment.description} + + + + + + + + + + + + ))} + +
+
+
+
-
+
); }; -export default PaymentsPage; \ No newline at end of file +export default Payments; \ No newline at end of file diff --git a/package/src/app/class-management/students/page.tsx b/package/src/app/class-management/students/page.tsx index ff2f165e..e83d259f 100644 --- a/package/src/app/class-management/students/page.tsx +++ b/package/src/app/class-management/students/page.tsx @@ -1,32 +1,135 @@ -"use client"; -import { Grid, Card, CardContent, Typography, Button, Box } from "@mui/material"; -import PageContainer from "@/app/(DashboardLayout)/components/container/PageContainer"; +'use client'; +import { + Grid, + Card, + CardContent, + Typography, + Button, + Box, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Chip, + IconButton, + Avatar, +} from '@mui/material'; +import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; +import { mockStudents } from '@/mock/data'; +import { IconEdit, IconTrash } from '@tabler/icons-react'; + +const Students = () => { + const getPaymentStatusColor = (status: string) => { + switch (status) { + case 'paid': + return 'success'; + case 'pending': + return 'warning'; + case 'overdue': + return 'error'; + default: + return 'default'; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case 'active': + return 'success'; + case 'inactive': + return 'error'; + default: + return 'default'; + } + }; -const StudentsPage = () => { return ( - - - - - - - - Students - - - - This is a basic student management page. We will add student listings, - registration forms, and student profiles here. - - - - + + + + + + + Students + + + + + + + + Student + Contact + Join Date + Status + Payment Status + Actions + + + + {mockStudents.map((student) => ( + + + + + {student.name.charAt(0)} + + + + {student.name} + + + {student.email} + + + + + {student.phone} + {new Date(student.joinDate).toLocaleDateString()} + + + + + + + + + + + + + + + + ))} + +
+
+
+
-
+
); }; -export default StudentsPage; \ No newline at end of file +export default Students; \ No newline at end of file diff --git a/package/src/app/class-management/timetable/page.tsx b/package/src/app/class-management/timetable/page.tsx index f0f48887..0e91eac0 100644 --- a/package/src/app/class-management/timetable/page.tsx +++ b/package/src/app/class-management/timetable/page.tsx @@ -1,73 +1,204 @@ -"use client"; -import { Grid, Card, CardContent, Typography, Box, Paper } from "@mui/material"; -import PageContainer from "@/app/(DashboardLayout)/components/container/PageContainer"; +'use client'; +import { useState, useMemo } from 'react'; +import { + Grid, + Card, + CardContent, + Typography, + Box, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Chip, + ToggleButtonGroup, + ToggleButton, + Stack, + IconButton, + Tooltip, +} from '@mui/material'; +import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; +import { mockClasses, mockCabinets, Class } from '@/mock/data'; +import EditIcon from '@mui/icons-material/Edit'; -const TimeSlots = ["9:00 - 10:30", "10:30 - 12:00", "14:00 - 15:30", "15:30 - 17:00", "17:00 - 18:30", "18:30 - 20:00"]; -const Days = ["Monday", "Wednesday", "Friday", "Tuesday", "Thursday", "Saturday"]; +const LEVELS = ['All Levels', 'A1', 'A2', 'B1', 'B2', 'IELTS']; + +const Timetable = () => { + const [selectedLevel, setSelectedLevel] = useState('All Levels'); + const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; + const timeSlots = [ + '09:00-10:30', + '10:45-12:15', + '13:00-14:30', + '14:45-16:15', + '16:30-18:00', + ]; + + const getCabinetInfo = (cabinetId: string) => { + const cabinet = mockCabinets.find((c) => c.id === cabinetId); + return { + name: cabinet ? cabinet.name : 'Not assigned', + capacity: cabinet ? cabinet.capacity : 0, + }; + }; + + const getClassStatus = (class_: Class) => { + const cabinet = getCabinetInfo(class_.cabinetId); + const studentCount = class_.students.length; + const remainingSpots = cabinet.capacity - studentCount; + + if (remainingSpots === 0) return { label: 'Full', color: 'error' }; + if (remainingSpots < 5) return { label: 'Near Full', color: 'warning' }; + return { label: 'Available', color: 'success' }; + }; + + const filteredClasses = useMemo(() => { + let classes = [...mockClasses]; + + // Sort by time + classes.sort((a, b) => { + const aTime = a.schedule[0]?.startTime || ''; + const bTime = b.schedule[0]?.startTime || ''; + return aTime.localeCompare(bTime); + }); + + // Filter by level if not "All Levels" + if (selectedLevel !== 'All Levels') { + classes = classes.filter(c => c.name.includes(selectedLevel)); + } + + return classes; + }, [selectedLevel]); + + const getClassForTimeSlot = (day: string, timeSlot: string) => { + const [slotStart] = timeSlot.split('-'); + return filteredClasses.find((class_) => + class_.schedule.some( + (s) => s.day === day && s.startTime === slotStart + ) + ); + }; + + const handleLevelChange = (event: React.MouseEvent, newLevel: string) => { + if (newLevel !== null) { + setSelectedLevel(newLevel); + } + }; -const TimetablePage = () => { return ( - - - - - - - Class Timetable - - - {/* Header */} - - - Time - - - {Days.map((day) => ( - - - {day} - - - ))} - - {/* Time slots */} - {TimeSlots.map((time) => ( - <> - - - {time} - - - {Days.map((day) => ( - - + + + + + + Weekly Timetable + + {LEVELS.map((level) => ( + + {level} + + ))} + + + + + + + + Time + {days.map((day) => ( + + {day} + + ))} + + + + {timeSlots.map((timeSlot) => ( + + + {timeSlot} + + {days.map((day) => { + const class_ = getClassForTimeSlot(day, timeSlot); + const cabinet = class_ ? getCabinetInfo(class_.cabinetId) : null; + const status = class_ ? getClassStatus(class_) : null; + + return ( + - - Click to add class - - - - ))} - + {class_ ? ( + + + + {class_.name} + + + + + + + + + {class_.teacher} + + + + + + + + ) : ( + + No class scheduled + + )} + + ); + })} + ))} - - - - - + +
+
+
+
-
+
); }; -export default TimetablePage; \ No newline at end of file +export default Timetable; \ No newline at end of file diff --git a/package/src/mock/data.ts b/package/src/mock/data.ts new file mode 100644 index 00000000..3a9250a1 --- /dev/null +++ b/package/src/mock/data.ts @@ -0,0 +1,148 @@ +// Types +export interface Student { + id: string; + name: string; + email: string; + phone: string; + joinDate: string; + status: 'active' | 'inactive'; + paymentStatus: 'paid' | 'pending' | 'overdue'; +} + +export interface Class { + id: string; + name: string; + teacher: string; + subject: string; + cabinetId: string; + schedule: { + day: string; + startTime: string; + endTime: string; + }[]; + students: string[]; // Array of student IDs +} + +export interface Cabinet { + id: string; + name: string; + capacity: number; + equipment: string[]; + status: 'available' | 'occupied' | 'maintenance'; + location: string; +} + +export interface Payment { + id: string; + studentId: string; + amount: number; + date: string; + status: 'completed' | 'pending' | 'failed'; + method: 'cash' | 'card' | 'transfer'; + description: string; +} + +// Mock Data +export const mockStudents: Student[] = [ + { + id: '1', + name: 'John Smith', + email: 'john@example.com', + phone: '+1234567890', + joinDate: '2024-01-15', + status: 'active', + paymentStatus: 'paid', + }, + { + id: '2', + name: 'Sarah Johnson', + email: 'sarah@example.com', + phone: '+1234567891', + joinDate: '2024-01-20', + status: 'active', + paymentStatus: 'pending', + }, +]; + +export const mockCabinets: Cabinet[] = [ + { + id: '1', + name: 'Room 101', + capacity: 20, + equipment: ['Projector', 'Whiteboard', 'Computers'], + status: 'available', + location: '1st Floor', + }, + { + id: '2', + name: 'Room 102', + capacity: 15, + equipment: ['Whiteboard', 'Smart TV'], + status: 'occupied', + location: '1st Floor', + }, +]; + +export const mockClasses: Class[] = [ + { + id: '1', + name: 'Advanced Mathematics', + teacher: 'Dr. Robert Brown', + subject: 'Mathematics', + cabinetId: '1', + schedule: [ + { + day: 'Monday', + startTime: '09:00', + endTime: '10:30', + }, + { + day: 'Wednesday', + startTime: '09:00', + endTime: '10:30', + }, + ], + students: ['1', '2'], + }, + { + id: '2', + name: 'English Literature', + teacher: 'Ms. Emily White', + subject: 'English', + cabinetId: '2', + schedule: [ + { + day: 'Tuesday', + startTime: '11:00', + endTime: '12:30', + }, + { + day: 'Thursday', + startTime: '11:00', + endTime: '12:30', + }, + ], + students: ['1'], + }, +]; + +export const mockPayments: Payment[] = [ + { + id: '1', + studentId: '1', + amount: 500, + date: '2024-02-01', + status: 'completed', + method: 'card', + description: 'February Tuition Fee', + }, + { + id: '2', + studentId: '2', + amount: 500, + date: '2024-02-01', + status: 'pending', + method: 'transfer', + description: 'February Tuition Fee', + }, +]; \ No newline at end of file From 90cae620569983ac6ff68068e96ab24800e16e1d Mon Sep 17 00:00:00 2001 From: MrFarrukhT Date: Fri, 14 Feb 2025 11:28:04 +0500 Subject: [PATCH 3/8] Enhance class management layout with responsive sidebar and additional class properties --- .../layout/header/Header.tsx | 41 +- .../layout/sidebar/Sidebar.tsx | 154 +++----- package/src/app/class-management/layout.tsx | 40 +- .../app/class-management/timetable/page.tsx | 350 ++++++++++-------- package/src/mock/data.ts | 79 +++- 5 files changed, 376 insertions(+), 288 deletions(-) diff --git a/package/src/app/(DashboardLayout)/layout/header/Header.tsx b/package/src/app/(DashboardLayout)/layout/header/Header.tsx index b75e5484..9cedbf63 100644 --- a/package/src/app/(DashboardLayout)/layout/header/Header.tsx +++ b/package/src/app/(DashboardLayout)/layout/header/Header.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Box, AppBar, Toolbar, styled, Stack, IconButton, Badge, Button } from '@mui/material'; +import { Box, AppBar, Toolbar, styled, Stack, IconButton, Badge, Button, useMediaQuery, Theme } from '@mui/material'; import PropTypes from 'prop-types'; import Link from 'next/link'; // components @@ -7,14 +7,13 @@ import Profile from './Profile'; import { IconBellRinging, IconMenu } from '@tabler/icons-react'; interface ItemType { - toggleMobileSidebar: (event: React.MouseEvent) => void; + toggleMobileSidebar: (event: React.MouseEvent) => void; + toggleSidebar?: (event: React.MouseEvent) => void; + isSidebarOpen?: boolean; } -const Header = ({toggleMobileSidebar}: ItemType) => { - - // const lgUp = useMediaQuery((theme) => theme.breakpoints.up('lg')); - // const lgDown = useMediaQuery((theme) => theme.breakpoints.down('lg')); - +const Header = ({ toggleMobileSidebar, toggleSidebar, isSidebarOpen }: ItemType) => { + const lgUp = useMediaQuery((theme: Theme) => theme.breakpoints.up('lg')); const AppBarStyled = styled(AppBar)(({ theme }) => ({ boxShadow: 'none', @@ -25,6 +24,7 @@ const Header = ({toggleMobileSidebar}: ItemType) => { minHeight: '70px', }, })); + const ToolbarStyled = styled(Toolbar)(({ theme }) => ({ width: '100%', color: theme.palette.text.secondary, @@ -33,6 +33,7 @@ const Header = ({toggleMobileSidebar}: ItemType) => { return ( + {/* Mobile menu button */} { + {/* Desktop menu button */} + {lgUp && toggleSidebar && ( + + + + )} { - + - @@ -74,6 +92,9 @@ const Header = ({toggleMobileSidebar}: ItemType) => { Header.propTypes = { sx: PropTypes.object, + toggleMobileSidebar: PropTypes.func.isRequired, + toggleSidebar: PropTypes.func, + isSidebarOpen: PropTypes.bool, }; export default Header; diff --git a/package/src/app/(DashboardLayout)/layout/sidebar/Sidebar.tsx b/package/src/app/(DashboardLayout)/layout/sidebar/Sidebar.tsx index 3a43e6f9..26e5b099 100644 --- a/package/src/app/(DashboardLayout)/layout/sidebar/Sidebar.tsx +++ b/package/src/app/(DashboardLayout)/layout/sidebar/Sidebar.tsx @@ -1,7 +1,6 @@ -import { useMediaQuery, Box, Drawer } from "@mui/material"; +import { useMediaQuery, Box, Drawer, styled } from "@mui/material"; import SidebarItems from "./SidebarItems"; import { Upgrade } from "./Updrade"; -import { Sidebar, Logo } from 'react-mui-sidebar'; interface ItemType { isMobileSidebarOpen: boolean; @@ -9,133 +8,84 @@ interface ItemType { isSidebarOpen: boolean; } -const MSidebar = ({ - isMobileSidebarOpen, - onSidebarClose, - isSidebarOpen, -}: ItemType) => { - const lgUp = useMediaQuery((theme: any) => theme.breakpoints.up("lg")); - - const sidebarWidth = "270px"; - - // Custom CSS for short scrollbar - const scrollbarStyles = { +const StyledDrawer = styled(Drawer)(({ theme }) => ({ + '& .MuiDrawer-paper': { + width: '270px', + border: 'none', + transition: theme.transitions.create(['width', 'transform'], { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.standard, + }), + overflowX: 'hidden', '&::-webkit-scrollbar': { width: '7px', - }, '&::-webkit-scrollbar-thumb': { backgroundColor: '#eff2f7', borderRadius: '15px', }, - }; + }, +})); +const LogoWrapper = styled(Box)({ + padding: '20px 20px 0px 20px', + '& img': { + maxWidth: '100%', + height: 'auto', + }, +}); + +const MSidebar = ({ + isMobileSidebarOpen, + onSidebarClose, + isSidebarOpen, +}: ItemType) => { + const lgUp = useMediaQuery((theme: any) => theme.breakpoints.up("lg")); + + const sidebarContent = ( + <> + + Logo + + + + + + + ); if (lgUp) { return ( - - {/* ------------------------------------------- */} - {/* Sidebar for desktop */} - {/* ------------------------------------------- */} - - {/* ------------------------------------------- */} - {/* Sidebar Box */} - {/* ------------------------------------------- */} - - - {/* ------------------------------------------- */} - {/* Logo */} - {/* ------------------------------------------- */} - - - {/* ------------------------------------------- */} - {/* Sidebar Items */} - {/* ------------------------------------------- */} - - - - - - - + {sidebarContent} + ); } return ( - theme.shadows[8], - ...scrollbarStyles, }, }} > - {/* ------------------------------------------- */} - {/* Sidebar Box */} - {/* ------------------------------------------- */} - - - {/* ------------------------------------------- */} - {/* Logo */} - {/* ------------------------------------------- */} - - {/* ------------------------------------------- */} - {/* Sidebar Items */} - {/* ------------------------------------------- */} - - - - - {/* ------------------------------------------- */} - {/* Sidebar For Mobile */} - {/* ------------------------------------------- */} - - + {sidebarContent} + ); }; export default MSidebar; - - - - - diff --git a/package/src/app/class-management/layout.tsx b/package/src/app/class-management/layout.tsx index ae701245..2186f820 100644 --- a/package/src/app/class-management/layout.tsx +++ b/package/src/app/class-management/layout.tsx @@ -1,22 +1,30 @@ "use client"; -import { styled, Container, Box } from "@mui/material"; +import { styled, Container, Box, useMediaQuery, Theme } from "@mui/material"; import Header from "@/app/(DashboardLayout)/layout/header/Header"; import Sidebar from "@/app/(DashboardLayout)/layout/sidebar/Sidebar"; -import { useState } from "react"; +import { useState, useEffect } from "react"; const MainWrapper = styled("div")(() => ({ display: "flex", minHeight: "100vh", width: "100%", + position: "relative", })); -const PageWrapper = styled("div")(() => ({ +const PageWrapper = styled("div")<{ isSidebarOpen: boolean }>(({ theme, isSidebarOpen }) => ({ display: "flex", flexGrow: 1, paddingBottom: "60px", flexDirection: "column", zIndex: 1, backgroundColor: "transparent", + transition: "width 0.2s ease-in-out, padding-left 0.2s ease-in-out", + width: "100%", + paddingLeft: isSidebarOpen ? "270px" : "0", + [theme.breakpoints.down("lg")]: { + paddingLeft: "0", + width: "100%", + }, })); export default function ClassManagementLayout({ @@ -26,18 +34,36 @@ export default function ClassManagementLayout({ }) { const [isSidebarOpen, setSidebarOpen] = useState(true); const [isMobileSidebarOpen, setMobileSidebarOpen] = useState(false); + const lgUp = useMediaQuery((theme: Theme) => theme.breakpoints.up("lg")); + + // Close sidebar on mobile by default + useEffect(() => { + if (!lgUp) { + setSidebarOpen(false); + } + }, [lgUp]); + + const toggleSidebar = () => { + setSidebarOpen(!isSidebarOpen); + }; + + const toggleMobileSidebar = () => { + setMobileSidebarOpen(!isMobileSidebarOpen); + }; return ( - {/* Sidebar */} setMobileSidebarOpen(false)} /> - {/* Main Content */} - -
setMobileSidebarOpen(true)} /> + +
{ + return new Intl.NumberFormat('uz-UZ', { + style: 'currency', + currency: 'UZS', + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(amount); +}; const Timetable = () => { - const [selectedLevel, setSelectedLevel] = useState('All Levels'); - const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; - const timeSlots = [ - '09:00-10:30', - '10:45-12:15', - '13:00-14:30', - '14:45-16:15', - '16:30-18:00', - ]; + const theme = useTheme(); + const [selectedTeacher, setSelectedTeacher] = useState('All Teachers'); + const [expandedClasses, setExpandedClasses] = useState([]); + + const teachers = useMemo(() => { + const uniqueTeachers = Array.from(new Set(mockClasses.map(c => c.teacher))); + return ['All Teachers', ...uniqueTeachers]; + }, []); + + const filteredClasses = useMemo(() => { + let classes = [...mockClasses]; + if (selectedTeacher !== 'All Teachers') { + classes = classes.filter(c => c.teacher === selectedTeacher); + } + return classes; + }, [selectedTeacher]); + + const mwfClasses = useMemo(() => + filteredClasses.filter(c => + c.schedule.some(s => ['Monday', 'Wednesday', 'Friday'].includes(s.day)) + ), + [filteredClasses] + ); + + const ttsClasses = useMemo(() => + filteredClasses.filter(c => + c.schedule.some(s => ['Tuesday', 'Thursday', 'Saturday'].includes(s.day)) + ), + [filteredClasses] + ); + + const getStudentsForClass = (classInfo: Class) => { + return mockStudents.filter(student => classInfo.students.includes(student.id)); + }; const getCabinetInfo = (cabinetId: string) => { const cabinet = mockCabinets.find((c) => c.id === cabinetId); @@ -45,156 +80,145 @@ const Timetable = () => { }; }; - const getClassStatus = (class_: Class) => { - const cabinet = getCabinetInfo(class_.cabinetId); - const studentCount = class_.students.length; - const remainingSpots = cabinet.capacity - studentCount; - - if (remainingSpots === 0) return { label: 'Full', color: 'error' }; - if (remainingSpots < 5) return { label: 'Near Full', color: 'warning' }; - return { label: 'Available', color: 'success' }; + const toggleClassExpansion = (classId: string) => { + setExpandedClasses(prev => + prev.includes(classId) + ? prev.filter(id => id !== classId) + : [...prev, classId] + ); }; - const filteredClasses = useMemo(() => { - let classes = [...mockClasses]; - - // Sort by time - classes.sort((a, b) => { - const aTime = a.schedule[0]?.startTime || ''; - const bTime = b.schedule[0]?.startTime || ''; - return aTime.localeCompare(bTime); - }); - - // Filter by level if not "All Levels" - if (selectedLevel !== 'All Levels') { - classes = classes.filter(c => c.name.includes(selectedLevel)); - } + const renderClassCard = (classInfo: Class) => { + const students = getStudentsForClass(classInfo); + const cabinet = getCabinetInfo(classInfo.cabinetId); + const isExpanded = expandedClasses.includes(classInfo.id); + const scheduleStr = classInfo.schedule + .map(s => `${s.day} ${s.startTime}-${s.endTime}`) + .join(', '); - return classes; - }, [selectedLevel]); - - const getClassForTimeSlot = (day: string, timeSlot: string) => { - const [slotStart] = timeSlot.split('-'); - return filteredClasses.find((class_) => - class_.schedule.some( - (s) => s.day === day && s.startTime === slotStart - ) - ); - }; + return ( + + + + + + {classInfo.name} + + + {scheduleStr} + + + + toggleClassExpansion(classInfo.id)}> + {isExpanded ? : } + + + + + + - const handleLevelChange = (event: React.MouseEvent, newLevel: string) => { - if (newLevel !== null) { - setSelectedLevel(newLevel); - } + + + = cabinet.capacity ? "error" : "success"} + /> + + + + + + + Students + + + {students.map((student) => ( + + } + > + + + ))} + + + + + + ); }; return ( - + + + Class Schedule + + Select Teacher + + + + - - - - - Weekly Timetable - - {LEVELS.map((level) => ( - - {level} - - ))} - - - - - - - - Time - {days.map((day) => ( - - {day} - - ))} - - - - {timeSlots.map((timeSlot) => ( - - - {timeSlot} - - {days.map((day) => { - const class_ = getClassForTimeSlot(day, timeSlot); - const cabinet = class_ ? getCabinetInfo(class_.cabinetId) : null; - const status = class_ ? getClassStatus(class_) : null; - - return ( - - {class_ ? ( - - - - {class_.name} - - - - - - - - - {class_.teacher} - - - - - - - - ) : ( - - No class scheduled - - )} - - ); - })} - - ))} - -
-
-
-
+ + + + Monday/Wednesday/Friday + + {mwfClasses.map(renderClassCard)} + {mwfClasses.length === 0 && ( + + No classes scheduled + + )} + + + + + + Tuesday/Thursday/Saturday + + {ttsClasses.map(renderClassCard)} + {ttsClasses.length === 0 && ( + + No classes scheduled + + )} +
diff --git a/package/src/mock/data.ts b/package/src/mock/data.ts index 3a9250a1..e17fb757 100644 --- a/package/src/mock/data.ts +++ b/package/src/mock/data.ts @@ -14,6 +14,7 @@ export interface Class { name: string; teacher: string; subject: string; + level: 'A1' | 'A2' | 'B1' | 'B2' | 'IELTS'; cabinetId: string; schedule: { day: string; @@ -21,6 +22,9 @@ export interface Class { endTime: string; }[]; students: string[]; // Array of student IDs + courseAmount: number; + createdAt: number; + openingDate?: string; } export interface Cabinet { @@ -62,6 +66,24 @@ export const mockStudents: Student[] = [ status: 'active', paymentStatus: 'pending', }, + { + id: '3', + name: 'Michael Brown', + email: 'michael@example.com', + phone: '+1234567892', + joinDate: '2024-01-25', + status: 'active', + paymentStatus: 'paid', + }, + { + id: '4', + name: 'Emma Wilson', + email: 'emma@example.com', + phone: '+1234567893', + joinDate: '2024-02-01', + status: 'active', + paymentStatus: 'overdue', + }, ]; export const mockCabinets: Cabinet[] = [ @@ -86,9 +108,10 @@ export const mockCabinets: Cabinet[] = [ export const mockClasses: Class[] = [ { id: '1', - name: 'Advanced Mathematics', + name: 'A1 English Basics', teacher: 'Dr. Robert Brown', - subject: 'Mathematics', + subject: 'English', + level: 'A1', cabinetId: '1', schedule: [ { @@ -101,14 +124,23 @@ export const mockClasses: Class[] = [ startTime: '09:00', endTime: '10:30', }, + { + day: 'Friday', + startTime: '09:00', + endTime: '10:30', + }, ], students: ['1', '2'], + courseAmount: 500000, + createdAt: Date.now() - 7 * 24 * 60 * 60 * 1000, + openingDate: new Date().toISOString().split('T')[0], }, { id: '2', - name: 'English Literature', + name: 'B2 Advanced English', teacher: 'Ms. Emily White', subject: 'English', + level: 'B2', cabinetId: '2', schedule: [ { @@ -121,8 +153,43 @@ export const mockClasses: Class[] = [ startTime: '11:00', endTime: '12:30', }, + { + day: 'Saturday', + startTime: '11:00', + endTime: '12:30', + }, + ], + students: ['3', '4'], + courseAmount: 700000, + createdAt: Date.now() - 30 * 24 * 60 * 60 * 1000, + }, + { + id: '3', + name: 'IELTS Preparation', + teacher: 'Dr. Robert Brown', + subject: 'English', + level: 'IELTS', + cabinetId: '1', + schedule: [ + { + day: 'Tuesday', + startTime: '09:00', + endTime: '10:30', + }, + { + day: 'Thursday', + startTime: '09:00', + endTime: '10:30', + }, + { + day: 'Saturday', + startTime: '09:00', + endTime: '10:30', + }, ], - students: ['1'], + students: ['1', '4'], + courseAmount: 900000, + createdAt: Date.now() - 15 * 24 * 60 * 60 * 1000, }, ]; @@ -130,7 +197,7 @@ export const mockPayments: Payment[] = [ { id: '1', studentId: '1', - amount: 500, + amount: 500000, date: '2024-02-01', status: 'completed', method: 'card', @@ -139,7 +206,7 @@ export const mockPayments: Payment[] = [ { id: '2', studentId: '2', - amount: 500, + amount: 500000, date: '2024-02-01', status: 'pending', method: 'transfer', From 178752eb1b38b14766f51970192d017ef801eb56 Mon Sep 17 00:00:00 2001 From: MrFarrukhT Date: Fri, 14 Feb 2025 19:27:35 +0500 Subject: [PATCH 4/8] CRUD updates in teachers, classes, timetable. Minor changes to the styles. --- landingpage/redesign-plan.md | 78 ++++ package/package-lock.json | 6 +- package/package.json | 4 +- .../layout/sidebar/MenuItems.tsx | 39 +- .../layout/sidebar/Sidebar.tsx | 13 +- package/src/app/authentication/login/page.tsx | 4 +- .../src/app/authentication/register/page.tsx | 2 +- .../class-management/classes/ClassDialog.tsx | 251 +++++++++++ .../src/app/class-management/classes/page.tsx | 400 ++++++++++++++---- .../teachers/TeacherDialog.tsx | 194 +++++++++ .../app/class-management/teachers/page.tsx | 324 ++++++++++++++ .../app/class-management/timetable/page.tsx | 357 +++++++++++++--- package/src/mock/data.ts | 34 ++ 13 files changed, 1512 insertions(+), 194 deletions(-) create mode 100644 landingpage/redesign-plan.md create mode 100644 package/src/app/class-management/classes/ClassDialog.tsx create mode 100644 package/src/app/class-management/teachers/TeacherDialog.tsx create mode 100644 package/src/app/class-management/teachers/page.tsx diff --git a/landingpage/redesign-plan.md b/landingpage/redesign-plan.md new file mode 100644 index 00000000..c199e307 --- /dev/null +++ b/landingpage/redesign-plan.md @@ -0,0 +1,78 @@ +# Landing Page Redesign Plan + +## Overview +Transform the current landing page to match the style and effectiveness of Cambridge Online UZ while maintaining the core functionality of showcasing the admin template. + +## Key Changes Required + +### 1. Header Section +- Modernize the navigation bar + - Add language switcher (EN/RU/UZ) + - Keep the logo but update styling + - Maintain download/pro version buttons but with updated design +- Add a hero section with impactful messaging + - Main headline focusing on the product's impact + - Strong value proposition + - Clear call-to-action buttons + +### 2. Main Content Section +- Restructure the comparison section + - Keep the two-column layout (Free vs Pro) + - Update the design to be more visually appealing + - Add hover effects and better spacing + - Improve typography and readability + - Update feature lists with better icons/bullets + +### 3. Visual Elements +- Update color scheme + - Primary: Deep blue (#1A237E) - for trust and professionalism + - Secondary: Light blue (#2196F3) - for CTAs and highlights + - Accent: White and light grays for clean, modern feel +- Add more whitespace for better readability +- Include high-quality images/graphics +- Implement subtle animations for better engagement + +### 4. Technical Improvements +- Update Bootstrap implementation +- Add custom CSS for new components +- Ensure mobile responsiveness +- Optimize performance +- Add smooth scrolling +- Implement modern hover effects + +### 5. Content Updates +- Rewrite headlines to be more impactful +- Update feature descriptions to be more benefit-focused +- Add social proof elements +- Include clear call-to-actions throughout + +## Implementation Steps + +1. Create new CSS file for custom styles +2. Update HTML structure + - Modify header/navigation + - Add hero section + - Restructure comparison section +3. Implement new visual elements +4. Add responsive design rules +5. Test across devices +6. Optimize performance + +## Success Metrics +- Improved visual appeal +- Better user engagement +- Clearer value proposition +- Maintained functionality +- Faster load times +- Better mobile experience + +## Timeline +- Phase 1: HTML Structure Updates (1-2 days) +- Phase 2: Styling and Visual Elements (2-3 days) +- Phase 3: Testing and Optimization (1 day) + +## Next Steps +1. Review and approve design plan +2. Begin HTML structure updates +3. Implement new styling +4. Test and optimize \ No newline at end of file diff --git a/package/package-lock.json b/package/package-lock.json index 4d601d2a..11d1f6dc 100644 --- a/package/package-lock.json +++ b/package/package-lock.json @@ -19,13 +19,13 @@ "@types/node": "20.4.5", "@types/react": "18.2.18", "@types/react-dom": "18.2.7", - "apexcharts": "3.41.1", + "apexcharts": "^3.41.1", "eslint": "8.46.0", "eslint-config-next": "13.4.12", "lodash": "4.17.21", "next": "14.2.3", "react": "18.2.0", - "react-apexcharts": "1.4.1", + "react-apexcharts": "^1.4.1", "react-dom": "18.2.0", "react-helmet-async": "1.3.0", "react-mui-sidebar": "^1.3.8", @@ -2425,6 +2425,7 @@ "version": "3.41.1", "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.41.1.tgz", "integrity": "sha512-kta8fhXrfZYqW7K9kF7FqZ6imQaC6moyRgcUZjwIky/oeHVVISSN/2rjUIvZXnwxWHiSdDHMqLy+TqJhB4DXFA==", + "license": "MIT", "dependencies": { "svg.draggable.js": "^2.2.2", "svg.easing.js": "^2.0.0", @@ -5199,6 +5200,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/react-apexcharts/-/react-apexcharts-1.4.1.tgz", "integrity": "sha512-G14nVaD64Bnbgy8tYxkjuXEUp/7h30Q0U33xc3AwtGFijJB9nHqOt1a6eG0WBn055RgRg+NwqbKGtqPxy15d0Q==", + "license": "MIT", "dependencies": { "prop-types": "^15.8.1" }, diff --git a/package/package.json b/package/package.json index 301751e4..e7a51f4b 100644 --- a/package/package.json +++ b/package/package.json @@ -20,13 +20,13 @@ "@types/node": "20.4.5", "@types/react": "18.2.18", "@types/react-dom": "18.2.7", - "apexcharts": "3.41.1", + "apexcharts": "^3.41.1", "eslint": "8.46.0", "eslint-config-next": "13.4.12", "lodash": "4.17.21", "next": "14.2.3", "react": "18.2.0", - "react-apexcharts": "1.4.1", + "react-apexcharts": "^1.4.1", "react-dom": "18.2.0", "react-helmet-async": "1.3.0", "react-mui-sidebar": "^1.3.8", diff --git a/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx b/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx index 0b3fe677..26f01ead 100644 --- a/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx +++ b/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx @@ -11,6 +11,7 @@ import { IconCalendar, IconCash, IconDoor, + IconChalkboard, } from "@tabler/icons-react"; import { uniqueId } from "lodash"; @@ -37,6 +38,12 @@ const Menuitems = [ icon: IconSchool, href: "/class-management/classes", }, + { + id: uniqueId(), + title: "Teachers", + icon: IconChalkboard, + href: "/class-management/teachers", + }, { id: uniqueId(), title: "Students", @@ -61,22 +68,6 @@ const Menuitems = [ icon: IconDoor, href: "/class-management/cabinets", }, - { - navlabel: true, - subheader: "Utilities", - }, - { - id: uniqueId(), - title: "Typography", - icon: IconTypography, - href: "/utilities/typography", - }, - { - id: uniqueId(), - title: "Shadow", - icon: IconCopy, - href: "/utilities/shadow", - }, { navlabel: true, subheader: "Auth", @@ -93,22 +84,6 @@ const Menuitems = [ icon: IconUserPlus, href: "/authentication/register", }, - { - navlabel: true, - subheader: "Extra", - }, - { - id: uniqueId(), - title: "Icons", - icon: IconMoodHappy, - href: "/icons", - }, - { - id: uniqueId(), - title: "Sample Page", - icon: IconAperture, - href: "/sample-page", - }, ]; export default Menuitems; diff --git a/package/src/app/(DashboardLayout)/layout/sidebar/Sidebar.tsx b/package/src/app/(DashboardLayout)/layout/sidebar/Sidebar.tsx index 26e5b099..cafcac09 100644 --- a/package/src/app/(DashboardLayout)/layout/sidebar/Sidebar.tsx +++ b/package/src/app/(DashboardLayout)/layout/sidebar/Sidebar.tsx @@ -1,6 +1,5 @@ -import { useMediaQuery, Box, Drawer, styled } from "@mui/material"; +import { useMediaQuery, Box, Drawer, styled, Typography } from "@mui/material"; import SidebarItems from "./SidebarItems"; -import { Upgrade } from "./Updrade"; interface ItemType { isMobileSidebarOpen: boolean; @@ -29,10 +28,7 @@ const StyledDrawer = styled(Drawer)(({ theme }) => ({ const LogoWrapper = styled(Box)({ padding: '20px 20px 0px 20px', - '& img': { - maxWidth: '100%', - height: 'auto', - }, + textAlign: 'center', }); const MSidebar = ({ @@ -45,11 +41,12 @@ const MSidebar = ({ const sidebarContent = ( <> - Logo + + Innovative Centre + - ); diff --git a/package/src/app/authentication/login/page.tsx b/package/src/app/authentication/login/page.tsx index a2b33d8b..54c9f872 100644 --- a/package/src/app/authentication/login/page.tsx +++ b/package/src/app/authentication/login/page.tsx @@ -55,7 +55,7 @@ const Login2 = () => { color="textSecondary" mb={1} > - Your Social Campaigns + Welcome to Innovative Centre } subtitle={ @@ -70,7 +70,7 @@ const Login2 = () => { variant="h6" fontWeight="500" > - New to Modernize? + New to Innovative Centre? ( color="textSecondary" mb={1} > - Your Social Campaigns + Welcome to Innovative Centre } subtitle={ diff --git a/package/src/app/class-management/classes/ClassDialog.tsx b/package/src/app/class-management/classes/ClassDialog.tsx new file mode 100644 index 00000000..5e3ec6a1 --- /dev/null +++ b/package/src/app/class-management/classes/ClassDialog.tsx @@ -0,0 +1,251 @@ +'use client'; +import { useState, useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + FormControl, + InputLabel, + Select, + MenuItem, + Grid, + Box, + Typography, + Chip, + IconButton, +} from '@mui/material'; +import { Class, mockTeachers, mockCabinets } from '@/mock/data'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AddIcon from '@mui/icons-material/Add'; + +interface ClassDialogProps { + open: boolean; + onClose: () => void; + onSave: (classData: Partial) => void; + onDelete?: () => void; + classData?: Class; + mode: 'create' | 'edit'; +} + +const defaultSchedule = { + day: 'Monday', + startTime: '09:00', + endTime: '10:30', +}; + +const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; +const levels = ['A1', 'A2', 'B1', 'B2', 'IELTS']; + +const ClassDialog = ({ open, onClose, onSave, onDelete, classData, mode }: ClassDialogProps) => { + const [formData, setFormData] = useState>({ + name: '', + teacher: '', + subject: 'English', + level: 'A1', + cabinetId: '', + schedule: [defaultSchedule], + students: [], + courseAmount: 500000, + }); + + useEffect(() => { + if (classData && mode === 'edit') { + setFormData(classData); + } else { + setFormData({ + name: '', + teacher: '', + subject: 'English', + level: 'A1', + cabinetId: '', + schedule: [defaultSchedule], + students: [], + courseAmount: 500000, + }); + } + }, [classData, mode]); + + const handleChange = (field: keyof Class, value: any) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleScheduleChange = (index: number, field: keyof typeof defaultSchedule, value: string) => { + const newSchedule = [...(formData.schedule || [])]; + newSchedule[index] = { ...newSchedule[index], [field]: value }; + handleChange('schedule', newSchedule); + }; + + const addSchedule = () => { + handleChange('schedule', [...(formData.schedule || []), defaultSchedule]); + }; + + const removeSchedule = (index: number) => { + const newSchedule = [...(formData.schedule || [])]; + newSchedule.splice(index, 1); + handleChange('schedule', newSchedule); + }; + + const handleSubmit = () => { + // Add createdAt and id for new classes + if (mode === 'create') { + const newClassData = { + ...formData, + id: Math.random().toString(36).substr(2, 9), + createdAt: Date.now(), + openingDate: new Date().toISOString().split('T')[0], + }; + onSave(newClassData); + } else { + onSave(formData); + } + }; + + return ( + + + {mode === 'create' ? 'Create New Class' : 'Edit Class'} + + + + + handleChange('name', e.target.value)} + /> + + + + Teacher + + + + + + Level + + + + + + Cabinet + + + + + handleChange('courseAmount', Number(e.target.value))} + /> + + + + + Schedule + + {formData.schedule?.map((schedule, index) => ( + + + Day + + + handleScheduleChange(index, 'startTime', e.target.value)} + InputLabelProps={{ shrink: true }} + inputProps={{ step: 300 }} + /> + handleScheduleChange(index, 'endTime', e.target.value)} + InputLabelProps={{ shrink: true }} + inputProps={{ step: 300 }} + /> + removeSchedule(index)} + disabled={formData.schedule?.length === 1} + > + + + + ))} + + + + + + + {mode === 'edit' && onDelete && ( + + )} + + + + + ); +}; + +export default ClassDialog; \ No newline at end of file diff --git a/package/src/app/class-management/classes/page.tsx b/package/src/app/class-management/classes/page.tsx index 135fbac5..49653859 100644 --- a/package/src/app/class-management/classes/page.tsx +++ b/package/src/app/class-management/classes/page.tsx @@ -1,116 +1,332 @@ 'use client'; +import { useState, useMemo, useCallback } from 'react'; import { Grid, Card, CardContent, Typography, - Button, Box, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, + Stack, IconButton, + List, + ListItem, + ListItemText, Chip, + FormControl, + InputLabel, + Select, + MenuItem, + Paper, + Collapse, + Button, } from '@mui/material'; import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; -import { mockClasses, mockCabinets } from '@/mock/data'; -import { IconEdit, IconTrash, IconCalendar } from '@tabler/icons-react'; +import { mockClasses, mockStudents, mockCabinets, Class, Student } from '@/mock/data'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import EditIcon from '@mui/icons-material/Edit'; +import ClassDialog from './ClassDialog'; + +interface DialogState { + open: boolean; + mode: 'create' | 'edit'; + classData?: Class; +} + +const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('uz-UZ', { + style: 'currency', + currency: 'UZS', + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(amount); +}; const Classes = () => { - const getCabinetName = (cabinetId: string) => { - const cabinet = mockCabinets.find(c => c.id === cabinetId); - return cabinet ? cabinet.name : 'Not assigned'; + const [selectedLevel, setSelectedLevel] = useState('A1'); + const [expandedClasses, setExpandedClasses] = useState([]); + const [classes, setClasses] = useState(mockClasses); + const [dialogState, setDialogState] = useState({ + open: false, + mode: 'create' + }); + + const levels = ['A1', 'A2', 'B1', 'B2', 'IELTS']; + + const handleOpenCreate = () => { + setDialogState({ + open: true, + mode: 'create' + }); + }; + + const handleOpenEdit = (classData: Class) => { + setDialogState({ + open: true, + mode: 'edit', + classData + }); + }; + + const handleClose = () => { + setDialogState({ + open: false, + mode: 'create' + }); + }; + + const handleSave = (classData: Partial) => { + if (dialogState.mode === 'create') { + setClasses(prev => [...prev, classData as Class]); + } else { + setClasses(prev => + prev.map(c => c.id === classData.id ? { ...c, ...classData } : c) + ); + } + handleClose(); + }; + + const handleDelete = useCallback(() => { + if (dialogState.classData) { + setClasses(prev => prev.filter(c => c.id !== dialogState.classData?.id)); + handleClose(); + } + }, [dialogState.classData]); + + const getTimeUntilStart = (openingDate?: string) => { + if (!openingDate) return null; + + const start = new Date(openingDate); + const now = new Date(); + const diffDays = Math.ceil((start.getTime() - now.getTime()) / (1000 * 60 * 60 * 24)); + + return diffDays; + }; + + const getStartDateColor = (daysUntilStart: number | null) => { + if (daysUntilStart === null) return 'default'; + if (daysUntilStart <= 0) return 'success'; + if (daysUntilStart <= 3) return 'warning'; + if (daysUntilStart <= 7) return 'info'; + return 'default'; }; - const formatSchedule = (schedule: { day: string; startTime: string; endTime: string }[]) => { - return schedule.map(s => `${s.day} ${s.startTime}-${s.endTime}`).join(', '); + const getCabinetFillColor = (students: Student[], capacity: number) => { + const remainingSeats = capacity - students.length; + if (remainingSeats <= 0) return 'error'; + if (remainingSeats <= 2) return 'error'; + if (remainingSeats <= 5) return 'warning'; + return 'success'; + }; + + const filteredClasses = useMemo(() => { + return classes.filter(c => c.level === selectedLevel); + }, [selectedLevel, classes]); + + const mwfClasses = useMemo(() => + filteredClasses.filter(c => + c.schedule.some(s => ['Monday', 'Wednesday', 'Friday'].includes(s.day)) + ), + [filteredClasses] + ); + + const ttsClasses = useMemo(() => + filteredClasses.filter(c => + c.schedule.some(s => ['Tuesday', 'Thursday', 'Saturday'].includes(s.day)) + ), + [filteredClasses] + ); + + const getStudentsForClass = (classInfo: Class) => { + return mockStudents.filter(student => classInfo.students.includes(student.id)); + }; + + const getCabinetInfo = (cabinetId: string) => { + const cabinet = mockCabinets.find((c) => c.id === cabinetId); + return { + name: cabinet ? cabinet.name : 'Not assigned', + capacity: cabinet ? cabinet.capacity : 0, + }; + }; + + const toggleClassExpansion = (classId: string) => { + setExpandedClasses(prev => + prev.includes(classId) + ? prev.filter(id => id !== classId) + : [...prev, classId] + ); + }; + + const renderClassCard = (classInfo: Class) => { + const students = getStudentsForClass(classInfo); + const cabinet = getCabinetInfo(classInfo.cabinetId); + const isExpanded = expandedClasses.includes(classInfo.id); + const scheduleStr = classInfo.schedule.length > 0 + ? `${classInfo.schedule[0].startTime} - ${classInfo.schedule[0].endTime}` + : 'No time set'; + + const daysUntilStart = getTimeUntilStart(classInfo.openingDate); + const startDateColor = getStartDateColor(daysUntilStart); + const cabinetFillColor = getCabinetFillColor(students, cabinet.capacity); + + const startDateText = daysUntilStart !== null + ? daysUntilStart <= 0 + ? 'Started' + : `Starts in ${daysUntilStart} days` + : 'Start date not set'; + + return ( + + + + + + {classInfo.level} + + + Teacher: {classInfo.teacher} + + + {scheduleStr} + + + + toggleClassExpansion(classInfo.id)}> + {isExpanded ? : } + + handleOpenEdit(classInfo)}> + + + + + + + + + + + + + + + Students + + + {students.map((student) => ( + + + + {student.name} + {student.phone} + + + + + ))} + + + + + + ); }; return ( - + + + + Classes + + + + Select Level + + + + - - - - - Classes - - - - - - - - Class Name - Teacher - Subject - Cabinet - Schedule - Students - Actions - - - - {mockClasses.map((class_) => ( - - - - {class_.name} - - - {class_.teacher} - - - - - - - - - - - {formatSchedule(class_.schedule)} - - - - - - - - - - - - - - - - ))} - -
-
-
-
+ + + + Monday/Wednesday/Friday + + {mwfClasses.map(renderClassCard)} + {mwfClasses.length === 0 && ( + + No classes scheduled + + )} + + + + + + Tuesday/Thursday/Saturday + + {ttsClasses.map(renderClassCard)} + {ttsClasses.length === 0 && ( + + No classes scheduled + + )} +
+ +
); }; diff --git a/package/src/app/class-management/teachers/TeacherDialog.tsx b/package/src/app/class-management/teachers/TeacherDialog.tsx new file mode 100644 index 00000000..d1c9ad2c --- /dev/null +++ b/package/src/app/class-management/teachers/TeacherDialog.tsx @@ -0,0 +1,194 @@ +import React from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Box, + FormControl, + InputLabel, + Select, + MenuItem, + Chip, + OutlinedInput, +} from '@mui/material'; +import { Teacher } from '@/mock/data'; + +interface TeacherDialogProps { + open: boolean; + onClose: () => void; + onSave: (teacher: Partial) => void; + teacher?: Teacher; +} + +const SUBJECTS = ['English', 'Math', 'Science', 'History', 'Geography']; +const QUALIFICATIONS = [ + 'PhD', + 'Masters', + 'Bachelors', + 'CELTA', + 'DELTA', + 'TESOL', + 'TEFL', +]; + +const TeacherDialog: React.FC = ({ + open, + onClose, + onSave, + teacher, +}) => { + const [formData, setFormData] = React.useState>({ + name: '', + email: '', + phone: '', + subjects: [], + qualifications: [], + status: 'active', + ...teacher, + }); + + React.useEffect(() => { + if (teacher) { + setFormData(teacher); + } else { + setFormData({ + name: '', + email: '', + phone: '', + subjects: [], + qualifications: [], + status: 'active', + }); + } + }, [teacher]); + + const handleChange = (e: React.ChangeEvent) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value, + }); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave({ + ...formData, + joinDate: formData.joinDate || new Date().toISOString().split('T')[0], + }); + }; + + return ( + +
+ + {teacher ? 'Edit Teacher' : 'Add New Teacher'} + + + + + + + + Subjects + + + + Qualifications + + + + Status + + + + + + + + +
+
+ ); +}; + +export default TeacherDialog; \ No newline at end of file diff --git a/package/src/app/class-management/teachers/page.tsx b/package/src/app/class-management/teachers/page.tsx new file mode 100644 index 00000000..ddf7ed67 --- /dev/null +++ b/package/src/app/class-management/teachers/page.tsx @@ -0,0 +1,324 @@ +'use client'; +import { + Card, + Grid, + Typography, + Box, + Chip, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Paper, + Button, + IconButton, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Alert, +} from '@mui/material'; +import { IconEdit, IconTrash, IconPlus } from '@tabler/icons-react'; +import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; +import DashboardCard from '@/app/(DashboardLayout)/components/shared/DashboardCard'; +import { mockTeachers, mockClasses, mockStudents, Teacher } from '@/mock/data'; +import TeacherDialog from './TeacherDialog'; +import { useState } from 'react'; + +const TeachersPage = () => { + const [teachers, setTeachers] = useState(mockTeachers); + const [openDialog, setOpenDialog] = useState(false); + const [selectedTeacher, setSelectedTeacher] = useState(); + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + const [deleteTeacher, setDeleteTeacher] = useState(null); + + const handleAddTeacher = () => { + setSelectedTeacher(undefined); + setOpenDialog(true); + }; + + const handleEditTeacher = (teacher: Teacher) => { + setSelectedTeacher(teacher); + setOpenDialog(true); + }; + + const handleDeleteClick = (teacher: Teacher) => { + setDeleteTeacher(teacher); + setDeleteConfirmOpen(true); + }; + + const handleDeleteConfirm = () => { + if (deleteTeacher) { + setTeachers(teachers.filter(t => t.id !== deleteTeacher.id)); + setDeleteConfirmOpen(false); + setDeleteTeacher(null); + } + }; + + const handleSaveTeacher = (teacherData: Partial) => { + if (selectedTeacher) { + // Edit existing teacher + setTeachers(teachers.map(t => + t.id === selectedTeacher.id ? { ...selectedTeacher, ...teacherData } : t + )); + } else { + // Add new teacher + const newTeacher: Teacher = { + id: (Math.max(...teachers.map(t => parseInt(t.id))) + 1).toString(), + ...teacherData as Omit, + }; + setTeachers([...teachers, newTeacher]); + } + setOpenDialog(false); + }; + + // Calculate statistics for each teacher + const teacherStats = teachers.map(teacher => { + const teacherClasses = mockClasses.filter(cls => cls.teacher === teacher.name); + const uniqueStudentIds = new Set(teacherClasses.flatMap(cls => cls.students)); + const teacherStudents = Array.from(uniqueStudentIds).map(id => + mockStudents.find(student => student.id === id) + ).filter(Boolean); + + const paidStudents = teacherStudents.filter(student => student?.paymentStatus === 'paid'); + const unpaidStudents = teacherStudents.filter(student => student?.paymentStatus !== 'paid'); + const newStudents = teacherStudents.filter(student => { + const joinDate = new Date(student?.joinDate || ''); + const oneMonthAgo = new Date(); + oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1); + return joinDate >= oneMonthAgo; + }); + + // Group classes by level + const levelGroups = teacherClasses.reduce((acc, cls) => { + acc[cls.level] = (acc[cls.level] || 0) + 1; + return acc; + }, {} as Record); + + return { + ...teacher, + classCount: teacherClasses.length, + totalStudents: teacherStudents.length, + paidStudents: paidStudents.length, + unpaidStudents: unpaidStudents.length, + newStudents: newStudents.length, + levelGroups, + }; + }); + + return ( + + {/* Add Button */} + + + + + + {/* Overall Statistics */} + + + + + + {mockTeachers.length} + Total Teachers + + + + + {mockClasses.length} + Total Classes + + + + + + {(mockClasses.length / mockTeachers.length).toFixed(1)} + + Avg Classes per Teacher + + + + + + {(mockStudents.length / mockTeachers.length).toFixed(1)} + + Avg Students per Teacher + + + + + + + {/* Teacher Details Table */} + + + + + + + Teacher + Classes + Students + New Students + Paid/Unpaid + Groups by Level + Actions + + + + {teacherStats.map((teacher) => ( + + + + + {teacher.name} + + + {teacher.email} + + + + {teacher.classCount} + {teacher.totalStudents} + {teacher.newStudents} + + + + + + + + + {Object.entries(teacher.levelGroups).map(([level, count]) => ( + + ))} + + + + + handleEditTeacher(teacher)} + > + + + handleDeleteClick(teacher)} + > + + + + + + ))} + +
+
+
+
+ + {/* Individual Teacher Cards */} + {teacherStats.map((teacher) => ( + + + + + + + Contact: {teacher.email} | {teacher.phone} + + + Joined: {new Date(teacher.joinDate).toLocaleDateString()} + + + + {teacher.qualifications.map((qual, index) => ( + + ))} + + + + + {teacher.classCount} + Classes + + + + + {teacher.totalStudents} + Students + + + + + {teacher.newStudents} + New + + + + + + {((teacher.paidStudents / teacher.totalStudents) * 100).toFixed(0)}% + + Payment Rate + + + + + + ))} +
+ + {/* Add/Edit Teacher Dialog */} + setOpenDialog(false)} + onSave={handleSaveTeacher} + teacher={selectedTeacher} + /> + + {/* Delete Confirmation Dialog */} + setDeleteConfirmOpen(false)}> + Delete Teacher + + + Are you sure you want to delete {deleteTeacher?.name}? This action cannot be undone. + + + + + + + +
+ ); +}; + +export default TeachersPage; \ No newline at end of file diff --git a/package/src/app/class-management/timetable/page.tsx b/package/src/app/class-management/timetable/page.tsx index 6fb767b8..a8788b48 100644 --- a/package/src/app/class-management/timetable/page.tsx +++ b/package/src/app/class-management/timetable/page.tsx @@ -1,5 +1,5 @@ 'use client'; -import { useState, useMemo } from 'react'; +import { useState, useMemo, useCallback, useEffect } from 'react'; import { Grid, Card, @@ -20,12 +20,31 @@ import { Collapse, Tooltip, useTheme, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, } from '@mui/material'; import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; import { mockClasses, mockStudents, mockCabinets, Class, Student } from '@/mock/data'; +import AddIcon from '@mui/icons-material/Add'; +import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import EditIcon from '@mui/icons-material/Edit'; +import ClassDialog from '../classes/ClassDialog'; + +interface DialogState { + open: boolean; + mode: 'create' | 'edit'; + classData?: Class; +} + +interface StudentDialogState { + open: boolean; + classId: string | null; +} const formatCurrency = (amount: number) => { return new Intl.NumberFormat('uz-UZ', { @@ -38,21 +57,127 @@ const formatCurrency = (amount: number) => { const Timetable = () => { const theme = useTheme(); - const [selectedTeacher, setSelectedTeacher] = useState('All Teachers'); + const [classes, setClasses] = useState(mockClasses); + const [dialogState, setDialogState] = useState({ + open: false, + mode: 'create' + }); + const [studentDialogState, setStudentDialogState] = useState({ + open: false, + classId: null + }); + const [availableStudents, setAvailableStudents] = useState([]); + + // Get first teacher as default instead of 'All Teachers' + const teachers = useMemo(() => + Array.from(new Set(classes.map(c => c.teacher))), + [classes] + ); + const [selectedTeacher, setSelectedTeacher] = useState(teachers[0] || ''); const [expandedClasses, setExpandedClasses] = useState([]); - const teachers = useMemo(() => { - const uniqueTeachers = Array.from(new Set(mockClasses.map(c => c.teacher))); - return ['All Teachers', ...uniqueTeachers]; - }, []); + const handleOpenCreate = () => { + setDialogState({ + open: true, + mode: 'create' + }); + }; + + const handleOpenEdit = (classData: Class) => { + setDialogState({ + open: true, + mode: 'edit', + classData + }); + }; + + const handleClose = () => { + setDialogState({ + open: false, + mode: 'create' + }); + }; + + const handleSave = (classData: Partial) => { + if (dialogState.mode === 'create') { + setClasses(prev => [...prev, classData as Class]); + } else { + setClasses(prev => + prev.map(c => c.id === classData.id ? { ...c, ...classData } : c) + ); + } + handleClose(); + }; - const filteredClasses = useMemo(() => { - let classes = [...mockClasses]; - if (selectedTeacher !== 'All Teachers') { - classes = classes.filter(c => c.teacher === selectedTeacher); + const handleDelete = useCallback(() => { + if (dialogState.classData) { + setClasses(prev => prev.filter(c => c.id !== dialogState.classData?.id)); + handleClose(); } - return classes; - }, [selectedTeacher]); + }, [dialogState.classData]); + + const handleOpenAddStudents = (classId: string) => { + const currentClass = classes.find(c => c.id === classId); + if (currentClass) { + // Filter out students already in the class + const studentsNotInClass = mockStudents.filter( + student => !currentClass.students.includes(student.id) + ); + setAvailableStudents(studentsNotInClass); + setStudentDialogState({ + open: true, + classId + }); + } + }; + + const handleCloseAddStudents = () => { + setStudentDialogState({ + open: false, + classId: null + }); + }; + + const handleDeleteStudent = (classId: string, studentId: string) => { + setClasses(prev => + prev.map(c => + c.id === classId + ? { ...c, students: c.students.filter(id => id !== studentId) } + : c + ) + ); + }; + + const handleAddStudent = (studentId: string) => { + if (studentDialogState.classId) { + const currentClass = classes.find(c => c.id === studentDialogState.classId); + const cabinet = mockCabinets.find(c => c.id === currentClass?.cabinetId); + + if (currentClass && cabinet) { + // Check if adding this student would exceed cabinet capacity + if (currentClass.students.length >= cabinet.capacity) { + alert('Cannot add more students. Cabinet capacity reached.'); + return; + } + + setClasses(prev => + prev.map(c => + c.id === studentDialogState.classId + ? { ...c, students: [...c.students, studentId] } + : c + ) + ); + + // Update available students list + setAvailableStudents(prev => prev.filter(s => s.id !== studentId)); + } + } + }; + + const filteredClasses = useMemo(() => + classes.filter(c => c.teacher === selectedTeacher), + [selectedTeacher, classes] + ); const mwfClasses = useMemo(() => filteredClasses.filter(c => @@ -88,84 +213,157 @@ const Timetable = () => { ); }; + const getClassStatus = (startTime: string) => { + const [hours, minutes] = startTime.split(':').map(Number); + const classTime = new Date(); + classTime.setHours(hours, minutes, 0); + + const now = new Date(); + const diffMs = now.getTime() - classTime.getTime(); + const diffMins = Math.floor(diffMs / 60000); + + if (diffMins >= -15 && diffMins <= 90) { // Class is in progress (including 15 min before) + return 'in-progress'; + } else if (diffMins < -15) { // More than 15 min before class + return 'upcoming'; + } else { // Class has ended + return 'completed'; + } + }; + const renderClassCard = (classInfo: Class) => { const students = getStudentsForClass(classInfo); const cabinet = getCabinetInfo(classInfo.cabinetId); const isExpanded = expandedClasses.includes(classInfo.id); - const scheduleStr = classInfo.schedule - .map(s => `${s.day} ${s.startTime}-${s.endTime}`) - .join(', '); + const scheduleStr = classInfo.schedule.length > 0 + ? `${classInfo.schedule[0].startTime} - ${classInfo.schedule[0].endTime}` + : 'No time set'; + + const classStatus = classInfo.schedule.length > 0 + ? getClassStatus(classInfo.schedule[0].startTime) + : 'no-time'; + + const getStatusColor = () => { + switch (classStatus) { + case 'in-progress': + return theme.palette.success.main; + case 'upcoming': + return theme.palette.info.main; + case 'completed': + return theme.palette.text.secondary; + default: + return theme.palette.warning.main; + } + }; return ( - + - - {classInfo.name} - - - {scheduleStr} + + {classInfo.level} + + + {scheduleStr} + + + + + toggleClassExpansion(classInfo.id)}> {isExpanded ? : } - + handleOpenEdit(classInfo)}> - - = cabinet.capacity ? "error" : "success"} - /> - - - Students - - + + + Students + + + + {students.map((student) => ( - - } - > - - + + + + {student.name} + {student.phone} + + + + handleDeleteStudent(classInfo.id, student.id)} + > + + + + + ))} - + @@ -221,6 +419,55 @@ const Timetable = () => { + + + + + Add Student to Class + + + {availableStudents.map((student) => ( + handleAddStudent(student.id)} + > + Add + + } + > + + + ))} + {availableStudents.length === 0 && ( + + + + )} + + + + + +
); }; diff --git a/package/src/mock/data.ts b/package/src/mock/data.ts index e17fb757..e2606fb4 100644 --- a/package/src/mock/data.ts +++ b/package/src/mock/data.ts @@ -1,4 +1,15 @@ // Types +export interface Teacher { + id: string; + name: string; + email: string; + phone: string; + subjects: string[]; + qualifications: string[]; + joinDate: string; + status: 'active' | 'inactive'; +} + export interface Student { id: string; name: string; @@ -193,6 +204,29 @@ export const mockClasses: Class[] = [ }, ]; +export const mockTeachers: Teacher[] = [ + { + id: '1', + name: 'Dr. Robert Brown', + email: 'robert.brown@example.com', + phone: '+1234567894', + subjects: ['English'], + qualifications: ['PhD in English Literature', 'CELTA', 'DELTA'], + joinDate: '2023-09-01', + status: 'active', + }, + { + id: '2', + name: 'Ms. Emily White', + email: 'emily.white@example.com', + phone: '+1234567895', + subjects: ['English'], + qualifications: ['MA in TESOL', 'CELTA'], + joinDate: '2023-10-15', + status: 'active', + }, +]; + export const mockPayments: Payment[] = [ { id: '1', From 1d700c96cfddab03d7f2eae0ea25ee705be68da7 Mon Sep 17 00:00:00 2001 From: MrFarrukhT <130832655+MrFarrukhT@users.noreply.github.com> Date: Fri, 14 Feb 2025 19:29:04 +0500 Subject: [PATCH 5/8] Update README.md --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 9bfbde65..65ad153d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,7 @@ # Modernize-nextjs-free -Modernize Free Next.js 14 Admin Template with Material Ui + Typescript -Give a Star - - - - - + # Installation 👨🏻‍💻 > We recommend you use npm From 1583d891ada139e087cf4a87a4c330910eba837c Mon Sep 17 00:00:00 2001 From: MrFarrukhT Date: Sat, 15 Feb 2025 16:10:59 +0500 Subject: [PATCH 6/8] Refactor dashboard layout by removing login links, enhancing logo display, and adding stats and status components for students, teachers, classes, and cabinets. --- .../layout/header/Header.tsx | 9 - .../layout/shared/logo/Logo.tsx | 32 +++- .../layout/sidebar/MenuItems.tsx | 18 -- package/src/app/(DashboardLayout)/page.tsx | 158 +++++++++++++++--- 4 files changed, 158 insertions(+), 59 deletions(-) diff --git a/package/src/app/(DashboardLayout)/layout/header/Header.tsx b/package/src/app/(DashboardLayout)/layout/header/Header.tsx index 9cedbf63..d4db1b4b 100644 --- a/package/src/app/(DashboardLayout)/layout/header/Header.tsx +++ b/package/src/app/(DashboardLayout)/layout/header/Header.tsx @@ -74,15 +74,6 @@ const Header = ({ toggleMobileSidebar, toggleSidebar, isSidebarOpen }: ItemType) - diff --git a/package/src/app/(DashboardLayout)/layout/shared/logo/Logo.tsx b/package/src/app/(DashboardLayout)/layout/shared/logo/Logo.tsx index 7a6479f3..0b2c85a1 100644 --- a/package/src/app/(DashboardLayout)/layout/shared/logo/Logo.tsx +++ b/package/src/app/(DashboardLayout)/layout/shared/logo/Logo.tsx @@ -1,18 +1,36 @@ import Link from "next/link"; -import { styled } from "@mui/material"; -import Image from "next/image"; +import { styled, Typography } from "@mui/material"; const LinkStyled = styled(Link)(() => ({ - height: "70px", - width: "180px", - overflow: "hidden", - display: "block", + textDecoration: "none", + display: "flex", + alignItems: "center", })); const Logo = () => { return ( - logo + + Innovative + + Centre + + ); }; diff --git a/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx b/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx index 26f01ead..7d4a9fb9 100644 --- a/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx +++ b/package/src/app/(DashboardLayout)/layout/sidebar/MenuItems.tsx @@ -2,10 +2,8 @@ import { IconAperture, IconCopy, IconLayoutDashboard, - IconLogin, IconMoodHappy, IconTypography, - IconUserPlus, IconSchool, IconUsers, IconCalendar, @@ -68,22 +66,6 @@ const Menuitems = [ icon: IconDoor, href: "/class-management/cabinets", }, - { - navlabel: true, - subheader: "Auth", - }, - { - id: uniqueId(), - title: "Login", - icon: IconLogin, - href: "/authentication/login", - }, - { - id: uniqueId(), - title: "Register", - icon: IconUserPlus, - href: "/authentication/register", - }, ]; export default Menuitems; diff --git a/package/src/app/(DashboardLayout)/page.tsx b/package/src/app/(DashboardLayout)/page.tsx index 5ab70994..53f28c9c 100644 --- a/package/src/app/(DashboardLayout)/page.tsx +++ b/package/src/app/(DashboardLayout)/page.tsx @@ -1,40 +1,148 @@ 'use client' -import { Grid, Box } from '@mui/material'; +import { Grid, Box, Card, Typography, Stack } from '@mui/material'; import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; -// components -import SalesOverview from '@/app/(DashboardLayout)/components/dashboard/SalesOverview'; -import YearlyBreakup from '@/app/(DashboardLayout)/components/dashboard/YearlyBreakup'; -import RecentTransactions from '@/app/(DashboardLayout)/components/dashboard/RecentTransactions'; -import ProductPerformance from '@/app/(DashboardLayout)/components/dashboard/ProductPerformance'; -import Blog from '@/app/(DashboardLayout)/components/dashboard/Blog'; -import MonthlyEarnings from '@/app/(DashboardLayout)/components/dashboard/MonthlyEarnings'; +import { mockStudents, mockTeachers, mockClasses, mockCabinets, mockPayments } from '@/mock/data'; + +// Stats Card Component +const StatsCard = ({ title, value, color }: { title: string; value: string | number; color: string }) => ( + + + + {title} + + + {value} + + + +); + +// Class Levels Component +const ClassLevels = () => { + const levels = mockClasses.reduce((acc, cls) => { + acc[cls.level] = (acc[cls.level] || 0) + 1; + return acc; + }, {} as Record); + + return ( + + Class Distribution + + {Object.entries(levels).map(([level, count]) => ( + + {level} + {count} classes + + ))} + + + ); +}; + +// Recent Payments Component +const RecentPayments = () => ( + + Recent Payments + + {mockPayments.map(payment => { + const student = mockStudents.find(s => s.id === payment.studentId); + return ( + + + {student?.name} + + {payment.date} + + + + ${payment.amount.toLocaleString()} + + + ); + })} + + +); + +// Cabinet Status Component +const CabinetStatus = () => ( + + Cabinet Status + + {mockCabinets.map(cabinet => ( + + + {cabinet.name} + + Capacity: {cabinet.capacity} + + + + {cabinet.status.charAt(0).toUpperCase() + cabinet.status.slice(1)} + + + ))} + + +); const Dashboard = () => { + const activeStudents = mockStudents.filter(s => s.status === 'active').length; + const activeTeachers = mockTeachers.filter(t => t.status === 'active').length; + const totalClasses = mockClasses.length; + return ( - + - - + {/* Stats Overview */} + + - - - - - - - - - + + - - + + - - + + {/* Class Levels */} + + + + {/* Recent Payments */} + + + + + {/* Cabinet Status */} - + From 417e46c94194fa7ed861bae0c736dc2e4fb11c61 Mon Sep 17 00:00:00 2001 From: MrFarrukhT Date: Mon, 17 Feb 2025 12:25:22 +0500 Subject: [PATCH 7/8] Refactor payment status handling and implement student management dialog with CRUD functionality --- package/src/app/(DashboardLayout)/page.tsx | 2 +- .../app/class-management/payments/page.tsx | 572 +++++++++++++++--- .../students/StudentDialog.tsx | 144 +++++ .../app/class-management/students/page.tsx | 115 +++- package/src/mock/data.ts | 6 +- 5 files changed, 749 insertions(+), 90 deletions(-) create mode 100644 package/src/app/class-management/students/StudentDialog.tsx diff --git a/package/src/app/(DashboardLayout)/page.tsx b/package/src/app/(DashboardLayout)/page.tsx index 53f28c9c..88ae8aa6 100644 --- a/package/src/app/(DashboardLayout)/page.tsx +++ b/package/src/app/(DashboardLayout)/page.tsx @@ -56,7 +56,7 @@ const RecentPayments = () => ( ${payment.amount.toLocaleString()} diff --git a/package/src/app/class-management/payments/page.tsx b/package/src/app/class-management/payments/page.tsx index 89ed52da..2c1d02f6 100644 --- a/package/src/app/class-management/payments/page.tsx +++ b/package/src/app/class-management/payments/page.tsx @@ -14,19 +14,175 @@ import { TableRow, Chip, IconButton, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + MenuItem, + Stack, + Tabs, + Tab, + Select, + FormControl, + InputLabel, + Autocomplete, + InputAdornment, } from '@mui/material'; +import { useState, useRef, ChangeEvent, useMemo } from 'react'; import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; -import { mockPayments, mockStudents } from '@/mock/data'; -import { IconEdit, IconTrash, IconReceipt } from '@tabler/icons-react'; +import { mockPayments, mockStudents, Payment } from '@/mock/data'; +import { IconEdit, IconTrash, IconReceipt, IconFilter } from '@tabler/icons-react'; + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +const TabPanel = (props: TabPanelProps) => { + const { children, value, index, ...other } = props; + return ( + + ); +}; const Payments = () => { + // State management + const [payments, setPayments] = useState(mockPayments); + const [selectedPayment, setSelectedPayment] = useState(null); + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + const [tabValue, setTabValue] = useState(0); + const [selectedStudent, setSelectedStudent] = useState('all'); + const [searchQuery, setSearchQuery] = useState(''); + + // Form refs + const studentIdRef = useRef(null); + const amountRef = useRef(null); + const dateRef = useRef(null); + const methodRef = useRef(null); + const statusRef = useRef(null); + const descriptionRef = useRef(null); + + // CRUD Functions + /** + * Creates a new payment or updates an existing one + * @param paymentData Payment data to be saved + */ + const savePayment = () => { + const paymentData = { + studentId: (studentIdRef.current as any)?.value, + amount: Number((amountRef.current as any)?.value), + date: (dateRef.current as any)?.value, + method: (methodRef.current as any)?.value as 'cash' | 'card' | 'transfer', + status: (statusRef.current as any)?.value as 'paid' | 'unpaid', + description: (descriptionRef.current as any)?.value, + }; + + if (selectedPayment) { + // Update existing payment + setPayments(payments.map(payment => + payment.id === selectedPayment.id + ? { ...payment, ...paymentData } + : payment + )); + } else { + // Create new payment + const newPayment: Payment = { + ...paymentData, + id: (Math.max(...payments.map(p => Number(p.id))) + 1).toString(), + }; + setPayments([...payments, newPayment]); + } + handleCloseDialog(); + }; + + /** + * Deletes a payment + * @param paymentId ID of the payment to delete + */ + const deletePayment = (paymentId: string) => { + setPayments(payments.filter(payment => payment.id !== paymentId)); + setIsDeleteDialogOpen(false); + }; + + // Filter functions + const getFilteredPayments = () => { + let filtered = payments; + + // Filter by selected student + if (selectedStudent !== 'all') { + filtered = filtered.filter(payment => payment.studentId === selectedStudent); + } + + // Filter by search query + if (searchQuery) { + const query = searchQuery.toLowerCase(); + filtered = filtered.filter(payment => { + const student = mockStudents.find(s => s.id === payment.studentId); + return ( + payment.id.toLowerCase().includes(query) || + (student?.name.toLowerCase().includes(query)) || + payment.description.toLowerCase().includes(query) || + payment.amount.toString().includes(query) || + payment.method.toLowerCase().includes(query) || + payment.status.toLowerCase().includes(query) + ); + }); + } + + return filtered; + }; + + const getStudentPaymentStats = (studentId: string) => { + const studentPayments = payments.filter(p => p.studentId === studentId); + return { + total: studentPayments.reduce((sum, p) => sum + p.amount, 0), + paid: studentPayments.filter(p => p.status === 'paid').length, + unpaid: studentPayments.filter(p => p.status === 'unpaid').length, + }; + }; + + // Event handlers + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { + setTabValue(newValue); + }; + + const handleStudentChange = (event: ChangeEvent<{ value: unknown }>) => { + setSelectedStudent(event.target.value as string); + }; + + // Dialog handlers + const handleOpenDialog = (payment?: Payment) => { + setSelectedPayment(payment || null); + setIsDialogOpen(true); + }; + + const handleCloseDialog = () => { + setSelectedPayment(null); + setIsDialogOpen(false); + }; + + const handleOpenDeleteDialog = (payment: Payment) => { + setSelectedPayment(payment); + setIsDeleteDialogOpen(true); + }; + + // Utility functions const getStatusColor = (status: string) => { switch (status) { - case 'completed': + case 'paid': return 'success'; - case 'pending': - return 'warning'; - case 'failed': + case 'unpaid': return 'error'; default: return 'default'; @@ -52,10 +208,9 @@ const Payments = () => { }; const formatCurrency = (amount: number) => { - return new Intl.NumberFormat('en-US', { - style: 'currency', - currency: 'USD' - }).format(amount); + // Format number with spaces between thousands + const formattedNumber = amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' '); + return `${formattedNumber} UZS`; }; return ( @@ -64,88 +219,341 @@ const Payments = () => { - - Payments - + + + + + - - - - - Payment ID - Student - Amount - Date - Method - Status - Description - Actions - - - - {mockPayments.map((payment) => ( - - - - - #{payment.id} - - - - - {getStudentName(payment.studentId)} + + + Payments + + setSearchQuery(e.target.value)} + size="small" + sx={{ width: 300 }} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + + + +
+ + + Payment ID + Student + Amount + Date + Method + Status + Description + Actions + + + + {getFilteredPayments().map((payment) => ( + + + + + #{payment.id} + + + + + {getStudentName(payment.studentId)} + + + + + {formatCurrency(payment.amount)} + + + {new Date(payment.date).toLocaleDateString()} + + + + + + + + + {payment.description} + + + + handleOpenDialog(payment)} + > + + + handleOpenDeleteDialog(payment)} + > + + + + + ))} + +
+
+ + + + + Student Payment History + + Select Student + + + + + {selectedStudent !== 'all' && ( + + + + + + Total Payments + + + {formatCurrency(getStudentPaymentStats(selectedStudent).total)} + + + + + + + + + Paid - - - - {formatCurrency(payment.amount)} + + {getStudentPaymentStats(selectedStudent).paid} - - {new Date(payment.date).toLocaleDateString()} - - - - - - - - - {payment.description} + + + + + + + + Unpaid - - - - - - - - - + + {getStudentPaymentStats(selectedStudent).unpaid} + + + + + + )} + + + + + + Payment ID + Student + Amount + Date + Method + Status + Description - ))} - -
-
+ + + {getFilteredPayments().map((payment) => ( + + + + + #{payment.id} + + + + + {getStudentName(payment.studentId)} + + + + + {formatCurrency(payment.amount)} + + + {new Date(payment.date).toLocaleDateString()} + + + + + + + + + {payment.description} + + + + ))} + + + +
+ + {/* Payment Dialog */} + + + {selectedPayment ? 'Edit Payment' : 'Record New Payment'} + + + + + {mockStudents.map((student) => ( + + {student.name} + + ))} + + + + + Cash + Card + Transfer + + + Paid + Unpaid + + + + + + + + + + + {/* Delete Confirmation Dialog */} + setIsDeleteDialogOpen(false)}> + Confirm Delete + + + Are you sure you want to delete payment #{selectedPayment?.id}? + + + + + + +
); }; diff --git a/package/src/app/class-management/students/StudentDialog.tsx b/package/src/app/class-management/students/StudentDialog.tsx new file mode 100644 index 00000000..d08487cf --- /dev/null +++ b/package/src/app/class-management/students/StudentDialog.tsx @@ -0,0 +1,144 @@ +import React from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Box, + FormControl, + InputLabel, + Select, + MenuItem, + Chip, +} from '@mui/material'; +import { Student } from '@/mock/data'; + +interface StudentDialogProps { + open: boolean; + onClose: () => void; + onSave: (student: Partial) => void; + student?: Student; +} + +const StudentDialog: React.FC = ({ + open, + onClose, + onSave, + student, +}) => { + const [formData, setFormData] = React.useState>({ + name: '', + email: '', + phone: '', + status: 'active', + ...student, + }); + + React.useEffect(() => { + if (student) { + setFormData(student); + } else { + setFormData({ + name: '', + email: '', + phone: '', + status: 'active', + }); + } + }, [student]); + + const handleChange = (e: React.ChangeEvent) => { + setFormData({ + ...formData, + [e.target.name]: e.target.value, + }); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSave({ + ...formData, + joinDate: formData.joinDate || new Date().toISOString().split('T')[0], + paymentStatus: formData.paymentStatus || 'pending', // Default payment status for new students + }); + }; + + return ( + +
+ + {student ? 'Edit Student' : 'Add New Student'} + + + + + + + + Status + + + {student && ( + + + + )} + + + + + + +
+
+ ); +}; + +export default StudentDialog; \ No newline at end of file diff --git a/package/src/app/class-management/students/page.tsx b/package/src/app/class-management/students/page.tsx index e83d259f..40ec52af 100644 --- a/package/src/app/class-management/students/page.tsx +++ b/package/src/app/class-management/students/page.tsx @@ -15,12 +15,30 @@ import { Chip, IconButton, Avatar, + Snackbar, + Alert, } from '@mui/material'; import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; import { mockStudents } from '@/mock/data'; import { IconEdit, IconTrash } from '@tabler/icons-react'; +import { useState } from 'react'; +import StudentDialog from './StudentDialog'; +import type { Student } from '@/mock/data'; const Students = () => { + const [students, setStudents] = useState(mockStudents); + const [selectedStudent, setSelectedStudent] = useState(); + const [dialogOpen, setDialogOpen] = useState(false); + const [notification, setNotification] = useState<{ + open: boolean; + message: string; + severity: 'success' | 'error'; + }>({ + open: false, + message: '', + severity: 'success', + }); + const getPaymentStatusColor = (status: string) => { switch (status) { case 'paid': @@ -45,6 +63,63 @@ const Students = () => { } }; + const handleAddStudent = () => { + setSelectedStudent(undefined); + setDialogOpen(true); + }; + + const handleEditStudent = (student: Student) => { + setSelectedStudent(student); + setDialogOpen(true); + }; + + const handleDeleteStudent = (studentId: string) => { + setStudents((prevStudents) => + prevStudents.filter((student) => student.id !== studentId) + ); + showNotification('Student deleted successfully', 'success'); + }; + + const handleSaveStudent = (studentData: Partial) => { + if (selectedStudent) { + // Update existing student + setStudents((prevStudents) => + prevStudents.map((student) => + student.id === selectedStudent.id + ? { ...student, ...studentData } + : student + ) + ); + showNotification('Student updated successfully', 'success'); + } else { + // Add new student + const newStudent: Student = { + id: String(Date.now()), // Generate a unique ID + name: studentData.name!, + email: studentData.email!, + phone: studentData.phone!, + joinDate: studentData.joinDate!, + status: studentData.status || 'active', + paymentStatus: 'pending', + }; + setStudents((prevStudents) => [...prevStudents, newStudent]); + showNotification('Student added successfully', 'success'); + } + setDialogOpen(false); + }; + + const showNotification = (message: string, severity: 'success' | 'error') => { + setNotification({ + open: true, + message, + severity, + }); + }; + + const handleCloseNotification = () => { + setNotification((prev) => ({ ...prev, open: false })); + }; + return ( @@ -53,7 +128,7 @@ const Students = () => { Students - @@ -71,7 +146,7 @@ const Students = () => { - {mockStudents.map((student) => ( + {students.map((student) => ( @@ -112,10 +187,19 @@ const Students = () => { /> - + handleEditStudent(student)} + > - + handleDeleteStudent(student.id)} + > @@ -128,6 +212,29 @@ const Students = () => { + + setDialogOpen(false)} + onSave={handleSaveStudent} + student={selectedStudent} + /> + + + + {notification.message} + + ); }; diff --git a/package/src/mock/data.ts b/package/src/mock/data.ts index e2606fb4..3aa084d6 100644 --- a/package/src/mock/data.ts +++ b/package/src/mock/data.ts @@ -52,7 +52,7 @@ export interface Payment { studentId: string; amount: number; date: string; - status: 'completed' | 'pending' | 'failed'; + status: 'paid' | 'unpaid'; method: 'cash' | 'card' | 'transfer'; description: string; } @@ -233,7 +233,7 @@ export const mockPayments: Payment[] = [ studentId: '1', amount: 500000, date: '2024-02-01', - status: 'completed', + status: 'paid', method: 'card', description: 'February Tuition Fee', }, @@ -242,7 +242,7 @@ export const mockPayments: Payment[] = [ studentId: '2', amount: 500000, date: '2024-02-01', - status: 'pending', + status: 'unpaid', method: 'transfer', description: 'February Tuition Fee', }, From 7f9f8afab8e7795f461b420fe4a92299080c14b7 Mon Sep 17 00:00:00 2001 From: MrFarrukhT Date: Tue, 18 Feb 2025 10:51:29 +0500 Subject: [PATCH 8/8] Add CabinetDialog component with CRUD functionality for cabinet management --- .../cabinets/CabinetDialog.tsx | 207 +++++++++++++++ .../app/class-management/cabinets/page.tsx | 241 +++++++++++++++++- 2 files changed, 442 insertions(+), 6 deletions(-) create mode 100644 package/src/app/class-management/cabinets/CabinetDialog.tsx diff --git a/package/src/app/class-management/cabinets/CabinetDialog.tsx b/package/src/app/class-management/cabinets/CabinetDialog.tsx new file mode 100644 index 00000000..3ba94ad9 --- /dev/null +++ b/package/src/app/class-management/cabinets/CabinetDialog.tsx @@ -0,0 +1,207 @@ +'use client'; +import { useState, useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + FormControl, + InputLabel, + Select, + MenuItem, + Grid, + Box, + Chip, + IconButton, +} from '@mui/material'; +import { Cabinet } from '@/mock/data'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AddIcon from '@mui/icons-material/Add'; + +interface CabinetDialogProps { + open: boolean; + onClose: () => void; + onSave: (cabinetData: Partial) => void; + onDelete?: () => void; + cabinetData?: Cabinet; + mode: 'create' | 'edit'; +} + +const statusOptions = ['available', 'occupied', 'maintenance'] as const; + +const CabinetDialog = ({ open, onClose, onSave, onDelete, cabinetData, mode }: CabinetDialogProps) => { + const [formData, setFormData] = useState>({ + name: '', + capacity: 15, + equipment: [], + status: 'available', + location: '', + }); + const [newEquipment, setNewEquipment] = useState(''); + + useEffect(() => { + if (cabinetData && mode === 'edit') { + setFormData(cabinetData); + } else { + setFormData({ + name: '', + capacity: 15, + equipment: [], + status: 'available', + location: '', + }); + } + }, [cabinetData, mode]); + + const handleChange = (field: keyof Cabinet, value: any) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleAddEquipment = () => { + if (newEquipment.trim()) { + handleChange('equipment', [...(formData.equipment || []), newEquipment.trim()]); + setNewEquipment(''); + } + }; + + const handleRemoveEquipment = (index: number) => { + const newEquipment = [...(formData.equipment || [])]; + newEquipment.splice(index, 1); + handleChange('equipment', newEquipment); + }; + + const handleSubmit = () => { + if (mode === 'create') { + const newCabinetData = { + ...formData, + id: Math.random().toString(36).substr(2, 9), + }; + onSave(newCabinetData); + } else { + onSave(formData); + } + }; + + const isValid = () => { + return ( + formData.name?.trim() && + formData.location?.trim() && + formData.capacity && + formData.capacity > 0 && + formData.status + ); + }; + + return ( + + + {mode === 'create' ? 'Create New Cabinet' : 'Edit Cabinet'} + + + + + handleChange('name', e.target.value)} + required + error={!formData.name?.trim()} + helperText={!formData.name?.trim() ? 'Name is required' : ''} + /> + + + handleChange('location', e.target.value)} + required + error={!formData.location?.trim()} + helperText={!formData.location?.trim() ? 'Location is required' : ''} + /> + + + handleChange('capacity', Number(e.target.value))} + required + error={!formData.capacity || formData.capacity <= 0} + helperText={!formData.capacity || formData.capacity <= 0 ? 'Capacity must be greater than 0' : ''} + inputProps={{ min: 1 }} + /> + + + + Status + + + + + + + setNewEquipment(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleAddEquipment(); + } + }} + /> + + + + {formData.equipment?.map((item, index) => ( + handleRemoveEquipment(index)} + size="small" + /> + ))} + + + + + + + {mode === 'edit' && onDelete && ( + + )} + + + + + ); +}; + +export default CabinetDialog; \ No newline at end of file diff --git a/package/src/app/class-management/cabinets/page.tsx b/package/src/app/class-management/cabinets/page.tsx index b7b18b5e..c0e24088 100644 --- a/package/src/app/class-management/cabinets/page.tsx +++ b/package/src/app/class-management/cabinets/page.tsx @@ -1,10 +1,52 @@ 'use client'; -import { Grid, Card, CardContent, Typography, Button, Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Chip, IconButton } from '@mui/material'; +import { useState } from 'react'; +import { Grid, Card, CardContent, Typography, Button, Box, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Chip, IconButton, Snackbar, Alert, Select, MenuItem, styled, SelectChangeEvent } from '@mui/material'; import PageContainer from '@/app/(DashboardLayout)/components/container/PageContainer'; -import { mockCabinets } from '@/mock/data'; +import { mockCabinets, mockClasses, mockTeachers, Cabinet } from '@/mock/data'; import { IconEdit, IconTrash } from '@tabler/icons-react'; +import CabinetDialog from './CabinetDialog'; + +// Styled components for uniform grid +const StyledTableCell = styled(TableCell)({ + width: '150px', + padding: '8px', + textAlign: 'center', + '&.time-cell': { + width: '80px', + }, + '&.mwf': { + backgroundColor: '#f5f5f5', + }, + '&.tts': { + backgroundColor: '#e8f4fd', + }, +}); + +const StyledSelect = styled(Select)({ + width: '100%', + '& .MuiSelect-select': { + padding: '8px', + }, +}); + +type ScheduleType = 'MWF' | 'TTS'; const Cabinets = () => { + const [cabinets, setCabinets] = useState(mockCabinets); + const [selectedCabinet, setSelectedCabinet] = useState(null); + const [dialogMode, setDialogMode] = useState<'create' | 'edit'>('create'); + const [dialogOpen, setDialogOpen] = useState(false); + const [scheduleData, setScheduleData] = useState<{[key: string]: { MWF: string; TTS: string }}>({}); + const [notification, setNotification] = useState<{ + open: boolean; + message: string; + severity: 'success' | 'error'; + }>({ + open: false, + message: '', + severity: 'success', + }); + const getStatusColor = (status: string) => { switch (status) { case 'available': @@ -18,6 +60,91 @@ const Cabinets = () => { } }; + // Generate time slots from 9:00 to 20:00 + const timeSlots = Array.from({ length: 12 }, (_, i) => { + const hour = i + 9; + return `${hour.toString().padStart(2, '0')}:00`; + }); + + const handleCreateCabinet = () => { + setSelectedCabinet(null); + setDialogMode('create'); + setDialogOpen(true); + }; + + const handleEditCabinet = (cabinet: Cabinet) => { + setSelectedCabinet(cabinet); + setDialogMode('edit'); + setDialogOpen(true); + }; + + const handleDeleteCabinet = (cabinetId: string) => { + const isInUse = mockClasses.some(cls => cls.cabinetId === cabinetId); + if (isInUse) { + setNotification({ + open: true, + message: 'Cannot delete cabinet that is in use by classes', + severity: 'error', + }); + return; + } + + const newCabinets = cabinets.filter(cab => cab.id !== cabinetId); + setCabinets(newCabinets); + setNotification({ + open: true, + message: 'Cabinet deleted successfully', + severity: 'success', + }); + }; + + const handleSaveCabinet = (cabinetData: Partial) => { + try { + if (dialogMode === 'create') { + const newCabinet = cabinetData as Cabinet; + setCabinets([...cabinets, newCabinet]); + setNotification({ + open: true, + message: 'Cabinet created successfully', + severity: 'success', + }); + } else { + const updatedCabinets = cabinets.map(cab => + cab.id === cabinetData.id ? { ...cab, ...cabinetData } : cab + ); + setCabinets(updatedCabinets); + setNotification({ + open: true, + message: 'Cabinet updated successfully', + severity: 'success', + }); + } + setDialogOpen(false); + } catch (error) { + setNotification({ + open: true, + message: 'An error occurred while saving the cabinet', + severity: 'error', + }); + } + }; + + const handleScheduleChange = (time: string, cabinetId: string, scheduleType: ScheduleType, event: SelectChangeEvent) => { + const key = `${time}-${cabinetId}`; + setScheduleData(prev => ({ + ...prev, + [key]: { + ...prev[key] || { MWF: '', TTS: '' }, + [scheduleType]: event.target.value + } + })); + }; + + const getTeacherForSlot = (time: string, cabinetId: string, scheduleType: ScheduleType) => { + const key = `${time}-${cabinetId}`; + return scheduleData[key]?.[scheduleType] || ''; + }; + return ( @@ -26,7 +153,7 @@ const Cabinets = () => { Cabinets - @@ -44,7 +171,7 @@ const Cabinets = () => { - {mockCabinets.map((cabinet) => ( + {cabinets.map((cabinet) => ( {cabinet.name} {cabinet.location} @@ -67,10 +194,19 @@ const Cabinets = () => { /> - + handleEditCabinet(cabinet)} + > - + handleDeleteCabinet(cabinet.id)} + > @@ -79,10 +215,103 @@ const Cabinets = () => { + + {/* Schedule Grid */} + + Schedule + + + + + Time + {cabinets.map((cabinet) => ( + <> + + {cabinet.name} (M/W/F) + + + {cabinet.name} (T/T/S) + + + ))} + + + + {timeSlots.map((time) => ( + + {time} + {cabinets.map((cabinet) => ( + <> + + ) => + handleScheduleChange(time, cabinet.id, 'MWF', e) + } + size="small" + displayEmpty + > + empty + {mockTeachers.map((teacher) => ( + + {teacher.name} + + ))} + + + + ) => + handleScheduleChange(time, cabinet.id, 'TTS', e) + } + size="small" + displayEmpty + > + empty + {mockTeachers.map((teacher) => ( + + {teacher.name} + + ))} + + + + ))} + + ))} + +
+
+
+ + setDialogOpen(false)} + onSave={handleSaveCabinet} + onDelete={selectedCabinet ? () => handleDeleteCabinet(selectedCabinet.id) : undefined} + cabinetData={selectedCabinet || undefined} + mode={dialogMode} + /> + + setNotification({ ...notification, open: false })} + anchorOrigin={{ vertical: 'top', horizontal: 'right' }} + > + setNotification({ ...notification, open: false })} + severity={notification.severity} + sx={{ width: '100%' }} + > + {notification.message} + +
); };