rasp_web/src/App.svelte
2025-03-29 16:25:15 +05:00

590 lines
15 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script>
import ThemeSwitch from "./lib/ThemeSwitch.svelte";
import Dialog from "./lib/Dialog.svelte";
import Horizontal from "./lib/Horizontal.svelte";
import Vertical from "./lib/Vertical.svelte";
import Spinner from "./lib/Spinner.svelte";
import Cache from "./lib/Cache.svelte";
import Checked from "./lib/Checked.svelte";
import X from "./lib/X.svelte";
import { onMount } from "svelte";
const host = "https://rasp.ogkod.ru";
let start_date = new Date();
const day = start_date.getDay();
const diff = (day === 0 ? -6 : 1) - day;
start_date.setDate(start_date.getDate() + diff);
start_date.setHours(0, 0, 0, 0);
start_date = start_date;
let end_date = new Date();
end_date.setDate(start_date.getDate() + 6);
end_date.setHours(23, 59, 59, 999);
/** @typedef GroupInfo
* @property {string} name
* @property {'group' | 'teacher' | 'aud'} type
*/
/**
* @property {number} selected
* @property {Object} saved
*/
let data = {
/** @type {number} */
selected: 0,
/** @type {Array<GroupInfo>} */
saved: [],
};
function save_to_ls() {
data = data;
ui_sytle = ui_sytle;
localStorage.setItem("data", JSON.stringify(data));
localStorage.setItem("ui_sytle", ui_sytle);
get_rasp();
}
function empty_rasp() {
const empty_rasp = [];
for (let i = 0; i < 7; i++) {
empty_rasp.push({
date: "dd.mm.yyyy",
weekDay: "dayofweek",
isCurrentDate: 1,
pairs: [
{
N: 0,
time: "infinity",
isCurrentPair: 0,
schedulePairs: [],
},
],
});
}
return empty_rasp;
}
function scroll_to_today() {
if (rasp_data.some((day) => day.isCurrentDate === 1)) {
setTimeout(() => {
const today = new Date();
const today_index = `${(today.getDay() + 6) % 7}`;
document.getElementById(today_index).scrollIntoView();
}, 0);
}
}
function get_rasp() {
const s_date = start_date.getDate();
const s_month = start_date.getMonth() + 1;
const startDate = `${s_date < 10 ? 0 : ""}${s_date}.${s_month < 10 ? 0 : ""}${s_month}.${start_date.getFullYear()}`;
end_date = new Date(start_date);
end_date.setDate(start_date.getDate() + 6);
end_date.setHours(23, 59, 59, 999);
const e_date = end_date.getDate();
const e_month = end_date.getMonth() + 1;
const endDate = `${e_date < 10 ? 0 : ""}${e_date}.${e_month < 10 ? 0 : ""}${e_month}.${end_date.getFullYear()}`;
const date_full = `${startDate}-${endDate}`;
is_rasp_data_loading = true;
is_rasp_cache_loaded = false;
is_rasp_data_loaded = false;
let cache_data = {};
if (data.saved.length > 0) {
if (
localStorage.getItem(`${data.saved[data.selected].name}`) !==
null
) {
cache_data = JSON.parse(
localStorage.getItem(`${data.saved[data.selected].name}`),
);
if (date_full in cache_data) {
rasp_data = cache_data[date_full];
is_rasp_cache_loaded = true;
}
}
}
if (data.saved.length !== 0) {
fetch(
`${host}/schedule/?t=0.2&action=show&startDate=${startDate}&endDate=${endDate}&${data.saved[data.selected].type}=${data.saved[data.selected].name}`,
)
.then((response) => response.json())
.then((json) => {
if (json.length !== 0) {
cache_data[date_full] = json;
localStorage.setItem(
`${data.saved[data.selected].name}`,
JSON.stringify(cache_data),
);
rasp_data = json;
is_rasp_data_loaded = true;
is_rasp_cache_loaded = false;
} else {
is_rasp_data_loaded = false;
if (!is_rasp_cache_loaded) {
rasp_data = empty_rasp();
}
}
scroll_to_today();
is_rasp_data_loading = false;
})
.catch(() => {
is_rasp_data_loaded = false;
if (!is_rasp_cache_loaded) {
rasp_data = empty_rasp();
}
scroll_to_today();
is_rasp_data_loading = false;
});
// setTimeout(() => {
// is_rasp_data_loaded = true;
// is_rasp_data_loading = false;
// rasp_data = example;
// scroll_to_today();
// }, 1000);
} else {
is_rasp_data_loading = false;
}
}
let is_rasp_data_loading = false;
let is_rasp_data_loaded = false;
let is_rasp_cache_loaded = false;
let is_settings_open = false;
/** @type {'horizontal' | 'vertical'} */
let ui_sytle = "vertical";
/** @type {"group-list" | "teacher-list" | "aud-list"} */
let search_type = "group-list";
let search_value = "";
let rasp_data = [];
let search_data = [];
function search_group() {
if (search_value != "") {
fetch(
`${host}/schedule/?action=${search_type}&term=${search_value}`,
)
.then((response) => response.json())
.then((json) => {
search_data = json;
})
.catch(() => {
search_data = ["not found"];
});
}
}
$: input_date = `${start_date.getDate()}.${start_date.getMonth() + 1}.${start_date.getFullYear()} - ${end_date.getDate()}.${end_date.getMonth() + 1}.${end_date.getFullYear()}`;
onMount(() => {
if (localStorage.getItem("data") !== null) {
data = JSON.parse(localStorage.getItem("data"));
}
if (localStorage.getItem("ui_sytle") === null) {
ui_sytle = "vertical";
} else {
const style = localStorage.getItem("ui_sytle");
if (style === "horizontal") {
ui_sytle = "horizontal";
} else if (style === "vertical") {
ui_sytle = "vertical";
} else {
ui_sytle = "vertical";
}
}
get_rasp();
});
let is_confirm_open = false;
let confirm_question = "__default__";
let on_confirm = () => {};
</script>
<Dialog name={"Rasp"} bind:is_open={is_settings_open}>
<h1 class="mx-2">Сохраненные</h1>
<div
class="min-h-[270px] m-2
overflow-y-auto
border-[1px] border-[var(--w-border)] dark:border-[var(--b-border)]"
>
{#each data.saved as info, i}
<div
class="p-2
flex justify-between items-center
{i === data.saved.length - 1 ? '' : 'border-b-2'}
border-[var(--w-border)] dark:border-[var(--b-border)]"
>
<p>{info.name}</p>
<button
on:click={() => {
data.saved.splice(i, 1);
data.selected = 0;
save_to_ls();
}}
>
<img
src="/x.svg"
class="min-w-[40px] min-h-[40px] size-[40px] transition-all
border-2 border-[var(--w-red)] dark:border-[var(--b-red)]"
alt="data not loaded"
/>
</button>
</div>
{/each}
</div>
<h1 class="mx-2 mt-10">Добавить</h1>
<select
class="min-h-[60px] p-2 m-2 w-atuo
bg-[var(--w-bg)] dark:bg-[var(--b-bg)]
border-[1px] border-[var(--w-border)] dark:border-[var(--b-border)]"
on:change={(event) => {
search_type = event.target.value;
search_value = "";
search_data = [];
}}
>
<option selected value="group-list">Группа</option>
<option value="teacher-list">Преподаватель</option>
<option value="aud-list">Аудитория</option>
</select>
<input
type="text"
placeholder="{{
'group-list': 'Группа',
'teacher-list': 'Преподователь',
'aud-list': 'Аудитория',
}[search_type]} "
class="p-2 m-2
bg-[var(--w-bg)] dark:bg-[var(--b-bg)]
border-[1px] border-[var(--w-border)] dark:border-[var(--b-border)]"
bind:value={search_value}
on:input={() => {
const saved = search_value;
setTimeout(() => {
if (saved === search_value) {
search_group();
}
}, 1000);
}}
/>
<div
class="min-h-[270px] m-2
overflow-y-auto
border-[1px] border-[var(--w-border)] dark:border-[var(--b-border)]"
>
{#if search_data[0] === "not found"}
<p class="p-2">Не найдено</p>
{:else}
{#each search_data as item, i}
<div
class="p-2
flex justify-between items-center
{i === search_data.length - 1 ? '' : 'border-b-2'}
border-[var(--w-border)] dark:border-[var(--b-border)]"
>
{#if search_type === "teacher-list"}
<p>{item.label}</p>
{:else}
<p>{item}</p>
{/if}
<button
class="min-w-[40px] min-h-[40px]
text-3xl text-(--w-green) dark:text-[var(--b-green)]
border-[1px] border-[var(--w-green)] dark:border-[var(--b-green)]"
on:click={() => {
if (item === "not found") {
return;
}
data.saved.push({
type: {
"group-list": "group",
"teacher-list": "teacher",
"aud-list": "aud",
}[search_type],
name:
search_type === "teacher-list"
? item.label
: item,
});
search_value = "";
search_data = [];
data.selected = data.saved.length - 1;
save_to_ls();
get_rasp();
is_settings_open = false;
}}
>
+
</button>
</div>
{/each}
{/if}
</div>
<h1 class="mx-2 mt-10">Вид</h1>
<select
class="min-h-[60px] p-2 m-2 w-atuo
bg-[var(--w-bg)] dark:bg-[var(--b-bg)]
border-[1px] border-[var(--w-border)] dark:border-[var(--b-border)]"
on:change={(event) => {
ui_sytle = event.target.value;
save_to_ls();
}}
>
{#each [{ type: "vertical", name: "Вертикальный" }, { type: "horizontal", name: "Горизонтальный" }] as ui_sytle_name}
{#if ui_sytle_name.type === ui_sytle}
<option selected value={ui_sytle_name.type}>
{ui_sytle_name.name}
</option>
{:else}
<option value={ui_sytle_name.type}>{ui_sytle_name.name}</option>
{/if}
{/each}
</select>
<button
class="w-auto p-2 m-2 mt-10
bg-[var(--w-red)] dark:bg-[var(--b-red)]
border-[1px] border-[var(--w-border)] dark:border-[var(--b-border)]"
on:click={() => {
localStorage.clear();
save_to_ls();
is_settings_open = false;
get_rasp();
}}
>
Очистить Кэш
</button>
<button
class="w-auto p-2 m-2
bg-[var(--w-red)] dark:bg-[var(--b-red)]
border-[1px] border-[var(--w-border)] dark:border-[var(--b-border)]"
on:click={() => {
is_confirm_open = true;
confirm_question = "Действительно удалить сохраненные расписания?";
on_confirm = () => {
localStorage.clear();
is_settings_open = false;
is_confirm_open = false;
window.location.reload();
};
}}
>
Удалить все данные
</button>
<div class="mt-10">
<pre
class="w-min m-2 break-words flex justify-center items-center"><Spinner
></Spinner> - Загрузка</pre>
<div
class="w-auto m-2 space-x-2 break-words flex justify-start items-center"
>
<Cache></Cache>
<p class="max-[683px]:hidden">-</p>
<p>Показывает последние сохраненные данные</p>
</div>
<div
class="w-auto m-2 space-x-2 break-words flex justify-start items-center"
>
<X></X>
<p class="max-[424px]:hidden">-</p>
<p>Данные не загрузились</p>
</div>
<div
class="w-auto m-2 space-x-2 break-words flex justify-start items-center"
>
<Checked></Checked>
<p class="max-[546px]:hidden">-</p>
<p>Покавзывает актуальные данные</p>
</div>
</div>
<div
class="min-h-[100px] m-2 mt-10 w-auto bg-gray-700 flex justify-center items-center"
>
<a href="https://ogkod.ru" class="underline text-white">
Made by OGkod
</a>
</div>
</Dialog>
<div class="w-full flex justify-center items-center">
<div
class="h-svh
{ui_sytle === 'vertical' ? 'w-[1024px]' : 'w-[2120px]'}
text-[var(--w-text)] dark:text-[var(--b-text)] transition-all
flex relative"
>
<div
class="top-0 left-0 w-full h-full p-2
flex flex-col absolute"
>
<div class="flex justify-between items-center">
<button
class="text-3xl w-min m-2"
on:click={() => {
is_settings_open = true;
}}>Настройки</button
>
<div class="flex justify-center items-center space-x-4">
{#if is_rasp_data_loading}
<Spinner></Spinner>
{/if}
{#if is_rasp_cache_loaded}
<Cache></Cache>
{/if}
{#if is_rasp_data_loaded}
<Checked></Checked>
{/if}
{#if !is_rasp_data_loaded && !is_rasp_cache_loaded && !is_rasp_data_loading}
<X></X>
{/if}
<ThemeSwitch></ThemeSwitch>
</div>
</div>
<div class="relative flex justify-start items-center">
<input
type="date"
name="date_input"
id="date_input"
class="bg-black text-white absolute top-0 invisible"
on:change={(event) => {
start_date = new Date(event.target.value);
get_rasp();
}}
/>
<button
class="m-2 p-2 w-auto h-[40px] flex-initial
bg-[var(--w-bbg)] dark:bg-[var(--w-bbg)]
border-[1px] border-[var(--w-border)] dark:border-[var(--b-border)]"
on:click={() => {
document.getElementById("date_input").showPicker();
}}
>
<pre>{input_date}</pre>
</button>
<select
class="h-[40px] p-2 m-2 flex-auto w-full max-w-[280px]
bg-[var(--w-bg)] dark:bg-[var(--b-bg)]
border-[1px] border-[var(--w-border)] dark:border-[var(--b-border)]"
on:change={(event) => {
data.selected = Number(event.target.value);
save_to_ls();
get_rasp();
}}
>
{#each data.saved as group, i}
{#if data.selected === i}
<option selected value={i}>
{group.name}
</option>
{:else}
<option value={i}>{group.name}</option>
{/if}
{/each}
</select>
</div>
<div
class="flex
{ui_sytle === 'vertical'
? 'flex-col space-y-2 justify-center'
: 'overflow-x-auto overflow-y-hidden'}
text-[16px]"
>
{#if rasp_data.length === 0 && data.saved.length === 0}
<div class="overflow-y-auto">
<p
class="break-words h-auto
text-2xl text-[var(--w-text)] dark:text-[var(--b-text)]"
>
У вас нету сохраненных расписаний, добавтье их в <button
class="underline"
on:click={() => {
is_settings_open = true;
}}>настройках</button
>
</p>
<img
src="/tutor.gif"
class="max-w-[400px] w-full mt-4 dark:border-[1px] border-[var(--b-border)]"
alt="turoiral gif"
/>
</div>
{:else if ui_sytle === "horizontal"}
<Horizontal
bind:rasp_data
bind:is_rasp_data_loading
bind:is_rasp_data_loaded
bind:is_rasp_cache_loaded
></Horizontal>
{:else if ui_sytle === "vertical"}
<Vertical
bind:rasp_data
bind:is_rasp_data_loading
bind:is_rasp_data_loaded
bind:is_rasp_cache_loaded
></Vertical>
{/if}
</div>
</div>
</div>
</div>
<Dialog
name={confirm_question}
bind:is_open={is_confirm_open}
class_name="h-[220px]"
>
<button
on:click={on_confirm}
class="w-auto py-1 m-2
flex justify-center items-center
text-[var(--w-text)] dark:text-[var(--b-text)]
transition-all duration-300
bg-[var(--w-red)] dark:bg-[var(--b-red)]
border-2 border-[var(--border)] rounded-xl"
>
Да
</button>
<button
on:click={on_confirm}
class="w-auto py-1 m-2
flex justify-center items-center
text-[var(--w-text)] dark:text-[var(--b-text)]
transition-all duration-300
bg-[var(--w-green)] dark:bg-green-700
border-2 border-[var(--border)] rounded-xl"
>
Нет
</button>
</Dialog>