Loading...
;
+ }
+
+ return authenticated ? Loading...
;
+ }
+
+ const userData = data.users.map((user) => ({
+ name: user.username,
+ registration_date: user.registration_date,
+ }));
+
+ const courseData = data.courses.map((course) => ({
+ name: course.course_name,
+ domain_id: course.domain_id,
+ }));
+
+ const instanceData = data.instances.reduce((acc, instance) => {
+ const course = data.courses.find(
+ (course) => course.course_id === instance.course_id
+ );
+ if (course) {
+ const courseName = course.course_name;
+ acc[courseName] = (acc[courseName] || 0) + 1;
+ }
+ return acc;
+ }, {});
+
+ const moduleData = data.modules.reduce((acc, module) => {
+ const instance = data.instances.find(
+ (instance) => instance.instance_id === module.instance_id
+ );
+ if (instance) {
+ const instanceName = instance.instance_name;
+ acc[instanceName] = (acc[instanceName] || 0) + 1;
+ }
+ return acc;
+ }, {});
+
+ const activityData = data.activities.reduce((acc, activity) => {
+ const module = data.modules.find(
+ (module) => module.module_id === activity.module_id
+ );
+ if (module) {
+ const moduleName = module.module_name;
+ acc[moduleName] = (acc[moduleName] || 0) + 1;
+ }
+ return acc;
+ }, {});
+
+ const userProgressData = data.user_progress_modules.reduce(
+ (acc, progress) => {
+ acc.submited += progress.submited ? 1 : 0;
+ acc.finished += progress.finished ? 1 : 0;
+ acc.passed += progress.passed ? 1 : 0;
+ return acc;
+ },
+ { submited: 0, finished: 0, passed: 0 }
+ );
+
+ return (
+
+
+
+
+
+ Check The Progress Of Students
+
+
+
+ {[
+ { name: "Submited", value: userProgressData.submited },
+ { name: "Finished", value: userProgressData.finished },
+ { name: "Passed", value: userProgressData.passed },
+ ].map((entry, index) => (
+ |
+ ))}
+
+
+
+
+
+
+
Courses by Domain
+
+
+
+
+
+
+
+ {courseData.map((entry, index) => (
+ |
+ ))}
+
+
+
+
+
+
+ What Is The Number of Instances Per Course ?
+
+ ({
+ name: key,
+ count: instanceData[key],
+ fill: COLORS[index % COLORS.length],
+ }))}
+ >
+
+
+
+
+
+
+ {Object.keys(instanceData).map((key, index) => (
+ |
+ ))}
+
+
+
+
+
+
What Is The Number Of Modules Per Instance
+
+ ({
+ name: key,
+ value: moduleData[key],
+ fill: COLORS[index % COLORS.length],
+ }))}
+ dataKey="value"
+ cx="50%"
+ cy="50%"
+ outerRadius={100}
+ >
+ {Object.keys(moduleData).map((key, index) => (
+ |
+ ))}
+
+
+
+
+
+
+
+ User Registrations Over Time
+
+
+
+
+
+
+
+
+
+
+
+
Activities Per Module
+ ({
+ name: key,
+ count: activityData[key],
+ fill: COLORS[index % COLORS.length],
+ }))}
+ >
+
+
+
+
+
+
+ {Object.keys(activityData).map((key, index) => (
+ |
+ ))}
+
+
+
+
+
+
+
+ );
+};
+
+export default AnalyticsDashboard;
diff --git a/frontend/web/src/components/BalloonAnimation/BalloonAnimation.js b/frontend/web/src/components/BalloonAnimation/BalloonAnimation.js
new file mode 100644
index 0000000..02a4e31
--- /dev/null
+++ b/frontend/web/src/components/BalloonAnimation/BalloonAnimation.js
@@ -0,0 +1,62 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+
+const balloonColors = ['red', 'blue', 'green', 'yellow', 'purple', 'orange', 'pink'];
+const numberOfBalloons = 10;
+
+const generateRandomColor = () => {
+ return balloonColors[Math.floor(Math.random() * balloonColors.length)];
+};
+
+const generateRandomXPosition = () => {
+ return Math.floor(Math.random() * 100) + '%';
+};
+
+const Balloon = ({ color, initialX }) => {
+ return (
+
+ {posts.map((_post, index) => {
+ const post = _post.post;
+ const comments = _post.comments;
+
+ return (
+
+
+ {post.first_name} {post.last_name}
+
+
{post.content}
+
+ {comments &&
+ Object.entries(comments).map(([emoji, details], index) => (
+
{
+ const tooltip = document.createElement("div");
+ tooltip.className =
+ "absolute bottom-full mb-2 p-2 bg-gray-700 text-white text-xs rounded shadow-lg";
+ tooltip.innerText = details.users.join(", ");
+ e.currentTarget.appendChild(tooltip);
+ }}
+ onMouseLeave={(e) => {
+ const tooltip = e.currentTarget.querySelector("div");
+ if (tooltip) e.currentTarget.removeChild(tooltip);
+ }}
+ >
+ {emoji}
+ {details.count}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ setNewComment({ ...newComment, [post.user_post_id]: e.target.value })
+ }
+ placeholder="Add a comment..."
+ className="border p-2 rounded w-full"
+ />
+
+
+
+ );
+ })}
+
+ );
+}
+
+export default Community;
diff --git a/frontend/web/src/components/DataPanel/ActivitiesTable.js b/frontend/web/src/components/DataPanel/ActivitiesTable.js
new file mode 100644
index 0000000..50d7c4c
--- /dev/null
+++ b/frontend/web/src/components/DataPanel/ActivitiesTable.js
@@ -0,0 +1,103 @@
+function ActivitiesTable(props = { activities: [] }) {
+ console.log({ props });
+
+ return (
+
+
+
+
+ - handleClick("users")}
+ >
+ Students
+
+ - handleClick("domaines")}
+ >
+ Domaines
+
+ - handleClick("courses")}
+ >
+ Courses
+
+ - handleClick("instances")}
+ >
+ Instances
+
+ - handleClick("modules")}
+ >
+ Modules
+
+ - handleClick("activities")}
+ >
+ Activities
+
+
+ - handleClick("new-data")}
+ >
+ ADD NEW
+
+
+
+
+
+ {activeItem === "domaines" && (
+
{
+ return {
+ domain_name: domain.domain_name,
+ domain_id: domain.domain_id,
+ };
+ })}
+ >
+ )}
+ {activeItem === "courses" && (
+
{
+ return domain.courses.map((course) => {
+ return {
+ domain_name: domain.domain_name,
+ course_id: course.course_id,
+ course_name: course.course_name,
+ course_description: course.course_description,
+ };
+ });
+ })
+ .flat()}
+ >
+ )}
+ {activeItem === "instances" && (
+
{
+ return domain.courses.map((course) => {
+ return course.instances.map((instance) => {
+ return {
+ domain_name: domain.domain_name,
+ course_name: course.course_name,
+ instance_name: instance.instance_name,
+ instance_id: instance.instance_id,
+ instance_end_date: instance.instance_end_date,
+ instance_start_date: instance.instance_start_date,
+ };
+ });
+ });
+ })
+ .flat(2)}
+ >
+ )}
+ {activeItem === "modules" && (
+
{
+ return domain.courses.map((course) => {
+ return course.instances.map((instance) => {
+ return instance.modules.map((module) => {
+ return {
+ domain_name: domain.domain_name,
+ course_name: course.course_name,
+ instance,
+ module_description: module.module_description,
+ module_id: module.module_id,
+ module_name: module.module_name,
+ required_point: module.required_point,
+ };
+ });
+ });
+ });
+ })
+ .flat(3)}
+ >
+ )}
+ {activeItem === "activities" && (
+
{
+ return domain.courses.map((course) => {
+ return course.instances.map((instance) => {
+ return instance.modules.map((module) => {
+ return module.activities.map((activity) => {
+ return {
+ activity_description: activity.activity_description,
+ activity_id: activity.activity_id,
+ activity_name: activity.activity_name,
+ level: activity.level,
+ point: activity.point,
+ type: activity.type,
+ domain_: domain.domain_name,
+ course_name: course.course_name,
+ instance,
+ module_description: module.module_description,
+ module_id: module.module_id,
+ module_name: module.module_name,
+ required_point: module.required_point,
+ };
+ });
+ });
+ });
+ });
+ })
+ .flat(4)}
+ >
+ )}
+ {activeItem === "new-data" &&
}
+ {activeItem === "users" &&
}
+
+
+ );
+}
+export default DataPanel;
diff --git a/frontend/web/src/components/DataPanel/DomainesTable.js b/frontend/web/src/components/DataPanel/DomainesTable.js
new file mode 100644
index 0000000..dfdf9f8
--- /dev/null
+++ b/frontend/web/src/components/DataPanel/DomainesTable.js
@@ -0,0 +1,60 @@
+function DominesTable(props = { domaines: [] }) {
+ console.log({ props });
+
+ return (
+
+ {activeCourses &&
+ Object.values(activeCourses).map((activeCourse, index) => {
+ return (
+
+
+
+
+
+
+ Complete your course
+
+
+
+ Domine : {activeCourse.domain_name}
+
+
+ Module Name: {activeCourse.module_name}
+
+
+ Course Name: {activeCourse.course_name}
+
+
+
+
+ );
+ })}
+
+
Courses
+
+ {props &&
+ props.domines &&
+ props.domines.map((domin, index) => {
+ return (
+
+
+ {domin.domain_name}
+
+
+
+
+ );
+ })}
+
+
+ );
+}
+
+export default Domines;
diff --git a/frontend/web/src/components/Home/Home.js b/frontend/web/src/components/Home/Home.js
new file mode 100644
index 0000000..cee989b
--- /dev/null
+++ b/frontend/web/src/components/Home/Home.js
@@ -0,0 +1,137 @@
+import { useEffect, useState } from "react";
+import { useLocation, useNavigate } from "react-router-dom";
+import { getRegisteredModules } from "../../services/domines";
+
+function Home() {
+ const location = useLocation();
+ const navigate = useNavigate();
+ const [activities, setActivities] = useState([]);
+ useEffect(() => {
+ getRegisteredModules()
+ .then((modules) => {
+ setActivities(modules);
+ })
+ .catch((error) => {});
+ }, []);
+
+ const totalPassedPoints = activities.reduce((total, activity) => {
+ return activity.passed ? total + activity.point : total;
+ }, 0);
+
+ const totalFinishedTasks = activities.filter(
+ (activity) => activity.passed
+ ).length;
+
+ const activeCourses = {};
+ activities.forEach((ele) => {
+ if (!activeCourses[ele.domain_name]) {
+ Object.assign(activeCourses, { [ele.domain_name]: ele });
+ }
+ });
+ return (
+ <>
+
+ );
+};
+
+export default NewDomine;
diff --git a/frontend/web/src/components/PushNotification/PushNotification.js b/frontend/web/src/components/PushNotification/PushNotification.js
new file mode 100644
index 0000000..cdfa034
--- /dev/null
+++ b/frontend/web/src/components/PushNotification/PushNotification.js
@@ -0,0 +1,37 @@
+import React, { useEffect, useState } from "react";
+import { useSocket } from "../SocketContext/SocketContext";
+import { ToastContainer, toast } from "react-toastify";
+import "react-toastify/dist/ReactToastify.css";
+
+const PushNotification = () => {
+ const socket = useSocket();
+ const [displayedMessages, setDisplayedMessages] = useState(new Set());
+
+ useEffect(() => {
+ if (!socket) return;
+
+ socket.on("notification", (message) => {
+ try {
+ if (displayedMessages && !displayedMessages.has(message.message)) {
+ toast(message.message);
+ setDisplayedMessages(prev => new Set(prev).add(message.message));
+ }
+ } catch (error) {
+ console.error(error);
+ }
+
+ });
+
+ return () => {
+ socket.off("notification");
+ };
+ }, [socket, displayedMessages]);
+
+ return (
+