Skip to content

Commit

Permalink
Merge branch 'main' into filter-with-one-option
Browse files Browse the repository at this point in the history
  • Loading branch information
lorenzocorallo committed Aug 22, 2024
2 parents c6d2831 + c47171c commit f14f6fb
Show file tree
Hide file tree
Showing 16 changed files with 186 additions and 69 deletions.
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
plugins: ["react-refresh", "prettier"],
rules: {
"@typescript-eslint/switch-exhaustiveness-check": "error",
"react-refresh/only-export-components": "warn",
"prettier/prettier": "warn",
},
Expand Down
32 changes: 20 additions & 12 deletions src/components/custom-ui/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,27 @@ export default function Alert({
}

function GetIcon(level: Level) {
if (level === "error") return <LuAlertCircle />;
if (level === "warning") return <LuAlertTriangle />;
if (level === "info") return <LuInfo />;
if (level === "success") return <LuCheckCircle2 />;
switch (level) {
case "error":
return <LuAlertCircle />;
case "warning":
return <LuAlertTriangle />;
case "info":
return <LuInfo />;
case "success":
return <LuCheckCircle2 />;
}
}

const getColors = (level: Level) => {
if (level === "error")
return "bg-red-300/5 border-red-600 text-red-600 dark:bg-red-100/5 dark:border-red-300 dark:text-red-300";
if (level === "warning")
return "bg-amber-300/5 border-amber-600 text-amber-600 dark:bg-amber-100/5 dark:border-amber-200 dark:text-amber-200";
if (level === "info")
return "bg-sky-300/5 border-sky-600 text-sky-600 dark:bg-sky-100/5 dark:border-sky-200 dark:text-sky-200";
if (level === "success")
return "bg-green-300/5 border-green-600 text-green-600 dark:bg-green-100/5 dark:border-green-200 dark:text-green-200";
switch (level) {
case "error":
return "bg-red-300/5 border-red-600 text-red-600 dark:bg-red-100/5 dark:border-red-300 dark:text-red-300";
case "warning":
return "bg-amber-300/5 border-amber-600 text-amber-600 dark:bg-amber-100/5 dark:border-amber-200 dark:text-amber-200";
case "info":
return "bg-sky-300/5 border-sky-600 text-sky-600 dark:bg-sky-100/5 dark:border-sky-200 dark:text-sky-200";
case "success":
return "bg-green-300/5 border-green-600 text-green-600 dark:bg-green-100/5 dark:border-green-200 dark:text-green-200";
}
};
21 changes: 12 additions & 9 deletions src/routes/about/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,19 @@ export const aboutRoute = new Route({
</a>
</div>

<div className="text-right">
Sviluppato con amore da{" "}
<a
target="_blank"
rel="noreferrer noopener"
href="https://polinetwork.org"
>
PoliNetwork
</a>{" "}
<div className="flex items-center justify-between">
<p className="opacity-60">Versione: {APP_VERSION}</p>
<p>
Sviluppato con amore da{" "}
<a
target="_blank"
rel="noreferrer noopener"
href="https://polinetwork.org"
>
PoliNetwork
</a>{" "}
❤️
</p>
</div>
</div>
</Page>
Expand Down
15 changes: 15 additions & 0 deletions src/routes/homepage/chooseSchool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ import { Button } from "@/components/ui/button";
import { homepageRoute } from ".";
import { ButtonGrid } from "@/components/Homepage/ButtonGrid";
import DevSettings from "@/components/DevSettings";
import School from "@/utils/types/data/School";

function getSchoolEmoji(school: School) {
switch (school) {
case "Architettura":
return (<span className="mr-2 text-lg rotate-[270deg]">&#128208;</span>);
case "Design":
return (<span className="mr-2 text-lg">&#128396;&#65039;</span>);
case "Ingegneria":
return (<span className="mr-2 text-lg">&#128736;&#65039;</span>);
case "Urbanistica":
return (<span className="mr-2 text-lg">&#127969;</span>);
}
}

export const chooseSchoolRoute = new Route({
getParentRoute: () => homepageRoute,
Expand Down Expand Up @@ -37,6 +51,7 @@ export const chooseSchoolRoute = new Route({
className="h-full"
>
<Button size="card" variant="secondary" className="h-full w-full">
{getSchoolEmoji(school)}
<span className="text-lg">{school}</span>
</Button>
</Link>
Expand Down
2 changes: 1 addition & 1 deletion src/routes/viewer/CourseCombobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function CourseCombobox({ value, courses: c, onSelect }: Props) {

return (
<div className="flex items-center space-x-4">
<p className="text-muted-foreground text-sm">Corso</p>
<p className="text-sm">Corso</p>
{selectedCourse ? (
<Removable onRemove={() => handleSelect(absCourse.value)}>
{selectedCourse.label}
Expand Down
2 changes: 1 addition & 1 deletion src/routes/viewer/LocationSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default function LocationsSelect(props: Props) {
if (locations.length === 0) return <></>;
return (
<div className="flex items-center space-x-4">
<p className="text-muted-foreground text-sm">Sede</p>
<p className="text-sm">Sede</p>
{locations.length >= 2 ? (
isMobile ? (
LocationCombobox(props)
Expand Down
2 changes: 1 addition & 1 deletion src/routes/viewer/PhaseSelect/GroupSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function GroupSelect(props: GroupSelectProps) {
const { groups, isCombobox, selectedGroup, groupOpen, onChange } = props;
return (
<div className="flex items-center space-x-4">
<p className="text-muted-foreground text-sm">Fase</p>
<p className="text-sm">Fase</p>
{groups.size >= 2 ? (
isCombobox ? (
<GroupCombobox
Expand Down
2 changes: 1 addition & 1 deletion src/routes/viewer/PhaseSelect/LangSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function LangSelect({

return (
<div className="flex items-center space-x-4">
<p className="text-muted-foreground text-sm">Lingua</p>
<p className="text-sm">Lingua</p>
{canChoose ? (
<Tabs
value={selectedLang}
Expand Down
2 changes: 1 addition & 1 deletion src/routes/viewer/PhaseSelect/RankingSelect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function RankingSelect(props: RankingSelectProps) {
const { phases, isCombobox, rankingOpen, selectedPhase, onChange } = props;
return (
<div className="flex items-center space-x-4">
<p className="text-muted-foreground text-sm">Graduatoria</p>
<p className="text-sm">Graduatoria</p>
{phases.length >= 2 ? (
isCombobox ? (
<RankingCombobox
Expand Down
42 changes: 23 additions & 19 deletions src/routes/viewer/Table/FilterBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
} from "@/components/ui/popover";
import { Separator } from "@/components/ui/separator";
import { Removable } from "@/components/custom-ui/Removable";
import { useState } from "react";

export type FilterOption<T> = {
originalValue: T;
Expand All @@ -39,11 +40,29 @@ export function FilterBtn<TData, TValue>({
title,
options,
}: Props<TData, TValue>) {
const [open, setOpen] = useState<boolean>(false);
const facets = column.getFacetedUniqueValues();
const selectedValues = new Set(column?.getFilterValue() as string[]);

function handleClearFilter(): void {
column?.setFilterValue(undefined);
setOpen(false);
}

function handleOptionSelect(
option: FilterOption<TValue>,
isSelected: boolean,
): void {
if (options.length <= 2) selectedValues.clear(); // filter with radio behaviour
if (isSelected) selectedValues.delete(option.value); // toggle behaviour
else selectedValues.add(option.value);

const filterValues = Array.from(selectedValues); // get selected filter options
column?.setFilterValue(filterValues.length ? filterValues : undefined); // update table
}

return facets.size >= 2 ? (
<Popover>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
Expand Down Expand Up @@ -101,20 +120,7 @@ export function FilterBtn<TData, TValue>({
return (
<CommandItem
key={option.value}
onSelect={() => {
if (isSelected) {
selectedValues.delete(option.value);
} else {
if (options.length <= 2) {
selectedValues.clear();
}
selectedValues.add(option.value);
}
const filterValues = Array.from(selectedValues);
column?.setFilterValue(
filterValues.length ? filterValues : undefined,
);
}}
onSelect={() => handleOptionSelect(option, isSelected)}
>
<div
className={cn(
Expand All @@ -127,9 +133,7 @@ export function FilterBtn<TData, TValue>({
>
<CheckIcon className={cn("h-4 w-4")} />
</div>
{option.icon && (
<option.icon className="text-muted-foreground mr-2 h-4 w-4" />
)}
{option.icon && <option.icon className="mr-2 h-4 w-4" />}
<span>{option.label}</span>
{facet && (
<span className="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
Expand All @@ -145,7 +149,7 @@ export function FilterBtn<TData, TValue>({
<CommandSeparator />
<CommandGroup>
<CommandItem
onSelect={() => column?.setFilterValue(undefined)}
onSelect={handleClearFilter}
className="justify-center text-center"
>
Pulisci i filtri
Expand Down
2 changes: 1 addition & 1 deletion src/routes/viewer/Table/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function Pagination<TData>({
<div className="my-2 flex items-center justify-end max-sm:flex-col max-sm:items-start max-sm:gap-2 sm:space-x-6">
<div className="sm:flex-1">
<p className="text-sm font-medium">
Totale righe: {table.getCoreRowModel().rows.length}
Totale righe: {table.getFilteredRowModel().rows.length}
</p>
</div>
<div className="flex items-center space-x-2">
Expand Down
95 changes: 84 additions & 11 deletions src/routes/viewer/Table/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { FilterBtn } from "./FilterBtn";
import { enrollStatusOpts, enrollAllowedOpts } from "./columns";
import { StudentResultKeys } from ".";
import StudentResult from "@/utils/types/data/parsed/Ranking/StudentResult";
import { sha256 } from "@/utils/strings/crypto";
import { useState } from "react";
import { LuXCircle } from "react-icons/lu";
import { Removable } from "@/components/custom-ui/Removable";

type Props = {
has: Record<StudentResultKeys, boolean>;
Expand All @@ -16,20 +20,89 @@ type Props = {
export function Toolbar({ has, onCsvClick, table }: Props) {
const enrollStatusCol = table.getColumn("enrollStatus");
const enrollAllowedCol = table.getColumn("enrollAllowed");
const [matricolaFilter, setMatricolaFilter] = useState<string>("");
const [matricolaFilterSubmitted, setMatricolaFilterSubmitted] =
useState<boolean>(false);
const { rows: filteredRows } = table.getFilteredRowModel();

function clearMatricolaTableFilter() {
table.getColumn("id")?.setFilterValue(undefined);
}

function handleClearMatricolaFilter() {
setMatricolaFilter("");
clearMatricolaTableFilter();
setMatricolaFilterSubmitted(false);
}

function handleMatricolaFilterChange(
event: React.ChangeEvent<HTMLInputElement>,
) {
if (matricolaFilterSubmitted) clearMatricolaTableFilter();
const input = event.target.value;
setMatricolaFilter(input);
setMatricolaFilterSubmitted(false);
}

async function handleMatricolaFilterSubmit(
e: React.FormEvent<HTMLFormElement>,
) {
e.preventDefault();

if (matricolaFilter.length === 0) handleClearMatricolaFilter();
else {
const hash = await sha256(matricolaFilter);
table.getColumn("id")?.setFilterValue(hash);
setMatricolaFilterSubmitted(true);
}
}

return (
<div className="flex w-full flex-wrap items-center justify-start gap-4 max-sm:items-start max-2xs:flex-col">
<div className="flex w-full flex-wrap items-start justify-start gap-6 max-2xs:flex-col">
{table.getColumn("id") && (
<Input
className="w-full sm:max-w-[300px]"
placeholder="Filter per matricola..."
value={(table.getColumn("id")?.getFilterValue() as string) ?? ""}
onChange={(event) =>
table.getColumn("id")?.setFilterValue(event.target.value)
}
/>
<div className="grid grid-cols-[auto_220px] grid-rows-[auto_auto] gap-x-4 gap-y-1">
<p className="self-center text-sm">Matricola</p>
{filteredRows.length > 0 && matricolaFilterSubmitted ? (
<Removable
onRemove={handleClearMatricolaFilter}
className="justify-between"
>
{matricolaFilter}
</Removable>
) : (
<>
<form
className="relative w-full"
onSubmit={handleMatricolaFilterSubmit}
>
<Input
placeholder="Inserisci la matricola..."
value={matricolaFilter}
onChange={handleMatricolaFilterChange}
/>
{matricolaFilter.length > 0 && (
<Button
type="button"
variant="ghost"
size="icon"
className="absolute right-1 top-1/2 h-7 w-7 -translate-y-1/2 text-gray-500 hover:bg-transparent hover:text-gray-900 dark:text-gray-400 dark:hover:bg-transparent dark:hover:text-gray-100"
onClick={handleClearMatricolaFilter}
>
<LuXCircle className="h-5 w-5" />
<span className="sr-only">Clear</span>
</Button>
)}
</form>
{matricolaFilterSubmitted && (
<p className="col-start-2 text-sm text-red-600 dark:text-red-400">
Matricola non trovata, ricontrolla.
</p>
)}
</>
)}
</div>
)}
<div className="flex flex-1 justify-start gap-4 max-xs:flex-wrap">
<div className="flex flex-1 justify-start gap-6 max-xs:flex-wrap">
{has.enrollAllowed && enrollAllowedCol && (
<FilterBtn
column={enrollAllowedCol}
Expand All @@ -45,7 +118,7 @@ export function Toolbar({ has, onCsvClick, table }: Props) {
/>
)}
</div>
<div className="flex justify-end ">
<div className="flex justify-end">
<Button
variant="outline"
className="whitespace-nowrap"
Expand Down
4 changes: 1 addition & 3 deletions src/routes/viewer/Table/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ class Formatter {

return (
<div className="flex items-center justify-center">
{option.icon && (
<option.icon className="text-muted-foreground mr-2 h-4 w-4" />
)}
{option.icon && <option.icon className="mr-2 h-4 w-4" />}
<span>{option.label}</span>
</div>
);
Expand Down
Loading

0 comments on commit f14f6fb

Please sign in to comment.