diff --git a/src/components/DefaultLayout.tsx b/src/components/DefaultLayout.tsx index 7796caa9ac6f06bbacdb4244f515820100d101a8..e25d887cb3dc5d0a0277635a88648857db3422e3 100644 --- a/src/components/DefaultLayout.tsx +++ b/src/components/DefaultLayout.tsx @@ -261,6 +261,11 @@ function UserField({ isUnavailable }: { isUnavailable: boolean }) { Feedback </Dropdown.Item> </li> + <li> + <Dropdown.Item as={Link} href="/internal/timetable"> + Drehplan + </Dropdown.Item> + </li> <li className="dropdown-divider" /> <li> <Dropdown.Item as={Link} href="/internal/user"> diff --git a/src/pages/internal/timetable.tsx b/src/pages/internal/timetable.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9f349d70a6ab7208c017811cf6b948735569acae --- /dev/null +++ b/src/pages/internal/timetable.tsx @@ -0,0 +1,217 @@ +import ModeratorBarrier from "@/components/ModeratorBarrier"; +import { DateTime } from "luxon"; +import Link from "next/link"; + +interface Days { + index: number; + date: DateTime; + lectures: Lecture[]; + maxcol: number; +} + +interface Lecture { + time: DateTime; + time_end: DateTime; + duration: number; + timetable_col: number; + course_id: number; + id: number; + place: string; + visible: boolean; + course: { + visible: boolean; + short: string; + }; +} + +function TimetableTable() { + // example data + const blocks: DateTime[] = []; + for (let i = 0; i < (20 - 8) * 4; i++) { + blocks.push( + DateTime.now() + .startOf("week") + .plus({ days: 0, hours: 8 + Math.floor(i / 4), minutes: (i % 4) * 15 }), + ); + } + const days: Days[] = []; + for (let i = 0; i < 5; i++) { + days.push({ + index: i, + date: DateTime.now().startOf("week").plus({ days: i }), + lectures: [], + maxcol: 1, + }); + } + + // example lecture + days[0].lectures.push({ + time: DateTime.now().startOf("week").plus({ days: 0, hours: 9, minutes: 30 }), + time_end: DateTime.now().startOf("week").plus({ days: 0, hours: 10, minutes: 45 }), + duration: 75, + timetable_col: 0, + course_id: 1, + id: 1, + place: "Online", + visible: true, + course: { + visible: true, + short: "algomod", + }, + }); + + return ( + <div className="row table-responsive"> + <table + className="table table-bordered timetable col-xs-12 w-auto" + style={{ minWidth: "100%" }} + > + <thead> + <tr> + <th style={{ width: "30px" }}></th> + {days.map((d) => ( + <th key={d.index} style={{ minWidth: "10em" }} colSpan={d.maxcol}> + {d.date.toLocaleString(DateTime.DATE_MED_WITH_WEEKDAY)} + </th> + ))} + </tr> + </thead> + + <tbody> + {blocks.map((t, time_index) => ( + <tr key={time_index} className={t.minute === 0 ? "hourlytime" : ""}> + {time_index % 4 === 0 && ( + <td + rowSpan={time_index === blocks.length - 1 ? undefined : 4} + style={{ verticalAlign: "top" }} + > + {t.toFormat("HH:mm")} + </td> + )} + {days.map((d) => { + const cols = []; + for (let i = 0; i < d.maxcol; i++) { + cols.push(i); + } + return cols.map((col) => { + const l = d.lectures.find( + (l) => + l.timetable_col === col && + l.time >= t && + l.time < blocks[time_index + 1], + ); + if (l) { + const rowSpan = Math.ceil( + (l.duration + + Math.abs( + t.minute + + t.hour * 60 - + (l.time.minute + l.time.hour * 60), + )) / + 15, + ); + return ( + <td + key={col} + rowSpan={rowSpan} + className={ + l.visible && l.course.visible + ? "course" + : "course-invisible" + } + > + <p className="small"> + <strong> + <Link + href={`/${l.course_id}#lecture-${l.id}`} + > + {l.course.short} + </Link> + </strong> + <br /> + {l.time.toFormat("HH:mm")} -{" "} + {l.time_end.toFormat("HH:mm")} + <br /> + {l.place} + </p> + </td> + ); + } else { + // if the lecture is in the current column but not the current time slot + const l = d.lectures.find( + (l: any) => + l.timetable_col === col && + l.time < t && + l.time_end > t, + ); + if (l) { + return null; // this part is covered by another lecture using rowspan + } else { + return ( + <td + key={col} + className={col === 0 ? "newday" : ""} + ></td> + ); // no lecture right now, just end column + } + } + }); + })} + </tr> + ))} + </tbody> + </table> + </div> + ); +} + +function TimetableImpl() { + return ( + <div className="card"> + <div className="card-header d-flex">Timetable</div> + <div className="card-body"> + This page is a work in progress, the data here is not real. + <hr /> + <TimetableTable /> + </div> + </div> + /* TODO: pagination + <div className="hidden-print"> + <div style="margin-top: 10px; padding: 15px;" className="col-xs-12"> + {% if user %} + <a href="{{url_for('timetable_user', user=user.id, kw=kw-1) }}" className="pull-left btn btn-default">{{ "<<" }}</a> + <a href="{{url_for('timetable_user', user=user.id, kw=kw+1) }}" className="pull-right btn btn-default">{{ ">>" }}</a> + <a href="{{url_for('timetable_user', user=user.id, kw=0) }}" style="width: 80px;" className="btn btn-default center-block">today</a> + {% else %} + <a href="{{url_for('timetable', kw=kw-1) }}" className="pull-left btn btn-default">{{ "<<" }}</a> + <a href="{{url_for('timetable', kw=kw+1) }}" className="pull-right btn btn-default">{{ ">>" }}</a> + <a href="{{url_for('timetable', kw=0) }}" style="width: 80px;" className="btn btn-default center-block">today</a> + {% endif %} + </div> + <input id="weeksel" type="week" className="center-block" value="{{ weekofyear }}"/> + <script> + $( function () { + $("#weeksel").on("change", function() { + {% if user %} + window.location.href="{{ url_for('timetable_user', user=user.id) }}?date="+$("#weeksel").val() + {% else %} + window.location.href="{{ url_for('timetable') }}?date="+$("#weeksel").val() + {% endif %} + }); + }); + </script> + </div> + + </div> +</div> + */ + ); +} + +export default function Timetable() { + return ( + <ModeratorBarrier> + <TimetableImpl /> + </ModeratorBarrier> + ); +} diff --git a/src/styles/globals.scss b/src/styles/globals.scss index 5ad7270eb864ad43dbe259fc369f15c4c130fa68..cc0c97373a3792c7152f2e0387c5fd07a390a016 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -7,6 +7,8 @@ $link-decoration: none; @import 'bootstrap-icons/font/bootstrap-icons.css'; +@import 'timetable.scss'; + [data-bs-theme="light"] { /* We use the light theme for dark and light (Theme can't be set by css)*/ --toastify-color-light: #f2f2f2; diff --git a/src/styles/legacy_style.css b/src/styles/legacy_style.css index 106369eb9b6705f26cb62b6cbb514cd83fd6c83c..2990a710eb0f695926063528211e52ac2fc31e44 100644 --- a/src/styles/legacy_style.css +++ b/src/styles/legacy_style.css @@ -11,31 +11,6 @@ text-shadow: 0 0 2px black; } -#timetable.table-bordered td:first-child { - border-left: 2px solid #ddd; -} -#timetable.table-bordered { - border-collapse: separate; - table-layout: fixed; -} - -#timetable.table-bordered tr.hourlytime > td { - border-top-width: 2px; - border-top-color: #ccc; -} - -#timetable.table-bordered tr > td { - border-bottom: 0px; -} - -#timetable.table-bordered td.newday { - border-left: 2px solid black; -} - -#timetable.table-bordered td { - border-right: 0px !important; -} - .thumbnailimg { height: 130px; position: relative; @@ -78,10 +53,6 @@ transform: translate(-50%, -50%); } -.plot-error { - -} - @-webkit-keyframes spin { 0% { -webkit-transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); } @@ -120,7 +91,6 @@ th.rotate > div > span { .tooltip-inner { max-width: 500px; - } #cutprogress td { diff --git a/src/styles/timetable.scss b/src/styles/timetable.scss new file mode 100644 index 0000000000000000000000000000000000000000..f00e928b1ee485ae1076aa8ad7dccef23999dc3a --- /dev/null +++ b/src/styles/timetable.scss @@ -0,0 +1,58 @@ +[data-bs-theme="light"] { + .timetable.table-bordered td:first-child { + border-left: 2px solid #ddd; + } + + .timetable.table-bordered tr.hourlytime>td { + border-top-width: 2px; + border-top-color: #ccc; + } + + .timetable.table-bordered td.newday { + border-left: 2px solid black; + } + + .timetable.table-bordered tr>td.course { + background: lightgray; + } + + .timetable.table-bordered tr>td.course-invisible { + background: #f2dede; + } +} + +[data-bs-theme="dark"] { + .timetable.table-bordered td:first-child { + // border-left: 2px solid #ddd; + } + + .timetable.table-bordered tr.hourlytime>td { + border-top-width: 2px; + border-top-color: rgb(82, 90, 97); + } + + .timetable.table-bordered td.newday { + border-left: 2px solid rgb(82, 90, 97); + } + + .timetable.table-bordered tr>td.course { + background: #404046; + } + + .timetable.table-bordered tr>td.course-invisible { + background: #504040; + } +} + +.timetable.table-bordered { + // border-collapse: separate; + table-layout: fixed; +} + +.timetable.table-bordered tr>td { + border-bottom: 0px; +} + +.timetable.table-bordered td { + border-right: 0px !important; +} \ No newline at end of file