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