Skip to content
Snippets Groups Projects
Commit 42f26fe3 authored by Dorian Koch's avatar Dorian Koch
Browse files

ts: Allow multiple courses on import, Fix misc bugs

parent 3008351b
No related branches found
No related tags found
No related merge requests found
...@@ -175,7 +175,19 @@ export class BackendImpl { ...@@ -175,7 +175,19 @@ export class BackendImpl {
this.fetch( this.fetch(
`${this.baseUrl()}/object_management/${object_type}/${object_id}/configuration`, `${this.baseUrl()}/object_management/${object_type}/${object_id}/configuration`,
), ),
); ).then((res) => {
// patch object to use the expected format
res.fields = res.fields?.map((entr: any) => {
if (entr.description) {
// take all keys and values of the description and put them in the original object
Object.keys(entr.description).forEach((key) => {
entr[key] = entr.description[key];
});
}
return entr;
});
return res;
});
} }
// GET /object_management/{object_type}/new/configuration // GET /object_management/{object_type}/new/configuration
......
...@@ -132,17 +132,29 @@ interface field_entry_description { ...@@ -132,17 +132,29 @@ interface field_entry_description {
id: id_string; id: id_string;
type: id_string; type: id_string;
may_be_null?: boolean; may_be_null?: boolean;
is_modifiable: boolean;
default_value?: any;
min_value?: int | long; min_value?: int | long;
max_value?: int | long; max_value?: int | long;
min_length?: int; min_length?: int;
max_length?: int; max_length?: int;
default_value?: any;
} }
interface field_entry { interface field_entry {
value: any; value: any;
description: field_entry_description;
id: id_string;
type: id_string;
may_be_null?: boolean;
min_value?: int | long;
max_value?: int | long;
min_length?: int;
max_length?: int;
default_value?: any;
// description?: field_entry_description;
} }
interface changelog_entry { interface changelog_entry {
...@@ -154,11 +166,9 @@ interface changelog_entry { ...@@ -154,11 +166,9 @@ interface changelog_entry {
// type: modification // type: modification
object_type?: id_string; object_type?: id_string;
field_id?: id_string;
field_type?: id_string;
is_field_modifiable?: boolean;
old_value?: any; old_value?: any;
new_value?: any; new_value?: any;
field_description?: field_entry_description;
// type: unknown // type: unknown
unknown_type: string; unknown_type: string;
......
...@@ -26,26 +26,22 @@ export function OMFieldComponent({ ...@@ -26,26 +26,22 @@ export function OMFieldComponent({
hideLabel?: boolean; hideLabel?: boolean;
[key: string]: any; [key: string]: any;
}) { }) {
const [val, setVal] = useState(field.value ?? field.description.default_value); const [val, setVal] = useState(field.value ?? field.default_value);
const language = useLanguage(); const language = useLanguage();
const hasChanged = const hasChanged = val !== (field.value ?? field.default_value) && val !== "unchosen";
val !== (field.value ?? field.description.default_value) && val !== "unchosen";
if ( if (field.default_value !== undefined && patch.current[field.id] === undefined) {
field.description.default_value !== undefined && patch.current[field.id] = field.default_value;
patch.current[field.description.id] === undefined
) {
patch.current[field.description.id] = field.description.default_value;
} }
useEffect(() => { useEffect(() => {
if (hasChanged) { if (hasChanged) {
setVal(field.value ?? field.description.default_value); setVal(field.value ?? field.default_value);
patch.current[field.description.id] = field.value ?? field.description.default_value; patch.current[field.id] = field.value ?? field.default_value;
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [field.value, field.description.default_value]); }, [field.value, field.default_value]);
let inpElement = <></>; let inpElement = <></>;
...@@ -58,7 +54,7 @@ export function OMFieldComponent({ ...@@ -58,7 +54,7 @@ export function OMFieldComponent({
className="form-select" className="form-select"
value={val ?? "unchosen"} value={val ?? "unchosen"}
onChange={(e) => { onChange={(e) => {
patch.current[field.description.id] = e.target.value; patch.current[field.id] = e.target.value;
patchNotify(); patchNotify();
setVal(e.target.value); setVal(e.target.value);
}} }}
...@@ -77,13 +73,13 @@ export function OMFieldComponent({ ...@@ -77,13 +73,13 @@ export function OMFieldComponent({
}; };
let resetElement = <></>; let resetElement = <></>;
if (field.description.may_be_null === true) { if (field.may_be_null === true) {
resetElement = ( resetElement = (
<button <button
type="button" type="button"
className="ms-2 btn btn-outline-danger" className="ms-2 btn btn-outline-danger"
onClick={() => { onClick={() => {
patch.current[field.description.id] = null; patch.current[field.id] = null;
setVal(undefined); setVal(undefined);
patchNotify(); patchNotify();
}} }}
...@@ -94,17 +90,17 @@ export function OMFieldComponent({ ...@@ -94,17 +90,17 @@ export function OMFieldComponent({
); );
} }
switch (field.description.type) { switch (field.type) {
case "lecture_id": case "lecture_id":
case "course_id_string": case "course_id_string":
case "semester_string": case "semester_string":
case "string": case "string":
const onChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => { const onChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
patch.current[field.description.id] = e.target.value; patch.current[field.id] = e.target.value;
patchNotify(); patchNotify();
setVal(e.target.value); setVal(e.target.value);
}; };
if (field.description.max_length && field.description.max_length < 300) if (field.max_length && field.max_length < 300)
inpElement = ( inpElement = (
<input <input
className="flex-fill" className="flex-fill"
...@@ -136,7 +132,7 @@ export function OMFieldComponent({ ...@@ -136,7 +132,7 @@ export function OMFieldComponent({
type="checkbox" type="checkbox"
checked={val ?? false} checked={val ?? false}
onChange={(e) => { onChange={(e) => {
patch.current[field.description.id] = e.target.checked; patch.current[field.id] = e.target.checked;
patchNotify(); patchNotify();
setVal(e.target.checked); setVal(e.target.checked);
}} }}
...@@ -151,7 +147,7 @@ export function OMFieldComponent({ ...@@ -151,7 +147,7 @@ export function OMFieldComponent({
type="number" type="number"
value={val ?? 0} value={val ?? 0}
onChange={(e) => { onChange={(e) => {
patch.current[field.description.id] = parseInt(e.target.value); patch.current[field.id] = parseInt(e.target.value);
patchNotify(); patchNotify();
setVal(e.target.value); setVal(e.target.value);
}} }}
...@@ -168,8 +164,8 @@ export function OMFieldComponent({ ...@@ -168,8 +164,8 @@ export function OMFieldComponent({
const date = new Date(e.target.value); const date = new Date(e.target.value);
// YYYY-MM-ddTHH:mm:ss, surely this will never stop working in the future // YYYY-MM-ddTHH:mm:ss, surely this will never stop working in the future
// TODO: fix // TODO: fix
patch.current[field.description.id] = date.toISOString().slice(0, 19); patch.current[field.id] = date.toISOString().slice(0, 19);
console.log(patch.current[field.description.id]); console.log(patch.current[field.id]);
patchNotify(); patchNotify();
setVal(e.target.value); setVal(e.target.value);
}} }}
...@@ -196,12 +192,10 @@ export function OMFieldComponent({ ...@@ -196,12 +192,10 @@ export function OMFieldComponent({
break; break;
default: default:
if (hideLabel) if (hideLabel)
throw new Error( throw new Error(`TODO: Field type not implemented for hideLabel: ${field.type}`);
`TODO: Field type not implemented for hideLabel: ${field.description.type}`,
);
return ( return (
<tr {...props}> <tr {...props}>
<td>{!hideLabel && `${field.description.id}(${field.description.type}): `}</td> <td>{!hideLabel && `${field.id}(${field.type}): `}</td>
<td> <td>
<small className="text-info">{JSON.stringify(field.value)}</small> <small className="text-info">{JSON.stringify(field.value)}</small>
</td> </td>
...@@ -231,7 +225,7 @@ export function OMFieldComponent({ ...@@ -231,7 +225,7 @@ export function OMFieldComponent({
style={{ color: "orange", width: "4px" }} style={{ color: "orange", width: "4px" }}
/> />
{!hideLabel && ( {!hideLabel && (
<div className="me-2">{`${language.getObjectField(object_type, field.description.id)}: `}</div> <div className="me-2">{`${language.getObjectField(object_type, field.id)}: `}</div>
)} )}
</div> </div>
</td> </td>
...@@ -278,7 +272,7 @@ export function StandaloneOMFieldComponent({ ...@@ -278,7 +272,7 @@ export function StandaloneOMFieldComponent({
const reloadFunc = useReloadBoundary(); const reloadFunc = useReloadBoundary();
const patch = useRef<SimpleOMPatch>({}); const patch = useRef<SimpleOMPatch>({});
if (field.description.type === "boolean" && debounce_ms === undefined) debounce_ms = 0; if (field.type === "boolean" && debounce_ms === undefined) debounce_ms = 0;
const { deferred } = useDebounce(() => { const { deferred } = useDebounce(() => {
if (Object.keys(patch.current).length === 0) { if (Object.keys(patch.current).length === 0) {
...@@ -339,7 +333,7 @@ export function StandaloneOMStringComponent({ ...@@ -339,7 +333,7 @@ export function StandaloneOMStringComponent({
}); });
}, [api, isEditing, object_id, object_type, forceReload]); }, [api, isEditing, object_id, object_type, forceReload]);
const confField = conf.fields?.find((field) => field.description.id === field_id); const confField = conf.fields?.find((field) => field.id === field_id);
const { deferred, now } = useDebounce( const { deferred, now } = useDebounce(
() => { () => {
...@@ -357,7 +351,7 @@ export function StandaloneOMStringComponent({ ...@@ -357,7 +351,7 @@ export function StandaloneOMStringComponent({
return false; return false;
}); });
}, },
confField?.description.type == "boolean" ? 0 : 800, confField?.type == "boolean" ? 0 : 800,
); );
const disableEditing = () => { const disableEditing = () => {
...@@ -432,7 +426,8 @@ function PermissionAdder({ parent_type, parent_id }: { parent_type: string; pare ...@@ -432,7 +426,8 @@ function PermissionAdder({ parent_type, parent_id }: { parent_type: string; pare
values: { values: {
username: form.username?.value, username: form.username?.value,
password: form.password?.value, password: form.password?.value,
moodle_course_id: parseInt(form.moodle_course_id?.value), moodle_course_id:
chosenType === "moodle" ? parseInt(form.moodle_course_id?.value) : undefined,
}, },
}).then(reloadFunc); }).then(reloadFunc);
}; };
...@@ -521,9 +516,7 @@ export function OMEdit({ ...@@ -521,9 +516,7 @@ export function OMEdit({
}, [api, object_type, object_id, showModal, refresh]); }, [api, object_type, object_id, showModal, refresh]);
const hasAnyChanges = conf.fields?.some( const hasAnyChanges = conf.fields?.some(
(field) => (field) => patch.current[field.id] !== undefined && patch.current[field.id] !== field.value,
patch.current[field.description.id] !== undefined &&
patch.current[field.description.id] !== field.value,
); );
const saveChanges = () => { const saveChanges = () => {
...@@ -581,7 +574,7 @@ export function OMEdit({ ...@@ -581,7 +574,7 @@ export function OMEdit({
{conf.fields?.map((field) => ( {conf.fields?.map((field) => (
<OMFieldComponent <OMFieldComponent
object_type={object_type} object_type={object_type}
key={field.description.id} key={field.id}
field={field} field={field}
patch={patch} patch={patch}
patchNotify={() => setPatchNotify(patchNotify + 1)} patchNotify={() => setPatchNotify(patchNotify + 1)}
...@@ -695,21 +688,18 @@ export function OMCreate({ ...@@ -695,21 +688,18 @@ export function OMCreate({
function createObject() { function createObject() {
let values = { ...corePatch.current, ...variantPatch.current }; let values = { ...corePatch.current, ...variantPatch.current };
for (const obj of confData?.fields ?? []) { for (const obj of confData?.fields ?? []) {
if (values[obj.description.id] === undefined) { if (values[obj.id] === undefined) {
if (obj.description.default_value !== undefined) { if (obj.default_value !== undefined) {
values[obj.description.id] = obj.description.default_value; values[obj.id] = obj.default_value;
} else if (obj.description.may_be_null) { } else if (obj.may_be_null) {
values[obj.description.id] = null; values[obj.id] = null;
} else if ( } else if (
(obj.description.type === "string" || (obj.type === "string" || obj.type === "semester_string") &&
obj.description.type === "semester_string") && (obj.min_length === undefined || obj.min_length <= 0)
(obj.description.min_length === undefined || obj.description.min_length <= 0)
) { ) {
values[obj.description.id] = ""; values[obj.id] = "";
} else { } else {
alert( alert(`Field "${language.getObjectField(object_type, obj.id)}" has no value`);
`Field "${language.getObjectField(object_type, obj.description.id)}" has no value`,
);
return; return;
} }
} }
...@@ -752,16 +742,14 @@ export function OMCreate({ ...@@ -752,16 +742,14 @@ export function OMCreate({
const hasAnyChanges = const hasAnyChanges =
confData?.fields?.some( confData?.fields?.some(
(field) => (field) =>
corePatch.current[field.description.id] !== undefined && corePatch.current[field.id] !== undefined &&
corePatch.current[field.description.id] !== corePatch.current[field.id] !== (field.value ?? field.default_value),
(field.value ?? field.description.default_value),
) || ) ||
(chosenVariant && (chosenVariant &&
confData?.variant_fields?.[chosenVariant]?.some( confData?.variant_fields?.[chosenVariant]?.some(
(field) => (field) =>
variantPatch.current[field.description.id] !== undefined && variantPatch.current[field.id] !== undefined &&
variantPatch.current[field.description.id] !== variantPatch.current[field.id] !== (field.value ?? field.default_value),
(field.value ?? field.description.default_value),
)); ));
if (!hasAnyChanges || confirm("Do you want to discard your changes?")) { if (!hasAnyChanges || confirm("Do you want to discard your changes?")) {
...@@ -771,10 +759,8 @@ export function OMCreate({ ...@@ -771,10 +759,8 @@ export function OMCreate({
setChosenVariant(undefined); setChosenVariant(undefined);
} }
}; };
const pflichtFelder = const pflichtFelder = confData?.fields?.filter((field) => field.may_be_null !== true) ?? [];
confData?.fields?.filter((field) => field.description.may_be_null !== true) ?? []; const optionaleFelder = confData?.fields?.filter((field) => field.may_be_null === true) ?? [];
const optionaleFelder =
confData?.fields?.filter((field) => field.description.may_be_null === true) ?? [];
return ( return (
<> <>
...@@ -801,7 +787,7 @@ export function OMCreate({ ...@@ -801,7 +787,7 @@ export function OMCreate({
{pflichtFelder.map((field) => ( {pflichtFelder.map((field) => (
<OMFieldComponent <OMFieldComponent
object_type={object_type} object_type={object_type}
key={field.description.id} key={field.id}
field={field} field={field}
patch={corePatch} patch={corePatch}
patchNotify={() => {}} patchNotify={() => {}}
...@@ -825,7 +811,7 @@ export function OMCreate({ ...@@ -825,7 +811,7 @@ export function OMCreate({
{optionaleFelder.map((field) => ( {optionaleFelder.map((field) => (
<OMFieldComponent <OMFieldComponent
object_type={object_type} object_type={object_type}
key={field.description.id} key={field.id}
field={field} field={field}
patch={corePatch} patch={corePatch}
patchNotify={() => {}} patchNotify={() => {}}
...@@ -858,7 +844,7 @@ export function OMCreate({ ...@@ -858,7 +844,7 @@ export function OMCreate({
confData?.variant_fields![chosenVariant].map((field) => ( confData?.variant_fields![chosenVariant].map((field) => (
<OMFieldComponent <OMFieldComponent
object_type={object_type} object_type={object_type}
key={field.description.id} key={field.id}
field={field} field={field}
patch={variantPatch} patch={variantPatch}
patchNotify={() => {}} patchNotify={() => {}}
......
...@@ -469,10 +469,9 @@ function Chapters({ chapters, seekTo }: { chapters?: chapter[]; seekTo: (time: n ...@@ -469,10 +469,9 @@ function Chapters({ chapters, seekTo }: { chapters?: chapter[]; seekTo: (time: n
object_type="chapter" object_type="chapter"
object_id={c.id!} object_id={c.id!}
field={{ field={{
description: {
id: "is_visible", id: "is_visible",
type: "boolean", type: "boolean",
},
value: c.is_visible, value: c.is_visible,
}} }}
className="me-2" className="me-2"
...@@ -533,10 +532,9 @@ export function DownloadSources({ ...@@ -533,10 +532,9 @@ export function DownloadSources({
if (v.is_visible === false) bgColor = "bg-danger-subtle"; if (v.is_visible === false) bgColor = "bg-danger-subtle";
let isVisibleField: field_entry = { let isVisibleField: field_entry = {
description: {
id: "is_visible", id: "is_visible",
type: "boolean", type: "boolean",
},
value: v.is_visible, value: v.is_visible,
}; };
......
...@@ -90,10 +90,9 @@ function FeatureCard({ data, all_featured }: { data: featured; all_featured: fea ...@@ -90,10 +90,9 @@ function FeatureCard({ data, all_featured }: { data: featured; all_featured: fea
object_type="featured" object_type="featured"
object_id={data.id!} object_id={data.id!}
field={{ field={{
description: {
id: "is_visible", id: "is_visible",
type: "boolean", type: "boolean",
},
value: data.is_visible, value: data.is_visible,
}} }}
/> />
......
...@@ -11,6 +11,7 @@ function ChangelogImpl() { ...@@ -11,6 +11,7 @@ function ChangelogImpl() {
const page = parseInt(params.get("page") ?? "0"); const page = parseInt(params.get("page") ?? "0");
const api = useBackendContext(); const api = useBackendContext();
const [changelog, setChangeLog] = useState<GetChangelogResponse>(); const [changelog, setChangeLog] = useState<GetChangelogResponse>();
const [errorMessage, setErrorMessage] = useState<string>();
const goToPage = (page: number) => { const goToPage = (page: number) => {
const newParams = new URLSearchParams(params.toString()); const newParams = new URLSearchParams(params.toString());
...@@ -25,11 +26,16 @@ function ChangelogImpl() { ...@@ -25,11 +26,16 @@ function ChangelogImpl() {
return; return;
} }
api.getChangelog(50, page).then((changelog) => { api.getChangelog(100, page)
.then((changelog) => {
setErrorMessage(undefined);
setChangeLog(changelog); setChangeLog(changelog);
if (page >= changelog.page_count) { if (page >= changelog.page_count) {
goToPage(changelog.page_count - 1); goToPage(changelog.page_count - 1);
} }
})
.catch((e) => {
setErrorMessage(e.toString());
}); });
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [page, api]); }, [page, api]);
...@@ -63,7 +69,6 @@ function ChangelogImpl() { ...@@ -63,7 +69,6 @@ function ChangelogImpl() {
Hier werden alle Änderungen an Kursen/Veranstaltungen/Videos etc. geloggt und Hier werden alle Änderungen an Kursen/Veranstaltungen/Videos etc. geloggt und
können Rückgängig gemacht werden. können Rückgängig gemacht werden.
</p> </p>
<ul className="pagination"> <ul className="pagination">
<li className="page-item"> <li className="page-item">
<button <button
...@@ -87,6 +92,13 @@ function ChangelogImpl() { ...@@ -87,6 +92,13 @@ function ChangelogImpl() {
</button> </button>
</li> </li>
</ul> </ul>
{changelog && `Seitenanzahl: ${changelog?.page_count}`}
{errorMessage ? (
<div className="alert alert-danger" role="alert">
Changelog konnte nicht geladen werden:
{errorMessage}
</div>
) : (
<div className="table-responsive"> <div className="table-responsive">
<table className="table table-hover"> <table className="table table-hover">
<thead> <thead>
...@@ -103,7 +115,7 @@ function ChangelogImpl() { ...@@ -103,7 +115,7 @@ function ChangelogImpl() {
{(changelog?.page ?? []).map((i) => { {(changelog?.page ?? []).map((i) => {
let pfad = ""; let pfad = "";
if (i.type === "modification") { if (i.type === "modification") {
pfad = `${i.object_type}.${i.object_id}.${i.field_id}(${i.field_type})`; pfad = `${i.object_type}.${i.object_id}.${i.field_description?.id}(${i.field_description?.type})`;
} else if (i.type === "unknown") { } else if (i.type === "unknown") {
pfad = `${i.unknown_type}.${i.unknown_field}`; pfad = `${i.unknown_type}.${i.unknown_field}`;
} else { } else {
...@@ -111,11 +123,15 @@ function ChangelogImpl() { ...@@ -111,11 +123,15 @@ function ChangelogImpl() {
} }
const valToStr = (val: any) => { const valToStr = (val: any) => {
if (val === null) { if (val === null) {
return <span className="fst-italic text-muted">null</span>; return (
<span className="fst-italic text-muted">null</span>
);
} }
if (val === undefined) { if (val === undefined) {
return ( return (
<span className="fst-italic text-muted">undefined</span> <span className="fst-italic text-muted">
undefined
</span>
); );
} }
return JSON.stringify(val); return JSON.stringify(val);
...@@ -128,14 +144,19 @@ function ChangelogImpl() { ...@@ -128,14 +144,19 @@ function ChangelogImpl() {
}; };
return ( return (
<tr key={i.id}> <tr key={i.id}>
<td style={{ whiteSpace: "nowrap" }}>{formattedDate}</td> <td style={{ whiteSpace: "nowrap" }}>
{formattedDate}
</td>
<td>{i.modifying_user_id}</td> <td>{i.modifying_user_id}</td>
<td style={{ whiteSpace: "nowrap" }}>{pfad}</td> <td style={{ whiteSpace: "nowrap" }}>{pfad}</td>
<td>{valToStr(i.old_value)}</td> <td>{valToStr(i.old_value)}</td>
<td>{valToStr(i.new_value)}</td> <td>{valToStr(i.new_value)}</td>
<td> <td>
{i.is_field_modifiable && ( {i.field_description?.is_modifiable && (
<button className="btn btn-warning" onClick={undo}> <button
className="btn btn-warning"
onClick={undo}
>
undo undo
</button> </button>
)} )}
...@@ -146,6 +167,7 @@ function ChangelogImpl() { ...@@ -146,6 +167,7 @@ function ChangelogImpl() {
</tbody> </tbody>
</table> </table>
</div> </div>
)}
</div> </div>
</div> </div>
); );
......
...@@ -5,9 +5,15 @@ import { ICalEvent, parseICal } from "@/misc/Calendar"; ...@@ -5,9 +5,15 @@ import { ICalEvent, parseICal } from "@/misc/Calendar";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import Link from "next/link"; import Link from "next/link";
import { useSearchParams } from "next/navigation"; import { useSearchParams } from "next/navigation";
import { useEffect, useRef, useState } from "react"; import { MutableRefObject, useEffect, useRef, useState } from "react";
function TerminBody({ course, imported_events }: { course: course; imported_events: ICalEvent[] }) { function TerminBody({
course,
imported_events,
}: {
course: course;
imported_events: MutableRefObject<ICalEvent[]>;
}) {
const api = useBackendContext(); const api = useBackendContext();
const reloadFunc = useReloadBoundary(); const reloadFunc = useReloadBoundary();
...@@ -34,7 +40,7 @@ function TerminBody({ course, imported_events }: { course: course; imported_even ...@@ -34,7 +40,7 @@ function TerminBody({ course, imported_events }: { course: course; imported_even
); );
}; };
// find events that are new (place, duration and time unique) // find events that are new (place, duration and time unique)
const new_events = imported_events const new_events = imported_events.current
.filter((event) => { .filter((event) => {
const eventMatch = existingEvents.some((existing) => { const eventMatch = existingEvents.some((existing) => {
const match = const match =
...@@ -49,7 +55,7 @@ function TerminBody({ course, imported_events }: { course: course; imported_even ...@@ -49,7 +55,7 @@ function TerminBody({ course, imported_events }: { course: course; imported_even
// find existing events that are not in the imported events // find existing events that are not in the imported events
const not_imported_events = existingEvents const not_imported_events = existingEvents
.filter((event) => { .filter((event) => {
return !imported_events.some((imported) => { return !imported_events.current.some((imported) => {
return ( return (
imported.location === event.location && imported.location === event.location &&
imported.duration === event.duration && imported.duration === event.duration &&
...@@ -59,7 +65,7 @@ function TerminBody({ course, imported_events }: { course: course; imported_even ...@@ -59,7 +65,7 @@ function TerminBody({ course, imported_events }: { course: course; imported_even
}) })
.filter(removeDuplicates); .filter(removeDuplicates);
// number of full matches // number of full matches
const full_matches = imported_events const full_matches = imported_events.current
.filter((event) => { .filter((event) => {
return existingEvents.some((existing) => { return existingEvents.some((existing) => {
return ( return (
...@@ -77,20 +83,16 @@ function TerminBody({ course, imported_events }: { course: course; imported_even ...@@ -77,20 +83,16 @@ function TerminBody({ course, imported_events }: { course: course; imported_even
.then((res) => { .then((res) => {
const defaultValues: any = {}; const defaultValues: any = {};
res.fields?.forEach((field) => { res.fields?.forEach((field) => {
if (field.description.may_be_null) { if (field.may_be_null) {
return; return;
} }
if ( if (field.default_value !== undefined && field.default_value !== null) {
field.description.default_value !== undefined && defaultValues[field.id] = field.default_value;
field.description.default_value !== null
) {
defaultValues[field.description.id] = field.description.default_value;
} else if ( } else if (
field.description.type === "string" && field.type === "string" &&
(field.description.min_length === undefined || (field.min_length === undefined || field.min_length <= 0)
field.description.min_length <= 0)
) { ) {
defaultValues[field.description.id] = ""; defaultValues[field.id] = "";
} }
}); });
...@@ -117,20 +119,16 @@ function TerminBody({ course, imported_events }: { course: course; imported_even ...@@ -117,20 +119,16 @@ function TerminBody({ course, imported_events }: { course: course; imported_even
.then((res) => { .then((res) => {
const defaultValues: any = {}; const defaultValues: any = {};
res.fields?.forEach((field) => { res.fields?.forEach((field) => {
if (field.description.may_be_null) { if (field.may_be_null) {
return; return;
} }
if ( if (field.default_value !== undefined && field.default_value !== null) {
field.description.default_value !== undefined && defaultValues[field.id] = field.default_value;
field.description.default_value !== null
) {
defaultValues[field.description.id] = field.description.default_value;
} else if ( } else if (
field.description.type === "string" && field.type === "string" &&
(field.description.min_length === undefined || (field.min_length === undefined || field.min_length <= 0)
field.description.min_length <= 0)
) { ) {
defaultValues[field.description.id] = ""; defaultValues[field.id] = "";
} }
}); });
...@@ -188,7 +186,7 @@ function TerminBody({ course, imported_events }: { course: course; imported_even ...@@ -188,7 +186,7 @@ function TerminBody({ course, imported_events }: { course: course; imported_even
<thead> <thead>
<tr> <tr>
<th scope="col">Nur im Import</th> <th scope="col">Nur im Import</th>
<th scope="col">Durchschnitt</th> <th scope="col">In beiden</th>
<th scope="col">Nur bei uns</th> <th scope="col">Nur bei uns</th>
</tr> </tr>
</thead> </thead>
...@@ -294,9 +292,11 @@ function RWTHOnlineImportImpl() { ...@@ -294,9 +292,11 @@ function RWTHOnlineImportImpl() {
const searchParam = useSearchParams(); const searchParam = useSearchParams();
const course_id = searchParam.get("course_id"); const course_id = searchParam.get("course_id");
const [course, setCourse] = useState<course>(); const [course, setCourse] = useState<course>();
const [imported_events, setImportedEvents] = useState<ICalEvent[]>([]); const imported_events = useRef<ICalEvent[]>([]);
const imported_courses = useRef<number[]>([]);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const [reload, setReload] = useState(false); const [reload, setReload] = useState(false);
const [isImporting, setIsImporting] = useState(false);
useEffect(() => { useEffect(() => {
if (course_id === null) return; if (course_id === null) return;
...@@ -341,14 +341,45 @@ function RWTHOnlineImportImpl() { ...@@ -341,14 +341,45 @@ function RWTHOnlineImportImpl() {
return; return;
} }
} }
if (courseIdNr === undefined) {
alert("Invalid URL");
return;
}
if (imported_courses.current.includes(courseIdNr)) {
alert("Kurs bereits importiert");
return;
}
api.importRWTHOnlineCourse(courseIdNr).then((ical) => { setIsImporting(true);
api.importRWTHOnlineCourse(courseIdNr)
.then((ical) => {
imported_courses.current.push(courseIdNr!);
const parsed = parseICal(ical); const parsed = parseICal(ical);
console.log(parsed); // add id to events
setImportedEvents(parsed); parsed.forEach((event) => {
event.foreign_id = courseIdNr;
});
// append
imported_events.current = imported_events.current.concat(parsed);
setIsImporting(false);
})
.catch((e) => {
alert("Fehler beim Import: " + e);
setIsImporting(false);
}); });
}; };
const removeCourse = (courseId: number) => {
if (!confirm("Wirklich löschen?")) return;
imported_courses.current = imported_courses.current.filter((id) => id !== courseId);
imported_events.current = imported_events.current.filter(
(event) => event.foreign_id !== courseId,
);
setReload(!reload);
};
return ( return (
<ReloadBoundary reloadFunc={() => setReload(!reload)}> <ReloadBoundary reloadFunc={() => setReload(!reload)}>
<Link href={`/${course.id_string}`} className="btn btn-primary mb-2"> <Link href={`/${course.id_string}`} className="btn btn-primary mb-2">
...@@ -371,9 +402,39 @@ function RWTHOnlineImportImpl() { ...@@ -371,9 +402,39 @@ function RWTHOnlineImportImpl() {
placeholder="https://online.rwth-aachen.de/RWTHonline/pl/ui/%24ctx/wbTvw_List.lehrveranstaltung?pStpSpNr=..." placeholder="https://online.rwth-aachen.de/RWTHonline/pl/ui/%24ctx/wbTvw_List.lehrveranstaltung?pStpSpNr=..."
ref={inputRef} ref={inputRef}
/> />
<button className="btn btn-primary mt-2" type="button" onClick={onImport}> {isImporting ? (
Importieren <div className="spinner-border mt-2" role="status" />
) : (
<button
className="btn btn-primary mt-2"
type="button"
onClick={onImport}
>
Zum Import hinzufügen
</button> </button>
)}
</div>
<div>
Bereits importierte Termine: {imported_events.current.length}
<table className="table table-bordered">
<tbody>
{imported_courses.current.map((courseId, index) => (
<tr key={index}>
<td>
{courseId}
<button
type="button"
className="ms-2 btn btn-danger"
onClick={() => removeCourse(courseId)}
>
<i className="bi bi-trash-fill"></i>
</button>
</td>
</tr>
))}
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment