[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 { RegisterSidebar } from "./RegisterSidebar";
import { DownloadSimpleIcon } from "../../icons/DownloadSimpleIcon";
import { backend, wait } from "../../utils/functions";
import { backend } from "../../utils/functions";
import { JsonResult } from "../../types/JsonResult";
import { LoadingIcon } from "../../icons/LoadingIcon";
import { RegisterElement, generateCert } from "./RegisterElement";
import { useCourses } from "../CourseContext";
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}) {
const [registers, setRegisters] = createSignal<Array<Register>>([]);
const [showHidden, setShowHidden] = createSignal(false);
const [sidebarRegister, setSidebarRegister] = createSignal<Register | null>(null);
const [loading, setLoading] = createSignal(false);
const [error, setError] = createSignal("");
const courses = useCourses();
const customLabels = useCustomLabel();
const loadRegisters = async() => {
const loadRegisters = () => {
setLoading(true);
setError("");
const person = props.person!;
setRegisters([]);
if (import.meta.env.DEV) await wait(1500);
backend.get<JsonResult<Array<Register>>>(`/api/register/${person.person_dni}`)
.then((response) => {
if (response.status === 200) {
@ -56,25 +56,20 @@ export function Registers(props: {person: Person | null, count: number}) {
loadRegisters();
});
// All registers sorted by year, and an indicator of whether there's register older
// than last year
const registerSortedList = createMemo<[Array<Array<Register>>, boolean]>(() => {
const registersReady = createMemo(() => !loading() && props.person !== null);
// Returns a map with years & their registers
const registerSortedList = createMemo<{[year: number]: Array<Register>}>(() => {
if (loading() || props.person === null) {
return {};
}
const allRegisters = registers();
const registerByYear: {[y: string]: Array<Register>} = {};
let olderThanLastYear = false;
allRegisters.forEach((register) => {
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) {
registerByYear[year] = [];
@ -83,23 +78,28 @@ export function Registers(props: {person: Person | null, count: number}) {
registerByYear[year].push(register);
});
// Create a new array with each year's registers
// Sort the object keys (years) in descending order
const sortedYears = Object.keys(registerByYear).sort((y1, y2) => (y1 < y2 ? 1 : -1));
// Make sure that there's a key for the current year
const currentYear = new Date().getFullYear();
if (registerByYear[currentYear] === undefined) {
registerByYear[currentYear] = [];
}
// Create the final array
const result = sortedYears.map((year) => registerByYear[year].sort((r1, r2) => (r1.register_display_date < r2.register_display_date ? 1 : -1)));
return [result, olderThanLastYear];
// Sort the registers by date
Object.entries(registerByYear).forEach(([, registers]) => {
registers.sort((r1, r2) => (r1.register_display_date < r2.register_display_date ? 1 : -1));
});
const inputCheck = () => setShowHidden((x) => !x);
return registerByYear;
});
const todayRegisters = createMemo(() => {
const today = new Date().toISOString()
.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 (
<div class="m-4 p-4 rounded-md relative">
<h3 class="text-xl font-medium">
<div class="p-2 rounded-md relative">
<Show when={registersReady()}>
<h3 class="text-xl font-medium mb-5">
<button
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}
title="Descargar todos los cert. creados hoy"
onclick={downloadTodayCerts}
>
<DownloadSimpleIcon fill="var(--c-on-primary)" size={24} />
<span class="text-sm">
&nbsp;hoy
&nbsp;Descargar los cert. creados hoy
</span>
</button>
{props.person?.person_names}&nbsp;
{props.person?.person_paternal_surname}&nbsp;
{props.person?.person_maternal_surname}
<Show when={registerSortedList()[1]}>
&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>
·&nbsp;Certificados registrados
</h3>
</Show>
<For each={registerSortedList()[0]}>
{(year) => (
<div class="flex flex-wrap justify-start gap-2 my-6">
<For each={year}>
{(register) => (
<RegisterElement
register={register}
<For each={Object.entries(registerSortedList()).sort(([year1], [year2]) => (year1 < year2 ? 1 : -1))}>
{([year,yearRegisters]) => (
<YearRegisters
year={parseInt(year, 10)}
registers={yearRegisters}
person={props.person!}
onClick={() => setSidebarRegister(register)}
setSidebarRegister={setSidebarRegister}
/>
)}
</For>
</div>
)}
</For>
<Show when={loading()}>
<div class="text-center">
@ -183,3 +171,62 @@ export function Registers(props: {person: Person | null, count: number}) {
</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>
);
}