[FE] Include custom labels in registration
This commit is contained in:
parent
b24f969cec
commit
c6b77b446d
@ -1,18 +1,18 @@
|
|||||||
import { createEffect, createSignal, For, onMount } from "solid-js";
|
import { createEffect, createSignal, For, onMount } from "solid-js";
|
||||||
import { allCourses } from "../../utils/allCourses";
|
import { customLabelsMap } from "../../utils/allCustomLabels";
|
||||||
|
|
||||||
export function CustomLabelSelect(props: {
|
export function CustomLabelSelect(props: {
|
||||||
onChange: (id: number | null) => void,
|
onChange: (value: string) => void,
|
||||||
count: number
|
count: number
|
||||||
}) {
|
}) {
|
||||||
const [filter, setFilter] = createSignal("");
|
const [filter, setFilter] = createSignal("");
|
||||||
const [selected, setSelected] = createSignal<number | null>(null);
|
const [selected, setSelected] = createSignal<boolean>(false);
|
||||||
const [inputValue, setInputValue] = createSignal("");
|
const [inputValue, setInputValue] = createSignal("");
|
||||||
|
|
||||||
const iHandler = (ev: KeyboardEvent & {currentTarget: HTMLInputElement, target: Element}) => {
|
const iHandler = (ev: KeyboardEvent & {currentTarget: HTMLInputElement, target: Element}) => {
|
||||||
const inputEl = ev.target as HTMLInputElement;
|
const inputEl = ev.target as HTMLInputElement;
|
||||||
// Clear current selection
|
// Clear current selection
|
||||||
setSelected(null);
|
setSelected(false);
|
||||||
|
|
||||||
let filter: string = inputEl.value.toLowerCase();
|
let filter: string = inputEl.value.toLowerCase();
|
||||||
filter = filter.replace("á", "a");
|
filter = filter.replace("á", "a");
|
||||||
@ -24,7 +24,7 @@ export function CustomLabelSelect(props: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
props.onChange(selected());
|
props.onChange(inputValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
@ -33,7 +33,7 @@ export function CustomLabelSelect(props: {
|
|||||||
const _count = props.count;
|
const _count = props.count;
|
||||||
|
|
||||||
setFilter("");
|
setFilter("");
|
||||||
setSelected(null);
|
setSelected(false);
|
||||||
setInputValue("");
|
setInputValue("");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,32 +51,31 @@ export function CustomLabelSelect(props: {
|
|||||||
const filteredOptions = () => {
|
const filteredOptions = () => {
|
||||||
const filterText = filter();
|
const filterText = filter();
|
||||||
|
|
||||||
return allCourses().filter((course) => {
|
return Object.entries(customLabelsMap()).filter(([, label]) => {
|
||||||
let courseText = course.course_name.toLowerCase();
|
let courseText = label.custom_label_value.toLowerCase();
|
||||||
courseText = courseText.replace("á", "a");
|
courseText = courseText.replace("á", "a");
|
||||||
courseText = courseText.replace("é", "e");
|
courseText = courseText.replace("é", "e");
|
||||||
courseText = courseText.replace("í", "i");
|
courseText = courseText.replace("í", "i");
|
||||||
courseText = courseText.replace("ó", "o");
|
courseText = courseText.replace("ó", "o");
|
||||||
courseText = courseText.replace("ú", "u");
|
courseText = courseText.replace("ú", "u");
|
||||||
|
|
||||||
return selected() === null && courseText.indexOf(filterText) !== -1;
|
return !selected() && courseText.indexOf(filterText) !== -1;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div class="relative">
|
||||||
<input
|
<input
|
||||||
id="create-subject"
|
id="create-subject"
|
||||||
ref={inputElement}
|
ref={inputElement}
|
||||||
class={`bg-c-background text-c-on-background
|
class={`bg-c-background text-c-on-background border-c-outline
|
||||||
${selected() !== null ? "border-c-green" : "border-c-outline"}
|
|
||||||
border-2 rounded-tl rounded-tr px-2 py-1
|
border-2 rounded-tl rounded-tr px-2 py-1
|
||||||
w-full
|
w-full
|
||||||
invalid:border-c-error invalid:text-c-error
|
invalid:border-c-error invalid:text-c-error
|
||||||
focus:border-c-primary outline-none
|
focus:border-c-primary outline-none
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed`}
|
disabled:opacity-50 disabled:cursor-not-allowed`}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Curso"
|
placeholder="Denominación"
|
||||||
onkeyup={iHandler}
|
onkeyup={iHandler}
|
||||||
value={inputValue()}
|
value={inputValue()}
|
||||||
onchange={(ev) => setInputValue(ev.target.value)}
|
onchange={(ev) => setInputValue(ev.target.value)}
|
||||||
@ -86,25 +85,26 @@ export function CustomLabelSelect(props: {
|
|||||||
<br />
|
<br />
|
||||||
<div
|
<div
|
||||||
class="border-c-outline border-l-2 border-b-2 border-r-2
|
class="border-c-outline border-l-2 border-b-2 border-r-2
|
||||||
rounded-bl rounded-br overflow-y-scroll h-[10rem]"
|
rounded-bl rounded-br overflow-y-scroll h-[4rem]
|
||||||
|
absolute w-full"
|
||||||
>
|
>
|
||||||
<For each={filteredOptions()}>
|
<For each={filteredOptions()}>
|
||||||
{(s) => (
|
{([, label]) => (
|
||||||
<button
|
<button
|
||||||
class="w-full text-left py-1 px-2
|
class="w-full text-left py-1 px-2
|
||||||
hover:bg-c-primary-container hover:text-c-on-primary-container"
|
hover:bg-c-primary-container hover:text-c-on-primary-container"
|
||||||
onclick={(ev) => {
|
onclick={(ev) => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
|
|
||||||
setSelected(s.course_id);
|
setSelected(true);
|
||||||
setInputValue(s.course_name);
|
setInputValue(label.custom_label_value);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{s.course_name}
|
{label.custom_label_value}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
import { JSX, createSignal } from "solid-js";
|
import { JSX, Show, createSignal } from "solid-js";
|
||||||
import { SearchableSelect } from "./SearchableSelect";
|
import { SearchableSelect } from "./SearchableSelect";
|
||||||
|
import { CustomLabelSelect } from "./CustomLabelSelect";
|
||||||
|
import { courseMap } from "../../utils/allCourses";
|
||||||
|
import { RegistrationPreview } from ".";
|
||||||
|
|
||||||
type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & {
|
type HTMLEventFn = JSX.EventHandlerUnion<HTMLFormElement, Event & {
|
||||||
submitter: HTMLElement;
|
submitter: HTMLElement;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export function ManualRegistration(props: {personId: number | null, onAdd: (v: [number, string]) => void}) {
|
export function ManualRegistration(props: {
|
||||||
|
personId: number | null,
|
||||||
|
onAdd: (v: RegistrationPreview) => void
|
||||||
|
}) {
|
||||||
// Used to update SearchableSelect.tsx manually
|
// Used to update SearchableSelect.tsx manually
|
||||||
const [count, setCount] = createSignal(0);
|
const [count, setCount] = createSignal(0);
|
||||||
const [error, setError] = createSignal("");
|
const [error, setError] = createSignal("");
|
||||||
|
|
||||||
const [selectedCourseId, seSelectedCourseId] = createSignal<number | null>(null);
|
const [selectedCourseId, seSelectedCourseId] = createSignal<number | null>(null);
|
||||||
|
const [customLabel, setCustomLabel] = createSignal("");
|
||||||
|
|
||||||
let datePicker: HTMLInputElement | undefined;
|
let datePicker: HTMLInputElement | undefined;
|
||||||
|
|
||||||
@ -38,11 +45,27 @@ export function ManualRegistration(props: {personId: number | null, onAdd: (v: [
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
props.onAdd([subject, date]);
|
const data: RegistrationPreview = {
|
||||||
|
courseId: subject,
|
||||||
|
date,
|
||||||
|
customLabel: customLabel(),
|
||||||
|
};
|
||||||
|
|
||||||
|
props.onAdd(data);
|
||||||
// This is used to update & refresh the <SearchableSelect> component
|
// This is used to update & refresh the <SearchableSelect> component
|
||||||
setCount((x) => x + 1);
|
setCount((x) => x + 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const courseHasCustomLabel = () => {
|
||||||
|
const courseId = selectedCourseId();
|
||||||
|
if (courseId === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const course = courseMap()[courseId];
|
||||||
|
return course?.course_has_custom_label === true;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form onsubmit={register}>
|
<form onsubmit={register}>
|
||||||
@ -63,18 +86,17 @@ export function ManualRegistration(props: {personId: number | null, onAdd: (v: [
|
|||||||
/>
|
/>
|
||||||
<label for="create-date" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">Fecha</label>
|
<label for="create-date" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">Fecha</label>
|
||||||
</div>
|
</div>
|
||||||
|
<Show when={courseHasCustomLabel()}>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input
|
<CustomLabelSelect
|
||||||
id="create-date"
|
onChange={setCustomLabel}
|
||||||
class="bg-c-surface text-c-on-surface border border-c-outline rounded-lg p-2 font-mono w-full
|
count={count()}
|
||||||
disabled:opacity-50 disabled:cursor-not-allowed"
|
|
||||||
type="text"
|
|
||||||
disabled
|
|
||||||
/>
|
/>
|
||||||
<label for="create-date" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">
|
<label for="create-date" class="absolute -top-2 left-2 text-xs bg-c-surface px-1">
|
||||||
Denominación
|
Denominación
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -2,6 +2,7 @@ import { Accessor, For, createSignal } from "solid-js";
|
|||||||
import { Chip } from "../../components/Chip";
|
import { Chip } from "../../components/Chip";
|
||||||
import { getCourseMemo } from "../../utils/allCourses";
|
import { getCourseMemo } from "../../utils/allCourses";
|
||||||
import { Course } from "../../types/Course";
|
import { Course } from "../../types/Course";
|
||||||
|
import { RegistrationPreview } from ".";
|
||||||
|
|
||||||
type PresetName = "None" | "2 Matpel" | "3 Matpel" | "4 Escolta" | "MD, 2 Matpel" | "3 4x4" | "2 4x4";
|
type PresetName = "None" | "2 Matpel" | "3 Matpel" | "4 Escolta" | "MD, 2 Matpel" | "3 4x4" | "2 4x4";
|
||||||
|
|
||||||
@ -39,7 +40,10 @@ function genPresets(): {[k: string]: Array<Accessor<Course | null>>} {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RegisterPresets(props: {disableCreation: boolean, onAdd: (v: [number, string]) => void}) {
|
export function RegisterPresets(props: {
|
||||||
|
disableCreation: boolean,
|
||||||
|
onAdd: (v: RegistrationPreview) => void
|
||||||
|
}) {
|
||||||
let datePicker: HTMLInputElement | undefined;
|
let datePicker: HTMLInputElement | undefined;
|
||||||
const [selectedPreset, setSelectedPreset] = createSignal<PresetName>("None");
|
const [selectedPreset, setSelectedPreset] = createSignal<PresetName>("None");
|
||||||
const [error, setError] = createSignal("");
|
const [error, setError] = createSignal("");
|
||||||
@ -78,7 +82,11 @@ export function RegisterPresets(props: {disableCreation: boolean, onAdd: (v: [nu
|
|||||||
const dateYYYYMMDD = currentDate.toISOString().split("T")[0];
|
const dateYYYYMMDD = currentDate.toISOString().split("T")[0];
|
||||||
|
|
||||||
// Add
|
// Add
|
||||||
props.onAdd([courseId, dateYYYYMMDD]);
|
props.onAdd({
|
||||||
|
courseId,
|
||||||
|
date: dateYYYYMMDD,
|
||||||
|
customLabel: "",
|
||||||
|
});
|
||||||
|
|
||||||
// Substract current date for the next course
|
// Substract current date for the next course
|
||||||
currentDate.setDate(currentDate.getDate() - courseDuration);
|
currentDate.setDate(currentDate.getDate() - courseDuration);
|
||||||
|
@ -3,6 +3,7 @@ import { For } from "solid-js";
|
|||||||
import { XIcon } from "../../icons/XIcon";
|
import { XIcon } from "../../icons/XIcon";
|
||||||
import { allCourses } from "../../utils/allCourses";
|
import { allCourses } from "../../utils/allCourses";
|
||||||
import { RegisterBatchCreate } from "../../types/Register";
|
import { RegisterBatchCreate } from "../../types/Register";
|
||||||
|
import { RegistrationPreview } from ".";
|
||||||
|
|
||||||
|
|
||||||
function isoDateToLocalDate(date: string): string {
|
function isoDateToLocalDate(date: string): string {
|
||||||
@ -10,9 +11,9 @@ function isoDateToLocalDate(date: string): string {
|
|||||||
return `${day}/${month}`;
|
return `${day}/${month}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RegisterPreview(props: {selections: Array<[number, string]>, personId: number | null, onDelete: (v: number) => void, onRegister: () => void}) {
|
export function RegisterPreview(props: {selections: Array<RegistrationPreview>, personId: number | null, onDelete: (v: number) => void, onRegister: () => void}) {
|
||||||
const submit = async() => {
|
const submit = async() => {
|
||||||
const registers: RegisterBatchCreate = props.selections.map(([courseId, date]) => ({
|
const registers: RegisterBatchCreate = props.selections.map(({courseId, date}) => ({
|
||||||
person_id: props.personId!,
|
person_id: props.personId!,
|
||||||
course_id: courseId,
|
course_id: courseId,
|
||||||
date,
|
date,
|
||||||
@ -34,7 +35,14 @@ export function RegisterPreview(props: {selections: Array<[number, string]>, per
|
|||||||
<h2 class="p-4 font-bold text-xl">Confirmar registro</h2>
|
<h2 class="p-4 font-bold text-xl">Confirmar registro</h2>
|
||||||
<div class="bg-c-surface p-4">
|
<div class="bg-c-surface p-4">
|
||||||
<For each={props.selections}>
|
<For each={props.selections}>
|
||||||
{([courseId, date]) => <Register courseId={courseId} date={date} onDelete={props.onDelete} />}
|
{({courseId, date, customLabel}) => (
|
||||||
|
<Register
|
||||||
|
courseId={courseId}
|
||||||
|
date={date}
|
||||||
|
onDelete={props.onDelete}
|
||||||
|
customLabel={customLabel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@ -51,7 +59,7 @@ export function RegisterPreview(props: {selections: Array<[number, string]>, per
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function Register(props: {courseId: number, date: string, onDelete: (v: number) => void}) {
|
function Register(props: {courseId: number, date: string, customLabel: string, onDelete: (v: number) => void}) {
|
||||||
const courseName = () => {
|
const courseName = () => {
|
||||||
const courses = allCourses();
|
const courses = allCourses();
|
||||||
return courses.find((course) => course.course_id === props.courseId)?.course_name ?? `Curso invalido! (${props.courseId})`;
|
return courses.find((course) => course.course_id === props.courseId)?.course_name ?? `Curso invalido! (${props.courseId})`;
|
||||||
|
@ -5,15 +5,20 @@ import { RegisterPresets } from "./RegisterPresets";
|
|||||||
import { ManualRegistration } from "./ManualRegistration";
|
import { ManualRegistration } from "./ManualRegistration";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type TabType = "Presets" | "Manual";
|
type TabType = "Presets" | "Manual";
|
||||||
|
|
||||||
|
export type RegistrationPreview = {
|
||||||
|
courseId: number,
|
||||||
|
date: string,
|
||||||
|
customLabel: string,
|
||||||
|
}
|
||||||
|
|
||||||
export function NewRegister(props: {
|
export function NewRegister(props: {
|
||||||
personId: number | null,
|
personId: number | null,
|
||||||
onSuccess: () => void,
|
onSuccess: () => void,
|
||||||
}) {
|
}) {
|
||||||
const [active, setActive] = createSignal<TabType>("Presets");
|
const [active, setActive] = createSignal<TabType>("Presets");
|
||||||
const [selections, setSelections] = createSignal<Array<[number, string]>>([]);
|
const [selections, setSelections] = createSignal<Array<RegistrationPreview>>([]);
|
||||||
|
|
||||||
const onRegister = () => {
|
const onRegister = () => {
|
||||||
setSelections([]);
|
setSelections([]);
|
||||||
@ -46,7 +51,7 @@ export function NewRegister(props: {
|
|||||||
<RegisterPreview
|
<RegisterPreview
|
||||||
selections={selections()}
|
selections={selections()}
|
||||||
personId={props.personId}
|
personId={props.personId}
|
||||||
onDelete={(deleteId) => setSelections((s) => [...s.filter(([id]) => id !== deleteId)])}
|
onDelete={(deleteId) => setSelections((s) => [...s.filter((obj) => obj.courseId !== deleteId)])}
|
||||||
onRegister={onRegister}
|
onRegister={onRegister}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -7,7 +7,7 @@ export const [customLabelsMap, setCustomLabelsMap] = createSignal<{[k: number]:
|
|||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
// Get all labels from the API
|
// Get all labels from the API
|
||||||
fetch(`${import.meta.env.VITE_BACKEND_URL}/api/labels`)
|
fetch(`${import.meta.env.VITE_BACKEND_URL}/api/label`)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data: Array<CustomLabel>) => {
|
.then((data: Array<CustomLabel>) => {
|
||||||
const map: CustomLabelsMap = {};
|
const map: CustomLabelsMap = {};
|
||||||
|
Loading…
Reference in New Issue
Block a user