1
0

feat: Added viewer

This commit is contained in:
2026-02-12 17:45:47 +01:00
parent 5ac84e3690
commit cea8cdf4ee
44 changed files with 16005 additions and 9 deletions

156
viewer/app/view.tsx Normal file
View File

@@ -0,0 +1,156 @@
"use client";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { SubstitutionData, LocalData } from "@/lib/types";
import { Card, CardContent } from "@/components/ui/card";
import ScheduleViewer from "@/components/own/schedule-viewer";
interface ViewProps {
data: SubstitutionData | null;
}
interface FormValues {
className: string;
jsonFile: FileList | null;
}
export default function View({ data }: ViewProps) {
const [loading, setLoading] = useState(true);
const [localData, setLocalData] = useState<LocalData | null>(null);
const form = useForm<FormValues>({
defaultValues: {
className: "",
jsonFile: null,
},
});
const onSubmit = async (values: FormValues) => {
const classNameProcessed = values.className.toLowerCase();
const file = values.jsonFile?.[0];
if (!file) {
alert("No file uploaded!");
return;
}
try {
const text = await file.text();
const jsonData = JSON.parse(text);
console.log(jsonData)
const foundKey = Object.keys(jsonData).find(k => k.toLowerCase() === classNameProcessed);
if (!foundKey) {
alert("Class not found in file!");
return;
}
const data = {
class: classNameProcessed,
timetable: jsonData[foundKey]
};
setLocalData(data);
localStorage.setItem("data", JSON.stringify(data));
} catch (err) {
console.log(err)
alert("Invalid JSON file.");
}
};
useEffect(() => {
const saved = localStorage.getItem("data");
if (saved) {
setLocalData(JSON.parse(saved));
}
setLoading(false);
}, [data]);
if (loading) return <p>Loading...</p>;
if (!localData) {
return (
<div className="flex justify-center w-full">
<Card className="my-8 max-w-200">
<CardContent>
<Form {...form} >
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="className"
rules={{
required: "Třída je povinná",
pattern: {
value: /^[AEC][1-4][a-c]?$/i,
message: "Neplatný název třídy"
},
}}
render={({ field }) => (
<FormItem>
<FormLabel>Třída</FormLabel>
<FormControl>
<Input {...field} placeholder="C2c" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="jsonFile"
rules={{ required: "Soubor je povinný" }}
render={({ field }) => (
<FormItem>
<FormLabel>Soubor</FormLabel>
<FormControl>
<input
type="file"
accept=".json"
onChange={(e) => field.onChange(e.target.files)}
className="cursor-pointer"
/>
</FormControl>
<FormDescription>
<a href="/posts/viewer/getting_file" target="_blank" className="hover:underline">
Jak získat soubor?
</a>
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" className="cursor-pointer">Odeslat</Button>
</form>
</Form>
</CardContent>
</Card>
</div>
);
}
return (
<div className="w-full max-w-[1920px] mx-auto p-4 space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold">Rozvrh třídy {localData.class}</h1>
<Button variant="outline" onClick={() => setLocalData(null)}>Změnit třídu/soubor</Button>
</div>
<ScheduleViewer localData={localData} substitutionData={data} />
</div>
);
}