Skip to content

Commit 9ea3382

Browse files
committed
Add examdate functionality
1 parent 3c4dfc6 commit 9ea3382

File tree

3 files changed

+214
-161
lines changed

3 files changed

+214
-161
lines changed

src/api/UserApi.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export function updateUserModules(axios: AxiosInstance, modules: UserModule[]) {
2323
const addExamData = (userModules: UserModule[]) => {
2424
return userModules.map(userModule => ({
2525
...userModule,
26-
examDate: "2025-08-20",
26+
examDate: "JJJJ-MM-DD",
2727
examLoc: "loco",
2828
}));
2929
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import React, { useState } from 'react';
2+
import '../../styles/Modal.css';
3+
import { CuteButton } from '../CuteButton';
4+
import { UserModule } from '../../dtos/ModuleDto';
5+
6+
interface Props {
7+
isOpen: boolean;
8+
modules: UserModule[];
9+
onClose: () => void;
10+
onSubmit: (moduleName: string, date: string, time: string, room: string) => void;
11+
}
12+
13+
export default function ExamDateModal({ isOpen, modules, onClose, onSubmit }: Props) {
14+
const [pick, setPick] = useState(modules[0]?.name || '');
15+
const [day, setDay] = useState('');
16+
const [hour, setHour] = useState('');
17+
const [loc, setLoc] = useState('');
18+
19+
if (!isOpen) return null;
20+
21+
return (
22+
<div className="modal-overlay" onClick={onClose}>
23+
<div className="modal-content max-w-[90%] w-[500px] p-7 relative" onClick={e => e.stopPropagation()}>
24+
<button onClick={onClose} className="absolute top-4 right-4 text-white text-2xl hover:text-red-400">×</button>
25+
<h2 className="text-2xl font-bold text-white mb-4">Prüfungstermin hinzufügen</h2>
26+
<div className="flex flex-col gap-4 mb-6">
27+
<select value={pick} onChange={e => setPick(e.target.value)} className="p-2 rounded bg-[#2A2A2A] text-white">
28+
{modules.map(m => <option key={m.name}>{m.name}</option>)}
29+
</select>
30+
<input type="date" value={day} onChange={e => setDay(e.target.value)} className="p-2 rounded bg-[#2A2A2A] text-white" />
31+
<input type="time" value={hour} onChange={e => setHour(e.target.value)} className="p-2 rounded bg-[#2A2A2A] text-white" />
32+
<input type="text" placeholder="Raum" value={loc} onChange={e => setLoc(e.target.value)} className="p-2 rounded bg-[#2A2A2A] text-white" />
33+
</div>
34+
<div className="flex justify-end gap-4">
35+
<CuteButton onClick={onClose} text="Abbrechen" bgColor="#974242" textColor="#e8fcf6" classname="px-4 py-2" />
36+
<CuteButton onClick={() => onSubmit(pick, day, hour, loc)} text="Speichern" bgColor="#56A095" textColor="#e6ebfc" classname="px-4 py-2" />
37+
</div>
38+
</div>
39+
</div>
40+
);
41+
}
Lines changed: 172 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -1,169 +1,181 @@
1-
import React, {useEffect, useState} from "react";
1+
import React, { useState, useEffect } from "react";
22
import ProgressBar from "../../components/Progressbar";
3-
import {getMeetingsOfWeek} from "../../api/MeetingApi";
3+
import { getMeetingsOfWeek } from "../../api/MeetingApi";
44
import axiosInstance from "../../AxiosConfig";
5-
import {MeetingDto} from "../../dtos/MeetingDto";
5+
import { MeetingDto } from "../../dtos/MeetingDto";
66
import ModuleProgressSettings from "../ModuleProgressSettings";
7-
import {Chapter, Checkbox, UserModule} from "../../dtos/ModuleDto";
8-
import {getUser} from "../../api/UserApi";
7+
import { Chapter, Checkbox, UserModule as BaseModule } from "../../dtos/ModuleDto";
8+
import { getUser, updateUserModules } from "../../api/UserApi";
9+
import ExamDateModal from "../../components/meeting/ExamDateModal";
910

10-
export default function UserInfo(props: { reload: boolean }) {
11-
const [weeklyMeetings, setWeeklyMeetings] = useState<MeetingDto[]>([]);
12-
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
13-
const [modules, setModules] = useState<UserModule[]>([]);
14-
const [activeModule, setActiveModule] = useState<UserModule | undefined>();
15-
16-
const filterMeetingsForCurrentWeek = (meetings: MeetingDto[]) => {
17-
const currentDate = new Date();
18-
const startOfWeek = new Date(currentDate.setDate(currentDate.getDate() - currentDate.getDay() + 1));
19-
const endOfWeek = new Date(currentDate.setDate(startOfWeek.getDate() + 6));
11+
type UserModule = BaseModule & {
12+
examDate?: string;
13+
examTime?: string;
14+
examRoom?: string;
15+
};
2016

21-
return meetings.filter((meeting) => {
22-
const meetingDate = new Date(meeting.dateFrom);
23-
return meetingDate >= startOfWeek && meetingDate <= endOfWeek;
24-
});
25-
};
26-
27-
const fetchUserInfo = async () => {
28-
try {
29-
const response = await getUser(axiosInstance);
30-
setModules(response.modules);
31-
} catch (error) {
32-
console.error("Fehler beim Abrufen der UserDaten: " + error);
33-
}
17+
export default function UserInfo(props: { reload: boolean }) {
18+
const [weeklyMeetings, setWeeklyMeetings] = useState<MeetingDto[]>([]);
19+
const [isProgressModalOpen, setIsProgressModalOpen] = useState(false);
20+
const [isExamModalOpen, setIsExamModalOpen] = useState(false);
21+
const [modules, setModules] = useState<UserModule[]>([]);
22+
const [activeModule, setActiveModule] = useState<UserModule | undefined>();
23+
24+
useEffect(() => { fetchUserInfo(); fetchMeetings(); }, []);
25+
useEffect(() => { fetchUserInfo(); }, [props.reload]);
26+
27+
async function fetchUserInfo() {
28+
try {
29+
const user = await getUser(axiosInstance);
30+
setModules(user.modules as UserModule[]);
31+
} catch (e) {
32+
console.error(e);
3433
}
35-
36-
const fetchMeetings = async () => {
37-
try {
38-
const response = await getMeetingsOfWeek(axiosInstance);
39-
setWeeklyMeetings(filterMeetingsForCurrentWeek(response));
40-
} catch (error) {
41-
console.error("Fehler beim Abrufen der Meetings: " + error);
42-
}
43-
};
44-
45-
useEffect(() => {
46-
fetchMeetings();
47-
fetchUserInfo();
48-
}, []);
49-
50-
useEffect(() => {
51-
fetchUserInfo();
52-
}, [props.reload]);
53-
54-
const openModal = (moduleName: string) => {
55-
setActiveModule(modules.find((m) => m.name === moduleName));
56-
setIsModalOpen(true);
57-
};
58-
59-
const closeModal = () => {
60-
setIsModalOpen(false);
61-
};
62-
63-
const calculateProgress = (module: UserModule): number => {
64-
if (!module.chapter?.length) return 0;
65-
66-
let checked = 0;
67-
let total = 0;
68-
69-
module.chapter.forEach((chapter: Chapter) => {
70-
total += chapter.checkbox.length;
71-
checked += chapter.checkbox.filter((box: Checkbox) => box.checked).length;
72-
});
73-
74-
return total === 0 ? 0 : checked / total;
34+
}
35+
36+
async function fetchMeetings() {
37+
try {
38+
const resp = await getMeetingsOfWeek(axiosInstance);
39+
setWeeklyMeetings(filterMeetingsForCurrentWeek(resp));
40+
} catch (e) {
41+
console.error(e);
7542
}
76-
77-
return (
78-
<div className="lg:w-[60%] w-[80%] sm:px-8 mb-16 justify-center mt-8">
79-
<div className="w-full md:px-16">
80-
<p className="text-2xl font-bold text-white text-left lg:mt-4 mt-16 mb-7">Prüfungstermine</p>
81-
82-
<table className="w-full border-collapse hidden md:table">
83-
<tbody>
84-
{modules && modules.map((subject, index) => (
85-
<tr key={index}>
86-
<td className="px-1 py-1 text-[#9B9B9B]">{subject.name}</td>
87-
<td className="px-1 py-1 text-[#2AB19D]">DATE</td>
88-
<td className="px-1 py-1 text-[#9B9B9B]">TIME</td>
89-
<td className="px-1 py-1 text-[#9B9B9B]">ROOM</td>
90-
</tr>
91-
))}
92-
</tbody>
93-
</table>
94-
<div className="md:hidden space-y-4">
95-
{modules && modules.map((subject, index) => (
96-
<div key={index} className="p-3 shadow-sm">
97-
<p className="text-[#9B9B9B]">
98-
{subject.name}
99-
</p>
100-
<p className="text-[#2AB19D] inline">
101-
DATE
102-
</p>
103-
<p className="text-[#9B9B9B] inline ml-2">
104-
TIME ROOM
105-
</p>
106-
</div>
107-
))}
108-
</div>
109-
110-
<p className="text-2xl font-bold text-white text-left mt-9">Lerngruppen in dieser Woche</p>
111-
112-
<table className="w-full border-collapse hidden md:table">
113-
<tbody>
114-
{weeklyMeetings.map((meeting, index) => (
115-
<tr key={index}>
116-
<td className="px-1 py-1 text-[#9B9B9B]">{meeting.module}</td>
117-
<td className="px-1 py-1 text-[#2AB19D]">
118-
{new Date(meeting.dateFrom).toLocaleDateString()}
119-
</td>
120-
<td className="px-1 py-1 text-[#9B9B9B]">
121-
{new Date(meeting.dateFrom).toLocaleTimeString([], {
122-
hour: '2-digit',
123-
minute: '2-digit'
124-
})}
125-
</td>
126-
<td className="px-1 py-1 text-[#9B9B9B]">{meeting.place}</td>
127-
</tr>
128-
))}
129-
</tbody>
130-
</table>
131-
132-
<div className="md:hidden space-y-4">
133-
{weeklyMeetings.map((meeting, index) => (
134-
<div key={index} className="p-3 shadow-sm">
135-
<p className="text-[#9B9B9B]">
136-
{meeting.module}
137-
</p>
138-
<p className="text-[#2AB19D] inline">
139-
{new Date(meeting.dateFrom).toLocaleDateString()}
140-
</p>
141-
<p className="text-[#9B9B9B] inline ml-2">
142-
{new Date(meeting.dateFrom).toLocaleTimeString([], {
143-
hour: '2-digit',
144-
minute: '2-digit'
145-
})} {meeting.place}
146-
</p>
147-
</div>
148-
))}
149-
</div>
150-
151-
<p className="text-2xl font-bold text-white text-left mt-9 mb-6">Lernfortschritt</p>
152-
{modules && modules.map((subject, index) => (
153-
<div key={index} className="mb-6">
154-
<p className="text-m text-[#9B9B9B]">{subject.name}</p>
155-
<div onClick={() => {
156-
openModal(subject.name)
157-
}}>
158-
<ProgressBar progress={calculateProgress(subject)}/>
159-
</div>
160-
</div>
161-
))}
43+
}
44+
45+
function filterMeetingsForCurrentWeek(meetings: MeetingDto[]) {
46+
const now = new Date();
47+
const start = new Date(now.setDate(now.getDate() - now.getDay() + 1));
48+
const end = new Date(start);
49+
end.setDate(start.getDate() + 6);
50+
return meetings.filter(m => {
51+
const d = new Date(m.dateFrom);
52+
return d >= start && d <= end;
53+
});
54+
}
55+
56+
function calculateProgress(module: UserModule) {
57+
if (!module.chapter) return 0;
58+
let total = 0, checked = 0;
59+
module.chapter.forEach(ch => {
60+
total += ch.checkbox.length;
61+
checked += ch.checkbox.filter(b => b.checked).length;
62+
});
63+
return total === 0 ? 0 : checked / total;
64+
}
65+
66+
const openProgressModal = (name: string) => {
67+
setActiveModule(modules.find(m => m.name === name));
68+
setIsProgressModalOpen(true);
69+
};
70+
const closeProgressModal = () => setIsProgressModalOpen(false);
71+
72+
const openExamModal = () => setIsExamModalOpen(true);
73+
const closeExamModal = () => setIsExamModalOpen(false);
74+
75+
const handleAddExam = async (moduleName: string, date: string, time: string, room: string) => {
76+
const updated = modules.map(m =>
77+
m.name === moduleName
78+
? { ...m, examDate: date, examTime: time, examRoom: room }
79+
: m
80+
);
81+
try {
82+
await updateUserModules(axiosInstance, updated as BaseModule[]);
83+
setModules(updated);
84+
closeExamModal();
85+
} catch (e) {
86+
console.error("Fehler beim Speichern der Prüfung: ", e);
87+
}
88+
};
89+
90+
return (
91+
<div className="lg:w-[60%] w-[80%] sm:px-8 mb-16 justify-center mt-8">
92+
<div className="w-full md:px-16">
93+
<p className="text-2xl font-bold text-white text-left lg:mt-4 mt-16 mb-7">Prüfungstermine</p>
94+
<table className="w-full border-collapse hidden md:table">
95+
<tbody>
96+
{modules.map((subject, idx) => (
97+
<tr key={idx}>
98+
<td className="px-1 py-1 text-[#9B9B9B]">{subject.name}</td>
99+
<td className="px-1 py-1 text-[#2AB19D]">{subject.examDate ?? '--'}</td>
100+
<td className="px-1 py-1 text-[#9B9B9B]">{subject.examTime ?? '--'}</td>
101+
<td className="px-1 py-1 text-[#9B9B9B]">{subject.examRoom ?? '--'}</td>
102+
</tr>
103+
))}
104+
</tbody>
105+
</table>
106+
<div className="md:hidden space-y-4">
107+
{modules.map((subject, idx) => (
108+
<div key={idx} className="p-3 shadow-sm">
109+
<p className="text-[#9B9B9B]">{subject.name}</p>
110+
<p className="text-[#2AB19D] inline">{subject.examDate ?? '--'}</p>
111+
<p className="text-[#9B9B9B] inline ml-2">
112+
{subject.examTime ?? '--'} {subject.examRoom ?? ''}
113+
</p>
162114
</div>
163-
{
164-
isModalOpen && activeModule &&
165-
<ModuleProgressSettings onClose={closeModal} module={activeModule} allUserModules={modules}/>
166-
}
115+
))}
167116
</div>
168-
);
169-
}
117+
<button
118+
onClick={openExamModal}
119+
className="mt-4 bg-[#56A095] text-white py-2 px-4 rounded-lg hover:bg-[#42907a]"
120+
>
121+
Prüfungstermin bearbeiten
122+
</button>
123+
124+
<p className="text-2xl font-bold text-white text-left mt-9 mb-4">Lerngruppen in dieser Woche</p>
125+
<table className="w-full border-collapse hidden md:table">
126+
<tbody>
127+
{weeklyMeetings.map((meeting, index) => (
128+
<tr key={index}>
129+
<td className="px-1 py-1 text-[#9B9B9B]">{meeting.module}</td>
130+
<td className="px-1 py-1 text-[#2AB19D]">
131+
{new Date(meeting.dateFrom).toLocaleDateString()}
132+
</td>
133+
<td className="px-1 py-1 text-[#9B9B9B]">
134+
{new Date(meeting.dateFrom).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
135+
</td>
136+
<td className="px-1 py-1 text-[#9B9B9B]">{meeting.place}</td>
137+
</tr>
138+
))}
139+
</tbody>
140+
</table>
141+
<div className="md:hidden space-y-4">
142+
{weeklyMeetings.map((meeting, index) => (
143+
<div key={index} className="p-3 shadow-sm">
144+
<p className="text-[#9B9B9B]">{meeting.module}</p>
145+
<p className="text-[#2AB19D] inline">{new Date(meeting.dateFrom).toLocaleDateString()}</p>
146+
<p className="text-[#9B9B9B] inline ml-2">
147+
{new Date(meeting.dateFrom).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} {meeting.place}
148+
</p>
149+
</div>
150+
))}
151+
</div>
152+
153+
<p className="text-2xl font-bold text-white text-left mt-9 mb-6">Lernfortschritt</p>
154+
{modules.map((subject, index) => (
155+
<div key={index} className="mb-6">
156+
<p className="text-m text-[#9B9B9B]">{subject.name}</p>
157+
<div onClick={() => openProgressModal(subject.name)}>
158+
<ProgressBar progress={calculateProgress(subject)} />
159+
</div>
160+
</div>
161+
))}
162+
163+
{isProgressModalOpen && activeModule && (
164+
<ModuleProgressSettings
165+
onClose={closeProgressModal}
166+
module={activeModule}
167+
allUserModules={modules}
168+
/>
169+
)}
170+
{isExamModalOpen && (
171+
<ExamDateModal
172+
isOpen={isExamModalOpen}
173+
modules={modules}
174+
onClose={closeExamModal}
175+
onSubmit={handleAddExam}
176+
/>
177+
)}
178+
</div>
179+
</div>
180+
);
181+
}

0 commit comments

Comments
 (0)