diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index c55a71e..4fb2688 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -22,34 +22,23 @@ model study_item {
}
model study_slot {
- id Int @id(map: "pk_study_slot") @default(autoincrement())
- study_item_id Int?
- study_item study_item? @relation(fields: [study_item_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "fk_study_slot_study_item")
- timetable_timetable_slot_1Tostudy_slot timetable[] @relation("timetable_slot_1Tostudy_slot")
- timetable_timetable_slot_2Tostudy_slot timetable[] @relation("timetable_slot_2Tostudy_slot")
- timetable_timetable_slot_3Tostudy_slot timetable[] @relation("timetable_slot_3Tostudy_slot")
- timetable_timetable_slot_4Tostudy_slot timetable[] @relation("timetable_slot_4Tostudy_slot")
- timetable_timetable_slot_5Tostudy_slot timetable[] @relation("timetable_slot_5Tostudy_slot")
- timetable_timetable_slot_6Tostudy_slot timetable[] @relation("timetable_slot_6Tostudy_slot")
+ id Int @id(map: "pk_study_slot") @default(autoincrement())
+ study_item_id Int
+ where String
+ studentsGroup String
+ position Int
+ study_item study_item @relation(fields: [study_item_id], references: [id], onDelete: Cascade, onUpdate: NoAction, map: "fk_study_slot_study_item")
+ timetable timetable? @relation(fields: [timetableId], references: [id])
+ timetableId Int?
}
model timetable {
- id Int @id(map: "pk_timetable") @default(autoincrement())
- slot_1 Int?
- slot_2 Int?
- slot_3 Int?
- slot_4 Int?
- slot_5 Int?
- slot_6 Int?
- day Int @db.SmallInt
- teacher Int
- study_slot_timetable_slot_1Tostudy_slot study_slot? @relation("timetable_slot_1Tostudy_slot", fields: [slot_1], references: [id], map: "fk_timetable_study_slot_1")
- study_slot_timetable_slot_2Tostudy_slot study_slot? @relation("timetable_slot_2Tostudy_slot", fields: [slot_2], references: [id], map: "fk_timetable_study_slot_2")
- study_slot_timetable_slot_3Tostudy_slot study_slot? @relation("timetable_slot_3Tostudy_slot", fields: [slot_3], references: [id], map: "fk_timetable_study_slot_3")
- study_slot_timetable_slot_4Tostudy_slot study_slot? @relation("timetable_slot_4Tostudy_slot", fields: [slot_4], references: [id], map: "fk_timetable_study_slot_4")
- study_slot_timetable_slot_5Tostudy_slot study_slot? @relation("timetable_slot_5Tostudy_slot", fields: [slot_5], references: [id], map: "fk_timetable_study_slot_5")
- study_slot_timetable_slot_6Tostudy_slot study_slot? @relation("timetable_slot_6Tostudy_slot", fields: [slot_6], references: [id], map: "fk_timetable_study_slot_6")
- users users @relation(fields: [teacher], references: [id], onDelete: Cascade, map: "fk_timetable_users_teacher")
+ id Int @id(map: "pk_timetable") @default(autoincrement())
+ day Int @db.SmallInt
+ odd Boolean
+ slots study_slot[]
+ teacher users @relation(fields: [teacherId], references: [id])
+ teacherId Int
}
model users {
diff --git a/src/components/StudyItemsList.astro b/src/components/StudyItemsList.astro
new file mode 100644
index 0000000..dd0b4f5
--- /dev/null
+++ b/src/components/StudyItemsList.astro
@@ -0,0 +1,82 @@
+---
+import { getStudyItems } from "../db";
+
+const studyItems = await getStudyItems();
+---
+
+
+
+
+
+
Предметы
+
+
+ {
+ studyItems.map((item) => (
+
+
{item.title}
+
+
+ ))
+ }
+ {studyItems.length === 0 &&
Предметов нет
}
+
+
+
+
+
+
+
diff --git a/src/components/TimetableEditor.astro b/src/components/TimetableEditor.astro
new file mode 100644
index 0000000..2cf89a5
--- /dev/null
+++ b/src/components/TimetableEditor.astro
@@ -0,0 +1,258 @@
+---
+import TimetableElement from "./TimetableElement.astro";
+
+import type { users, timetable } from "@prisma/client";
+import { getUserFromLogin, searchUsers, getTimetable, createTimetable } from "../db";
+
+const allUsers = await searchUsers({
+ isAdmin: false,
+});
+
+let editedUser: users | null = null;
+const sLogin = Astro.url.searchParams.get("login");
+if (sLogin !== null) {
+ editedUser = await getUserFromLogin(sLogin);
+}
+
+let oddTT: timetable[] = [];
+let evenTT: timetable[] = [];
+if (editedUser !== null) {
+ oddTT = await getTimetable(editedUser.id, true);
+ if (oddTT.length === 0) {
+ await createTimetable(editedUser.id, true);
+ oddTT = await getTimetable(editedUser.id, true);
+ }
+ evenTT = await getTimetable(editedUser.id, false);
+ if (evenTT.length === 0) {
+ await createTimetable(editedUser.id, false);
+ evenTT = await getTimetable(editedUser.id, false);
+ }
+}
+---
+
+
+ {
+ editedUser === null && (
+
+ )
+ }
+ {
+ editedUser !== null && (
+ <>
+
+
+
Нечётная неделя
+
+
+
+
+
+
+
+
+
+
+
Чётная неделя
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )
+ }
+
+
+
+
+
diff --git a/src/components/TimetableElement.astro b/src/components/TimetableElement.astro
new file mode 100644
index 0000000..26bef36
--- /dev/null
+++ b/src/components/TimetableElement.astro
@@ -0,0 +1,30 @@
+---
+import TimetableSlotElement from "./TimetableSlotElement.astro";
+
+import type { timetable } from "@prisma/client";
+import { getTimetableSlots } from "../db";
+
+export interface Props {
+ tt: timetable;
+ position: number;
+}
+
+const { tt, position } = Astro.props;
+
+const dayNames = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];
+
+const slots = await getTimetableSlots(tt.id);
+---
+
+
+
+
{dayNames[tt.day]}
+
+
+ {
+ [0, 1, 2, 3, 4, 5].map((i) => (
+ slot.position == i) ?? null} position={i} day={tt.day} isOdd={tt.odd} />
+ ))
+ }
+
+
diff --git a/src/components/TimetableElementViewer.astro b/src/components/TimetableElementViewer.astro
new file mode 100644
index 0000000..a35bd1e
--- /dev/null
+++ b/src/components/TimetableElementViewer.astro
@@ -0,0 +1,33 @@
+---
+import TimetableSlotElementViewer from "./TimetableSlotElementViewer.astro";
+
+import type { timetable } from "@prisma/client";
+import { getTimetableSlots } from "../db";
+
+export interface Props {
+ tt: timetable;
+ position: number;
+}
+
+const { tt, position } = Astro.props;
+
+const dayNames = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"];
+
+const slots = await getTimetableSlots(tt.id);
+const none = slots.length == 0;
+---
+
+{
+ !none && (
+
+
+
{dayNames[tt.day]}
+
+
+ {[0, 1, 2, 3, 4, 5].map((i) => (
+ slot.position == i) ?? null} position={i} day={tt.day} isOdd={tt.odd} />
+ ))}
+
+
+ )
+}
diff --git a/src/components/TimetableSlotElement.astro b/src/components/TimetableSlotElement.astro
new file mode 100644
index 0000000..a2c7f93
--- /dev/null
+++ b/src/components/TimetableSlotElement.astro
@@ -0,0 +1,120 @@
+---
+import type { study_slot, study_item } from "@prisma/client";
+import { getStudyItem } from "../db";
+
+export interface Props {
+ ss: study_slot | null;
+ position: number;
+ day: number;
+ isOdd: boolean;
+}
+
+const { ss, position, day, isOdd } = Astro.props;
+
+const positionToTime = ["08:30-10:05", "10:15-11:50", "12:00-13:35", "14:10-15:45", "15:55-17:30", "17:40-19:15"];
+
+let studyItem: study_item | null = null;
+if (ss !== null) {
+ studyItem = await getStudyItem(ss.id);
+}
+---
+
+
+
+ {
+ ss === null && (
+
+ )
+ }
+ {
+ ss !== null && (
+
+ )
+ }
+
+
{positionToTime[position]}
+
+
+ {
+ ss !== null && (
+ <>
+
+
+
{studyItem!.title}
+
{positionToTime[position]}
+
Где: {ss.where}
+
Группа(ы): {ss.studentsGroup}
+
+ >
+ )
+ }
+
+
+
diff --git a/src/components/TimetableSlotElementViewer.astro b/src/components/TimetableSlotElementViewer.astro
new file mode 100644
index 0000000..aa7b0b2
--- /dev/null
+++ b/src/components/TimetableSlotElementViewer.astro
@@ -0,0 +1,44 @@
+---
+import type { study_slot, study_item } from "@prisma/client";
+import { getStudyItem } from "../db";
+
+export interface Props {
+ ss: study_slot | null;
+ position: number;
+ day: number;
+ isOdd: boolean;
+}
+
+const { ss, position, day, isOdd } = Astro.props;
+
+const positionToTime = ["08:30-10:05", "10:15-11:50", "12:00-13:35", "14:10-15:45", "15:55-17:30", "17:40-19:15"];
+
+let studyItem: study_item | null = null;
+if (ss !== null) {
+ studyItem = await getStudyItem(ss.id);
+}
+---
+
+{
+ ss !== null && (
+
+
+
{studyItem!.title}
+
{positionToTime[position]}
+
Где: {ss.where}
+
Группа(ы): {ss.studentsGroup}
+
+
+ )
+}
+{
+ /*
+ ss === null && (
+
+
+
Свободно
+
{positionToTime[position]}
+
+
+ )*/
+}
diff --git a/src/components/TimetableViewer.astro b/src/components/TimetableViewer.astro
new file mode 100644
index 0000000..31e1cff
--- /dev/null
+++ b/src/components/TimetableViewer.astro
@@ -0,0 +1,51 @@
+---
+import TimetableElementViewer from "./TimetableElementViewer.astro";
+
+import type { users, timetable } from "@prisma/client";
+import { getTimetable, createTimetable } from "../db";
+
+export interface Props {
+ user: users;
+}
+
+const { user } = Astro.props;
+
+let oddTT: timetable[] = await getTimetable(user.id, true);
+let evenTT: timetable[] = await getTimetable(user.id, false);
+
+if (oddTT.length === 0) {
+ await createTimetable(user.id, true);
+ oddTT = await getTimetable(user.id, true);
+}
+if (evenTT.length === 0) {
+ await createTimetable(user.id, false);
+ evenTT = await getTimetable(user.id, false);
+}
+---
+
+
+
+
+
Нечётная неделя
+
+
+
+
+
+
+
+
+
+
+
Чётная неделя
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/db.ts b/src/db.ts
index b37857b..3454859 100644
--- a/src/db.ts
+++ b/src/db.ts
@@ -218,3 +218,156 @@ export async function searchUsers(params: { login?: string; isAdmin?: boolean; f
});
return users;
}
+
+export async function getStudyItems() {
+ const items = await client.study_item.findMany({
+ orderBy: {
+ id: "asc",
+ },
+ });
+ return items;
+}
+
+export async function createStudyItem(title: string) {
+ const item = await client.study_item.create({
+ data: {
+ title: title,
+ },
+ });
+ return item !== null;
+}
+
+export async function deleteStudyItem(id: number) {
+ const result = await client.study_item.delete({
+ where: {
+ id: id,
+ },
+ });
+ return result !== null;
+}
+
+export async function getTimetable(userId: number, isOdd: boolean) {
+ const tt = await client.timetable.findMany({
+ where: {
+ teacherId: userId,
+ odd: isOdd,
+ },
+ orderBy: {
+ day: "asc",
+ },
+ });
+ return tt;
+}
+
+export async function createTimetable(userId: number, isOdd: boolean) {
+ const tt = await client.timetable.createMany({
+ data: [
+ { day: 0, odd: isOdd, teacherId: userId },
+ { day: 1, odd: isOdd, teacherId: userId },
+ { day: 2, odd: isOdd, teacherId: userId },
+ { day: 3, odd: isOdd, teacherId: userId },
+ { day: 4, odd: isOdd, teacherId: userId },
+ { day: 5, odd: isOdd, teacherId: userId },
+ ],
+ });
+ return tt.count === 6;
+}
+
+export async function getTimetableSlots(ttId: number) {
+ const slots = await client.study_slot.findMany({
+ where: {
+ timetableId: ttId,
+ },
+ orderBy: {
+ position: "asc",
+ },
+ });
+ return slots;
+}
+
+export async function getStudyItem(ttSlotId: number) {
+ const slot = await client.study_slot.findFirst({
+ where: {
+ id: ttSlotId,
+ },
+ });
+ const item = await client.study_item.findFirst({
+ where: {
+ id: slot!.study_item_id,
+ },
+ });
+ return item;
+}
+
+export type UpdateTTElementModel = {
+ isWindow: boolean;
+ position: number;
+ day: number;
+ studyItem: number;
+ slotPlace: string;
+ slotGroup: string;
+};
+
+export async function updateUserTimetable(userLogin: string, oddUpdate: UpdateTTElementModel[][], evenUpdate: UpdateTTElementModel[][]) {
+ const user = await getUserFromLogin(userLogin);
+ const oddTT = await client.timetable.findMany({
+ where: {
+ teacherId: user!.id,
+ odd: true,
+ },
+ });
+ const evenTT = await client.timetable.findMany({
+ where: {
+ teacherId: user!.id,
+ odd: false,
+ },
+ });
+ for (let odt of oddTT) {
+ await client.study_slot.deleteMany({
+ where: {
+ timetableId: odt.id,
+ },
+ });
+ }
+ for (let edt of evenTT) {
+ await client.study_slot.deleteMany({
+ where: {
+ timetableId: edt.id,
+ },
+ });
+ }
+ await client.study_slot.createMany({
+ data: oddUpdate
+ .map((e) => {
+ return e
+ .filter((e) => !e.isWindow)
+ .map((el) => {
+ return {
+ timetableId: oddTT.find((e) => e.day === el.day)!.id,
+ position: el.position,
+ study_item_id: el.studyItem,
+ where: el.slotPlace,
+ studentsGroup: el.slotGroup,
+ };
+ });
+ })
+ .reduce((acc, val) => acc.concat(val), []),
+ });
+ await client.study_slot.createMany({
+ data: evenUpdate
+ .map((e) => {
+ return e
+ .filter((e) => !e.isWindow)
+ .map((el) => {
+ return {
+ timetableId: evenTT.find((e) => e.day === el.day)!.id,
+ position: el.position,
+ study_item_id: el.studyItem,
+ where: el.slotPlace,
+ studentsGroup: el.slotGroup,
+ };
+ });
+ })
+ .reduce((acc, val) => acc.concat(val), []),
+ });
+}
diff --git a/src/pages/timetable.astro b/src/pages/timetable.astro
new file mode 100644
index 0000000..e4bfa68
--- /dev/null
+++ b/src/pages/timetable.astro
@@ -0,0 +1,32 @@
+---
+import Layout from "../layouts/Layout.astro";
+import Navbar from "../components/Navbar.astro";
+import TimetableViewer from "../components/TimetableViewer.astro";
+import StudyItemsList from "../components/StudyItemsList.astro";
+import TimetableEditor from "../components/TimetableEditor.astro";
+
+import { getUserSession, getSessionUser } from "../db";
+
+if (Astro.cookies.has("session")) {
+ const sessId = Astro.cookies.get("session").value!;
+ const dbSess = await getUserSession(sessId);
+ if (dbSess === null) {
+ Astro.cookies.delete("session");
+ return Astro.redirect("/login");
+ }
+} else {
+ return Astro.redirect("/login");
+}
+
+const sessId = Astro.cookies.get("session").value!;
+const user = (await getSessionUser(sessId))!;
+---
+
+
+
+
+ {user.is_admin && }
+ {user.is_admin && }
+ {!user.is_admin && }
+
+
diff --git a/src/pages/ttapi/createStudyItem.ts b/src/pages/ttapi/createStudyItem.ts
new file mode 100644
index 0000000..0f9cabf
--- /dev/null
+++ b/src/pages/ttapi/createStudyItem.ts
@@ -0,0 +1,39 @@
+import type { APIContext } from "astro";
+import { createStudyItem, getSessionUser } from "../../db";
+import { Prisma } from "@prisma/client";
+
+export async function post({ request, cookies }: APIContext) {
+ const response: { ok: boolean; reason?: string } = {
+ ok: true,
+ };
+ try {
+ const sessId = cookies.get("session").value!;
+ const user = (await getSessionUser(sessId))!;
+ if (!user.is_admin) {
+ throw new Error("Доступно только администраторам");
+ }
+
+ const formData = await request.formData();
+ const studyItemName = formData.get("studyItemName");
+ if (studyItemName === null) {
+ throw new Error("Не предоставлены данные создания предмета");
+ }
+
+ await createStudyItem(studyItemName.toString());
+
+ response.ok = true;
+ } catch (e: any) {
+ response.ok = false;
+ if (e instanceof Prisma.PrismaClientKnownRequestError) {
+ response.reason = `Неизвестная ошибка базы данных. Код ${e.code}`;
+ } else if (e instanceof Error) {
+ response.reason = e.message.trim();
+ } else {
+ response.reason = e.toString().trim();
+ }
+ }
+
+ return {
+ body: JSON.stringify(response),
+ };
+}
diff --git a/src/pages/ttapi/deleteStudyItem.ts b/src/pages/ttapi/deleteStudyItem.ts
new file mode 100644
index 0000000..257cb7c
--- /dev/null
+++ b/src/pages/ttapi/deleteStudyItem.ts
@@ -0,0 +1,39 @@
+import type { APIContext } from "astro";
+import { deleteStudyItem, getSessionUser } from "../../db";
+import { Prisma } from "@prisma/client";
+
+export async function post({ request, cookies }: APIContext) {
+ const response: { ok: boolean; reason?: string } = {
+ ok: true,
+ };
+ try {
+ const sessId = cookies.get("session").value!;
+ const user = (await getSessionUser(sessId))!;
+ if (!user.is_admin) {
+ throw new Error("Доступно только администраторам");
+ }
+
+ const formData = await request.formData();
+ const itemId = formData.get("itemId");
+ if (itemId === null) {
+ throw new Error("Не предоставлены данные создания предмета");
+ }
+
+ await deleteStudyItem(parseInt(itemId.toString()));
+
+ response.ok = true;
+ } catch (e: any) {
+ response.ok = false;
+ if (e instanceof Prisma.PrismaClientKnownRequestError) {
+ response.reason = `Неизвестная ошибка базы данных. Код ${e.code}`;
+ } else if (e instanceof Error) {
+ response.reason = e.message.trim();
+ } else {
+ response.reason = e.toString().trim();
+ }
+ }
+
+ return {
+ body: JSON.stringify(response),
+ };
+}
diff --git a/src/pages/ttapi/updateTimetable.ts b/src/pages/ttapi/updateTimetable.ts
new file mode 100644
index 0000000..5f31e5b
--- /dev/null
+++ b/src/pages/ttapi/updateTimetable.ts
@@ -0,0 +1,44 @@
+import type { APIContext } from "astro";
+import { getSessionUser, updateUserTimetable, UpdateTTElementModel } from "../../db";
+import { Prisma } from "@prisma/client";
+
+type UpdateTTModel = {
+ login: string;
+ odd: UpdateTTElementModel[][];
+ even: UpdateTTElementModel[][];
+};
+
+export async function post({ request, cookies }: APIContext) {
+ const response: { ok: boolean; reason?: string } = {
+ ok: true,
+ };
+ try {
+ const sessId = cookies.get("session").value!;
+ const user = (await getSessionUser(sessId))!;
+ if (!user.is_admin) {
+ throw new Error("Доступно только администраторам");
+ }
+
+ const data: UpdateTTModel = await request.json();
+ if (data === null || data.login === null || data.odd === null || data.even === null) {
+ throw new Error("Не предоставлены данные обновления расписания");
+ }
+
+ await updateUserTimetable(data.login, data.odd, data.even);
+
+ response.ok = true;
+ } catch (e: any) {
+ response.ok = false;
+ if (e instanceof Prisma.PrismaClientKnownRequestError) {
+ response.reason = `Неизвестная ошибка базы данных. Код ${e.code}`;
+ } else if (e instanceof Error) {
+ response.reason = e.message.trim();
+ } else {
+ response.reason = e.toString().trim();
+ }
+ }
+
+ return {
+ body: JSON.stringify(response),
+ };
+}