[FE][Certs] Fixes #22: Improve styles for the register list

master
Araozu 2023-12-21 15:44:21 -05:00
parent 791f946538
commit 12093b88c3
5 changed files with 160 additions and 70 deletions

View File

@ -3,31 +3,31 @@ import { Person } from "../../types/Person";
import { Register } from "../../types/Register"; import { Register } from "../../types/Register";
import { RegisterSidebar } from "./RegisterSidebar"; import { RegisterSidebar } from "./RegisterSidebar";
import { DownloadSimpleIcon } from "../../icons/DownloadSimpleIcon"; import { DownloadSimpleIcon } from "../../icons/DownloadSimpleIcon";
import { backend, wait } from "../../utils/functions"; import { backend } from "../../utils/functions";
import { JsonResult } from "../../types/JsonResult"; import { JsonResult } from "../../types/JsonResult";
import { LoadingIcon } from "../../icons/LoadingIcon"; import { LoadingIcon } from "../../icons/LoadingIcon";
import { RegisterElement, generateCert } from "./RegisterElement"; import { RegisterElement, generateCert } from "./RegisterElement";
import { useCourses } from "../CourseContext"; import { useCourses } from "../CourseContext";
import { useCustomLabel } from "../CustomLabelContext"; import { useCustomLabel } from "../CustomLabelContext";
import { TrayIcon } from "../../icons/TrayIcon";
import { CaretDownIcon } from "../../icons/CaretDownIcon";
import { CaretUpIcon } from "../../icons/CaretUpIcon";
export function Registers(props: {person: Person | null, count: number}) { export function Registers(props: {person: Person | null, count: number}) {
const [registers, setRegisters] = createSignal<Array<Register>>([]); const [registers, setRegisters] = createSignal<Array<Register>>([]);
const [showHidden, setShowHidden] = createSignal(false);
const [sidebarRegister, setSidebarRegister] = createSignal<Register | null>(null); const [sidebarRegister, setSidebarRegister] = createSignal<Register | null>(null);
const [loading, setLoading] = createSignal(false); const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal(""); const [error, setError] = createSignal("");
const courses = useCourses(); const courses = useCourses();
const customLabels = useCustomLabel(); const customLabels = useCustomLabel();
const loadRegisters = async() => { const loadRegisters = () => {
setLoading(true); setLoading(true);
setError(""); setError("");
const person = props.person!; const person = props.person!;
setRegisters([]); setRegisters([]);
if (import.meta.env.DEV) await wait(1500);
backend.get<JsonResult<Array<Register>>>(`/api/register/${person.person_dni}`) backend.get<JsonResult<Array<Register>>>(`/api/register/${person.person_dni}`)
.then((response) => { .then((response) => {
if (response.status === 200) { if (response.status === 200) {
@ -56,25 +56,20 @@ export function Registers(props: {person: Person | null, count: number}) {
loadRegisters(); loadRegisters();
}); });
// All registers sorted by year, and an indicator of whether there's register older const registersReady = createMemo(() => !loading() && props.person !== null);
// than last year
const registerSortedList = createMemo<[Array<Array<Register>>, boolean]>(() => { // Returns a map with years & their registers
const registerSortedList = createMemo<{[year: number]: Array<Register>}>(() => {
if (loading() || props.person === null) {
return {};
}
const allRegisters = registers(); const allRegisters = registers();
const registerByYear: {[y: string]: Array<Register>} = {}; const registerByYear: {[y: string]: Array<Register>} = {};
let olderThanLastYear = false;
allRegisters.forEach((register) => { allRegisters.forEach((register) => {
const year = new Date(register.register_display_date).getFullYear(); const year = new Date(register.register_display_date).getFullYear();
const currentYear = new Date().getFullYear();
// Don't show registers from before last year
if (year < (currentYear - 1)) {
olderThanLastYear = true;
if (!showHidden()) {
return;
}
}
if (registerByYear[year] === undefined) { if (registerByYear[year] === undefined) {
registerByYear[year] = []; registerByYear[year] = [];
@ -83,23 +78,28 @@ export function Registers(props: {person: Person | null, count: number}) {
registerByYear[year].push(register); registerByYear[year].push(register);
}); });
// Create a new array with each year's registers // Make sure that there's a key for the current year
// Sort the object keys (years) in descending order const currentYear = new Date().getFullYear();
const sortedYears = Object.keys(registerByYear).sort((y1, y2) => (y1 < y2 ? 1 : -1)); if (registerByYear[currentYear] === undefined) {
registerByYear[currentYear] = [];
}
// Create the final array // Sort the registers by date
const result = sortedYears.map((year) => registerByYear[year].sort((r1, r2) => (r1.register_display_date < r2.register_display_date ? 1 : -1))); Object.entries(registerByYear).forEach(([, registers]) => {
registers.sort((r1, r2) => (r1.register_display_date < r2.register_display_date ? 1 : -1));
return [result, olderThanLastYear];
}); });
const inputCheck = () => setShowHidden((x) => !x); return registerByYear;
});
const todayRegisters = createMemo(() => { const todayRegisters = createMemo(() => {
const today = new Date().toISOString() const today = new Date().toISOString()
.split("T")[0]; .split("T")[0];
return registerSortedList()[0][0]
?.filter((r) => r.register_creation_date === today) ?? []; return Object.entries(registerSortedList())
.map(([,registers]) => registers)
.flat()
.filter((r) => r.register_creation_date === today);
}); });
@ -120,48 +120,36 @@ export function Registers(props: {person: Person | null, count: number}) {
return ( return (
<div class="m-4 p-4 rounded-md relative"> <div class="p-2 rounded-md relative">
<h3 class="text-xl font-medium"> <Show when={registersReady()}>
<h3 class="text-xl font-medium mb-5">
<button <button
class={`${downButtonBg()} inline-block mr-2 rounded-full transition-colors class={`${downButtonBg()} inline-block mr-2 rounded-full transition-colors
disabled:opacity-25 disabled:cursor-not-allowed px-2`} disabled:opacity-25 disabled:cursor-not-allowed px-2 hover:underline`}
disabled={todayRegisters().length === 0} disabled={todayRegisters().length === 0}
title="Descargar todos los cert. creados hoy" title="Descargar todos los cert. creados hoy"
onclick={downloadTodayCerts} onclick={downloadTodayCerts}
> >
<DownloadSimpleIcon fill="var(--c-on-primary)" size={24} /> <DownloadSimpleIcon fill="var(--c-on-primary)" size={24} />
<span class="text-sm"> <span class="text-sm">
&nbsp;hoy &nbsp;Descargar los cert. creados hoy
</span> </span>
</button> </button>
{props.person?.person_names}&nbsp;
{props.person?.person_paternal_surname}&nbsp;
{props.person?.person_maternal_surname}
<Show when={registerSortedList()[1]}> ·&nbsp;Certificados registrados
&nbsp;&nbsp;&nbsp;|
<input id={`person_${props.person?.person_id ?? "_"}`} type="checkbox" class="ml-4 mr-2" checked oninput={inputCheck} />
<label for={`person_${props.person?.person_id ?? "_"}`} class="text-sm">
Ocultar cert. anteriores a {new Date().getFullYear() - 1}
</label>
</Show>
</h3> </h3>
</Show>
<For each={registerSortedList()[0]}> <For each={Object.entries(registerSortedList()).sort(([year1], [year2]) => (year1 < year2 ? 1 : -1))}>
{(year) => ( {([year,yearRegisters]) => (
<div class="flex flex-wrap justify-start gap-2 my-6"> <YearRegisters
<For each={year}> year={parseInt(year, 10)}
{(register) => ( registers={yearRegisters}
<RegisterElement
register={register}
person={props.person!} person={props.person!}
onClick={() => setSidebarRegister(register)} setSidebarRegister={setSidebarRegister}
/> />
)} )}
</For> </For>
</div>
)}
</For>
<Show when={loading()}> <Show when={loading()}>
<div class="text-center"> <div class="text-center">
@ -183,3 +171,62 @@ export function Registers(props: {person: Person | null, count: number}) {
</div> </div>
); );
} }
function YearRegisters(props: {
year: number,
registers: Array<Register>,
person: Person,
setSidebarRegister: (register: Register) => void,
}) {
const [collapsed, setCollapsed] = createSignal(props.year < new Date().getFullYear() - 1);
return (
<div
class={`rounded-lg mb-8 hover:shadow-md ${collapsed() ? "border border-c-outline" : ""}`}
>
<button
class="p-2 rounded-lg bg-c-surface-variant text-c-on-surface-variant font-bold
grid grid-cols-[auto_2rem] w-full text-left hover:underline"
onclick={() => setCollapsed((x) => !x)}
>
{props.year}
<Show when={collapsed()}>
<CaretDownIcon size={24} fill="var(--c-outline)" />
</Show>
<Show when={!collapsed()}>
<CaretUpIcon size={24} fill="var(--c-outline)" />
</Show>
</button>
<div
class={`${collapsed() ? "hidden" : ""} ${props.year === 2023 ? "min-h-[11rem]" : ""}`}
>
<Show when={props.registers.length > 0}>
<div
class={"flex flex-wrap justify-start gap-2 p-2 "}
>
<For each={props.registers}>
{(register) => (
<RegisterElement
register={register}
person={props.person}
onClick={() => props.setSidebarRegister(register)}
/>
)}
</For>
</div>
</Show>
<Show when={props.registers.length === 0}>
<div class="text-center">
<div class="text-center p-10">
<TrayIcon class="scale-[300%]" fill="var(--c-outline)" />
</div>
<p>No hay certificados registrados en {props.year}.</p>
</div>
</Show>
</div>
</div>
);
}

View File

@ -0,0 +1,14 @@
export function CaretDownIcon(props: {fill: string, size?: number, class?: string}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
class={`inline-block w-6 ${props.class}`}
width={props.size ?? 32}
height={props.size ?? 32}
fill={props.fill}
viewBox="0 0 256 256"
>
<path d="M213.66,101.66l-80,80a8,8,0,0,1-11.32,0l-80-80A8,8,0,0,1,53.66,90.34L128,164.69l74.34-74.35a8,8,0,0,1,11.32,11.32Z" />
</svg>
);
}

View File

@ -0,0 +1,14 @@
export function CaretUpIcon(props: {fill: string, size?: number, class?: string}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
class={`inline-block w-6 ${props.class}`}
width={props.size ?? 32}
height={props.size ?? 32}
fill={props.fill}
viewBox="0 0 256 256"
>
<path d="M213.66,165.66a8,8,0,0,1-11.32,0L128,91.31,53.66,165.66a8,8,0,0,1-11.32-11.32l80-80a8,8,0,0,1,11.32,0l80,80A8,8,0,0,1,213.66,165.66Z" />
</svg>
);
}

View File

@ -0,0 +1,15 @@
export function TrayIcon(props: {fill: string, size?: number, class?: string}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
class={`inline-block w-6 ${props.class}`}
width={props.size ?? 32}
height={props.size ?? 32}
fill={props.fill}
viewBox="0 0 256 256"
>
<path d="M208,32H48A16,16,0,0,0,32,48V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V48A16,16,0,0,0,208,32Zm0,16V152h-28.7A15.86,15.86,0,0,0,168,156.69L148.69,176H107.31L88,156.69A15.86,15.86,0,0,0,76.69,152H48V48Zm0,160H48V168H76.69L96,187.31A15.86,15.86,0,0,0,107.31,192h41.38A15.86,15.86,0,0,0,160,187.31L179.31,168H208v40Z" />
</svg>
);
}