diff --git a/package-lock.json b/package-lock.json index be0e4cd..0f16e51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "dotenv": "^17.2.1", "input-otp": "^1.4.2", "lucide-react": "^0.510.0", + "moment": "^2.30.1", "next-themes": "^0.4.6", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -4490,6 +4491,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 0cb95e6..2859513 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dotenv": "^17.2.1", "input-otp": "^1.4.2", "lucide-react": "^0.510.0", + "moment": "^2.30.1", "next-themes": "^0.4.6", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/components/academic/courses/create-course.tsx b/src/components/academic/courses/create-course.tsx index 5c7b9a7..1f1fc9d 100644 --- a/src/components/academic/courses/create-course.tsx +++ b/src/components/academic/courses/create-course.tsx @@ -14,13 +14,15 @@ import axios from "axios"; import { Plus } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; -import type { CourseData } from "types"; +import type { CourseData, DepartmentData } from "@/types"; import { courses } from "../../../store/atoms/academics/courses"; import { useRecoilRefresher_UNSTABLE } from "recoil"; +import { useDepartmentData } from "@/store/hooks/academics/use-department-data"; const base_api = import.meta.env.VITE_SERVER_BASE_URL; const CreateCourse = () => { + const { departmentInfo, loading } = useDepartmentData(); const refreshCourse = useRecoilRefresher_UNSTABLE(courses); const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -67,6 +69,10 @@ const CreateCourse = () => { } }; + if (loading) { + return
Loading department...
; + } + return ( { />
- - Department +
diff --git a/src/components/academic/courses/edit-course.tsx b/src/components/academic/courses/edit-course.tsx index ef0520d..ac8cd79 100644 --- a/src/components/academic/courses/edit-course.tsx +++ b/src/components/academic/courses/edit-course.tsx @@ -9,22 +9,20 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { InputHandler, SelectHandler } from "@/lib/utils"; +import { FormateDate, InputHandler, SelectHandler } from "@/lib/utils"; import axios from "axios"; import { Edit } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; -import type { CourseData } from "types"; +import type { CourseData, DepartmentData, EditCourseProps } from "@/types"; import { courses } from "../../../store/atoms/academics/courses"; import { useRecoilRefresher_UNSTABLE } from "recoil"; +import { useDepartmentData } from "@/store/hooks/academics/use-department-data"; const base_api = import.meta.env.VITE_SERVER_BASE_URL; -interface EditCourseProps { - course: CourseData; -} - const EditCourse = ({ course }: EditCourseProps) => { + const { departmentInfo } = useDepartmentData(); const refreshCourse = useRecoilRefresher_UNSTABLE(courses); const [editingCourse, setEditingCourse] = useState(null); @@ -108,14 +106,26 @@ const EditCourse = ({ course }: EditCourseProps) => { />
- - Department +
@@ -123,7 +133,7 @@ const EditCourse = ({ course }: EditCourseProps) => { id="update-startdate" name="startdate" type="date" - value={editingCourse.startdate} + value={FormateDate(editingCourse.startdate)} onChange={(e) => InputHandler(e, setEditingCourse)} />
@@ -133,7 +143,7 @@ const EditCourse = ({ course }: EditCourseProps) => { id="update-enddate" name="enddate" type="date" - value={editingCourse.enddate} + value={FormateDate(editingCourse.enddate)} onChange={(e) => InputHandler(e, setEditingCourse)} /> diff --git a/src/components/academic/courses/academic-courses-tab.tsx b/src/components/academic/courses/index.tsx similarity index 94% rename from src/components/academic/courses/academic-courses-tab.tsx rename to src/components/academic/courses/index.tsx index b0bb356..dc36bcd 100644 --- a/src/components/academic/courses/academic-courses-tab.tsx +++ b/src/components/academic/courses/index.tsx @@ -1,9 +1,9 @@ import { Badge } from "@/components/ui/badge"; -import { Card, CardContent } from "../../ui/card"; +import { Card, CardContent } from "@/components/ui/card"; import { useCoursesData } from "@/store/hooks/academics/use-courses-data"; import { FormateDate } from "@/lib/utils"; -import { Skeleton } from "../../ui/skeleton"; -import type { CourseData } from "../../../../types/index"; +import { Skeleton } from "@/components/ui/skeleton"; +import type { CourseData } from "@/types"; import EditCourse from "./edit-course"; import CreateCourse from "./create-course"; import { Button } from "@/components/ui/button"; @@ -12,7 +12,7 @@ import { ConfirmDialog } from "@/components/confirm-dialog"; import axios from "axios"; import { useState } from "react"; import { toast } from "sonner"; -import { courses } from "../../../store/atoms/academics/courses"; +import { courses } from "@/store/atoms/academics/courses"; import { useRecoilRefresher_UNSTABLE } from "recoil"; const base_api = import.meta.env.VITE_SERVER_BASE_URL; @@ -64,7 +64,6 @@ const AcademinCoursesTab = () => {

Courses Data

- {/* CREATE COURSE MODAL */}
@@ -110,8 +109,10 @@ const AcademinCoursesTab = () => {
- {/* UPDATE COURSE MODAL */} + {/* update course */} + + {/* delete course */}
{ + const refreshDepartment = useRecoilRefresher_UNSTABLE(department); + const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const [createDepartment, setCreateDepartment] = useState({ + name: "", + school: "", + established: "", + }); + + // Create department + const handleCreateSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + + try { + const response = await axios.post( + `${base_api}/api/v1/create-department`, + createDepartment, + { withCredentials: true } + ); + if (response.status === 200) { + toast.success("Department created successfully"); + setCreateDepartment({ + name: "", + school: "", + established: "", + }); + refreshDepartment(); + setIsCreateDialogOpen(false); + setIsLoading(false); + } + } catch (error: any) { + console.error(error?.message); + toast.error("Failed to create Department"); + setIsCreateDialogOpen(false); + setIsLoading(false); + } + }; + + return ( + + + Add new + + } + > +
+ + InputHandler(e, setCreateDepartment)} + name="name" + placeholder="Enter department name" + /> +
+
+ + InputHandler(e, setCreateDepartment)} + name="school" + placeholder="Enter school name" + /> +
+
+ + InputHandler(e, setCreateDepartment)} + name="established" + placeholder="Enter established year" + /> +
+
+ ); +}; + +export default CreateDepartment; diff --git a/src/components/academic/departments/edit-department.tsx b/src/components/academic/departments/edit-department.tsx new file mode 100644 index 0000000..e8debb3 --- /dev/null +++ b/src/components/academic/departments/edit-department.tsx @@ -0,0 +1,116 @@ +import UseModel from "@/components/models/useModel"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { InputHandler } from "@/lib/utils"; +import axios from "axios"; +import { Edit } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import type { DepartmentData, EditDepartmentData } from "@/types"; +import { department } from "../../../store/atoms/academics/department"; +import { useRecoilRefresher_UNSTABLE } from "recoil"; + +const base_api = import.meta.env.VITE_SERVER_BASE_URL; + +const EditDepartment = ({ departmentInfo }: EditDepartmentData) => { + const refreshDepartment = useRecoilRefresher_UNSTABLE(department); + + const [editingDepartment, setEditingDepartment] = + useState(null); + const [isUpdateDialogOpen, setIsUpdateDialogOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + // Update department submit + const handleUpdateSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!editingDepartment?.id) return; + + setIsLoading(true); + + try { + const response = await axios.put( + `${base_api}/api/v1/update-department/${editingDepartment.id}`, + editingDepartment, + { withCredentials: true } + ); + if (response.status === 200) { + toast.success("Department updated successfully"); + refreshDepartment(); + setIsUpdateDialogOpen(false); + setEditingDepartment(null); + setIsLoading(false); + } + } catch (error: any) { + console.error(error?.message); + toast.error("Failed to update Department"); + setIsUpdateDialogOpen(false); + setIsLoading(false); + } + }; + + // Handle edit button click + const handleEditClick = (departmentInfo: DepartmentData) => { + setEditingDepartment({ ...departmentInfo }); + setIsUpdateDialogOpen(true); + }; + + return ( + handleEditClick(departmentInfo)} + > + + Update + + } + > + {editingDepartment && ( + <> +
+ + InputHandler(e, setEditingDepartment)} + name="name" + placeholder="Enter department name" + /> +
+
+ + InputHandler(e, setEditingDepartment)} + name="school" + placeholder="Enter school name" + /> +
+
+ + InputHandler(e, setEditingDepartment)} + name="established" + placeholder="Enter established year" + /> +
+ + )} +
+ ); +}; + +export default EditDepartment; diff --git a/src/components/academic/departments/index.tsx b/src/components/academic/departments/index.tsx new file mode 100644 index 0000000..0eb5629 --- /dev/null +++ b/src/components/academic/departments/index.tsx @@ -0,0 +1,116 @@ +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { useDepartmentData } from "@/store/hooks/academics/use-department-data"; +import type { DepartmentData } from "@/types"; +import CreateDepartment from "./create-department"; +import EditDepartment from "./edit-department"; +import { ConfirmDialog } from "@/components/confirm-dialog"; +import { Trash } from "lucide-react"; +import { useState } from "react"; +import { toast } from "sonner"; +import { useRecoilRefresher_UNSTABLE } from "recoil"; +import { department } from "@/store/atoms/academics/department"; +import axios from "axios"; + +const base_api = import.meta.env.VITE_SERVER_BASE_URL; + +const AcademicDepartmentTab = () => { + const { departmentInfo, loading } = useDepartmentData(); + const [isLoading, setIsLoading] = useState(false); + const refreshDepartment = useRecoilRefresher_UNSTABLE(department); + + // handle Delete + + async function handleDelete(dept: DepartmentData) { + try { + const response = await axios.delete( + `${base_api}/api/v1/delete-department/${dept?.id}`, + { + withCredentials: true, + } + ); + + if (response.status === 201) { + toast.success("Department deleted !!"); + setIsLoading(false); + refreshDepartment(); + } + } catch (error) { + console.log(error); + setIsLoading(false); + } + } + + if (loading) { + return
loading
; + } + + const departmentData: DepartmentData[] = departmentInfo?.Departments; + return ( + <> +
+

Department Management

+ {/* create model */} + +
+ +
+ {departmentData?.length > 0 ? ( + departmentData.map((dept, index) => ( + + +
+
+

{dept.name}

+
+
+ School:{" "} + {dept.school} +
+
+ Department Head:{" "} + {/* {dept.head} */} +
+ +
+ Established:{" "} + {dept.established.slice(0, 4)} +
+
+
+
+ + {/* delete department */} +
+ handleDelete(dept)} + destructive + trigger={ + + } + /> +
+
+
+
+
+ )) + ) : ( +
No department available to show
+ )} +
+ + ); +}; + +export default AcademicDepartmentTab; diff --git a/src/components/academic/programs/academic-programs-tsb.tsx b/src/components/academic/programs/academic-programs-tsb.tsx deleted file mode 100644 index 3a94b1d..0000000 --- a/src/components/academic/programs/academic-programs-tsb.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { Skeleton } from "@/components/ui/skeleton"; -import { TabsContent } from "@/components/ui/tabs"; -import { useProgramsData } from "@/store/hooks/academics/use-program-data"; -import { Edit, Plus } from "lucide-react"; -import type { ProgramData } from "types"; - -const AcademicProgramsTab = () => { - const { programsInfo, error, loading } = useProgramsData(); - - if (loading) - return ( -
- -
- ); - - if (error) - return ( -
- Error loading courses data. -
- ); - - const programData: ProgramData[] = programsInfo?.Programs; - console.log(programData); - - return ( - -
-

Academic Programs

- -
- -
- {programData?.length > 0 ? ( - programData.map((program, index) => ( - - -
-

{program.name}

- - {program.status} - -
-
- {/*
- Department:{" "} - {program.department} // need to bind on department -
*/} - -
- Duration:{" "} - {program.duration} -
-
- Coordinator:{" "} - {program.coordinator} -
-
-
- - -
-
-
- )) - ) : ( -
- No data available to show -
- )} -
-
- ); -}; - -export default AcademicProgramsTab; diff --git a/src/components/academic/programs/create-programs.tsx b/src/components/academic/programs/create-programs.tsx index 1ba55e4..da9f374 100644 --- a/src/components/academic/programs/create-programs.tsx +++ b/src/components/academic/programs/create-programs.tsx @@ -14,68 +14,76 @@ import axios from "axios"; import { Plus } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; -import type { CourseData } from "types"; -import { courses } from "../../../store/atoms/academics/courses"; +import type { DepartmentData, ProgramData } from "@/types"; +import { programs } from "../../../store/atoms/academics/programs"; import { useRecoilRefresher_UNSTABLE } from "recoil"; +import { Textarea } from "@/components/ui/textarea"; +import { useDepartmentData } from "@/store/hooks/academics/use-department-data"; const base_api = import.meta.env.VITE_SERVER_BASE_URL; const CreatePrograms = () => { - const refreshCourse = useRecoilRefresher_UNSTABLE(courses); + const refreshPrograms = useRecoilRefresher_UNSTABLE(programs); const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); + const { departmentInfo, loading } = useDepartmentData(); - const [createCourse, setCreateCourse] = useState({ - coursename: "", - professorname: "", - department: "", + const [createProgram, setCreateProgram] = useState({ + name: "", + departmentId: "", + detail: "", + duration: "", + coordinator: "", status: "", - startdate: "", - enddate: "", }); - // Create course submit + // Create department submit const handleCreateSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); try { const response = await axios.post( - `${base_api}/api/v1/create-course`, - createCourse, + `${base_api}/api/v1/create-program`, + createProgram, + { withCredentials: true } ); - if (response.status === 200) { - toast.success("Course created successfully"); - setCreateCourse({ - coursename: "", - professorname: "", - department: "", + if (response.status === 201) { + setCreateProgram({ + name: "", + departmentId: "", + detail: "", + duration: "", + coordinator: "", status: "", - startdate: "", - enddate: "", }); - refreshCourse(); - setIsCreateDialogOpen(false); + toast.success("program created successfully"); setIsLoading(false); + refreshPrograms(); + setIsCreateDialogOpen(false); } } catch (error: any) { console.error(error?.message); - toast.error("Failed to create course"); - setIsCreateDialogOpen(false); + toast.error("Failed to create program"); setIsLoading(false); + setIsCreateDialogOpen(false); } }; + if (loading) { + return
loading department ....
; + } + return ( @@ -84,70 +92,80 @@ const CreatePrograms = () => { } >
- + InputHandler(e, setCreateCourse)} - name="coursename" - placeholder="Enter course name" + id="name" + name="name" + value={createProgram.name} + onChange={(e) => InputHandler(e, setCreateProgram)} + placeholder="Enter program name" />
- + InputHandler(e, setCreateCourse)} - name="professorname" - placeholder="Enter professor name" + id="coordinator" + name="coordinator" + value={createProgram.coordinator} + onChange={(e) => InputHandler(e, setCreateProgram)} + placeholder="Enter coordinator name" />
- - InputHandler(e, setCreateCourse)} - name="department" - placeholder="Enter department" + +