Skip to content

Commit

Permalink
feat: add table filter by "matricola" (#109)
Browse files Browse the repository at this point in the history
* feat: add shacdn/ui input

* feat: add table filter by "matricola"
  • Loading branch information
lorenzocorallo authored Jul 11, 2023
1 parent 6ab7611 commit 0b22ea0
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 37 deletions.
24 changes: 24 additions & 0 deletions src/components/ui/input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from "react"

import { cn } from "@/utils/ui.ts"

export type InputProps = React.InputHTMLAttributes<HTMLInputElement>

const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-slate-300 bg-white px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-slate-400 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-800",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"

export { Input }
96 changes: 88 additions & 8 deletions src/routes/view/viewer/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { useState } from "react"
import { Table as TableType } from "@tanstack/react-table"
import { useMemo, useState } from "react"
import {
ColumnFiltersState,
Row,
Table as TableType
} from "@tanstack/react-table"
import {
MdOutlineKeyboardDoubleArrowLeft as DoubleArrowLeft,
MdKeyboardDoubleArrowRight as DoubleArrowRight,
MdKeyboardArrowLeft as ArrowLeft,
MdKeyboardArrowRight as ArrowRight
MdKeyboardArrowRight as ArrowRight,
MdDownload
} from "react-icons/md"
import {
CellContext,
Expand All @@ -14,6 +19,7 @@ import {
flexRender,
getCoreRowModel,
getPaginationRowModel,
getFilteredRowModel,
useReactTable
} from "@tanstack/react-table"

Expand All @@ -35,10 +41,16 @@ import {
} from "@/components/ui/select"
import School from "@/utils/types/data/School"
import StudentResult from "@/utils/types/data/parsed/Ranking/StudentResult"
import { Input } from "@/components/ui/input"
import MeritTable from "@/utils/types/data/parsed/Ranking/MeritTable"
import CourseTable from "@/utils/types/data/parsed/Ranking/CourseTable"
import Store from "@/utils/data/store"
import { sha256 } from "@/utils/strings"

interface TableProps extends React.HTMLAttributes<HTMLTableElement> {
school: School
rows: StudentResult[]
csvFilename: string
table: MeritTable | CourseTable
isGlobalRanking?: boolean
}

Expand Down Expand Up @@ -102,7 +114,9 @@ const headerBorder = (
return "border-x"
}

export default function Table({ rows }: TableProps) {
export default function Table({ table: pTable, csvFilename }: TableProps) {
const { rows } = pTable
const csv = useMemo(() => (pTable ? Store.tableToCsv(pTable) : ""), [pTable])
const has = makeHas(rows)
const columns = getColumns(rows)
const [columnVisibility, setColumnVisibility] =
Expand All @@ -112,21 +126,60 @@ export default function Table({ rows }: TableProps) {
pageIndex: 0
})

const [idFilter, setIdFilter] = useState<string>("")
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([
{
id: "matricola-hash",
value: idFilter
}
])

const handleIdFilterChange = async (v: string) => {
setIdFilter(v)
const hashed = v === "" ? "" : await sha256(v)
setColumnFilters([
{
id: "matricola-hash",
value: hashed
}
])
}

const table = useReactTable({
data: rows,
columns,
state: {
columnVisibility,
pagination
pagination,
columnFilters
},
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onColumnFiltersChange: setColumnFilters,
onPaginationChange: setPagination
})

return (
<div className="w-full">
<div className="flex w-full flex-col gap-4">
<div className="flex items-center justify-between gap-4">
{has.id && (
<Input
placeholder="Filtra per matricola..."
value={idFilter}
onChange={e => handleIdFilterChange(e.target.value)}
/>
)}
<Button
variant="secondary"
className="whitespace-nowrap"
onClick={() => downloadCsv(csv, csvFilename)}
>
<MdDownload size={20} />
Download CSV
</Button>
</div>
<div className="rounded-md border border-slate-300 dark:border-slate-700 [&_*]:border-slate-300 [&_*]:dark:border-slate-700">
<TableComponent>
<TableHeader>
Expand Down Expand Up @@ -368,7 +421,20 @@ function getColumns(rows: StudentResult[]): ColumnDef<StudentResult>[] {
{
accessorKey: "id",
header: "Matricola hash",
cell: CellFns.displayHash
id: "matricola-hash",
cell: CellFns.displayHash,
enableColumnFilter: true,
enableGlobalFilter: true,
filterFn: (
row: Row<StudentResult>,
_: string,
filterValue: string
): boolean => {
if (!filterValue) return true
return (
row.original.id?.slice(0, 10) === filterValue.slice(0, 10) ?? true
)
}
},
{
accessorKey: "birthDate",
Expand All @@ -388,3 +454,17 @@ function calculateRowSpan(header: Header<StudentResult, unknown>): number {

return 1
}

function downloadCsv(csv: string, filename: string) {
const blob = new Blob([csv], { type: "text/csv" })
const url = window.URL.createObjectURL(blob)

const a = document.createElement("a")
a.href = url
a.download = (filename ?? "data") + ".csv"
a.click()

a.remove()

window.URL.revokeObjectURL(url)
}
34 changes: 5 additions & 29 deletions src/routes/view/viewer/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { useContext, useEffect, useMemo, useState } from "react"
import { MdDownload } from "react-icons/md"
import { ErrorComponent, Navigate, Route, useNavigate } from "@tanstack/router"
import MobileContext from "@/contexts/MobileContext"
import School from "@/utils/types/data/School.ts"
import CourseTable from "@/utils/types/data/parsed/Ranking/CourseTable.ts"
import { PhaseLink } from "@/utils/types/data/parsed/Index/RankingFile.ts"
import { ABS_ORDER } from "@/utils/constants.ts"
import Store from "@/utils/data/store.ts"
import { Button } from "@/components/ui/button"
import Spinner from "@/components/custom-ui/Spinner.tsx"
import Page from "@/components/custom-ui/Page.tsx"
import { rootRoute } from "@/routes/root.tsx"
Expand Down Expand Up @@ -90,7 +88,6 @@ export const viewerRoute = new Route({
() => store.getTable(selectedCourse, selectedLocation),
[selectedCourse, selectedLocation, store]
)
const csv = useMemo(() => (table ? Store.tableToCsv(table) : ""), [table])

useEffect(() => {
if (!table) return
Expand Down Expand Up @@ -140,24 +137,17 @@ export const viewerRoute = new Route({
/>
)}
</div>
<div>
<Button
variant="secondary"
onClick={() =>
downloadCsv(csv, `${selectedCourse}_${selectedLocation ?? "0"}`)
}
>
<MdDownload size={20} />
Download CSV
</Button>
</div>
</div>

<div className="flex w-full flex-col gap-4 overflow-x-auto">
{selectedPhase?.name === ranking.phase ? (
<div className="col-start-1 col-end-3 row-start-1 row-end-2">
{table ? (
<Table school={school as School} rows={table.rows} />
<Table
school={school as School}
table={table}
csvFilename={`${selectedCourse}_${selectedLocation ?? "0"}`}
/>
) : (
<p>Nessun dato disponibile</p>
)}
Expand All @@ -172,17 +162,3 @@ export const viewerRoute = new Route({
)
}
})

function downloadCsv(csv: string, filename: string) {
const blob = new Blob([csv], { type: "text/csv" })
const url = window.URL.createObjectURL(blob)

const a = document.createElement("a")
a.href = url
a.download = (filename ?? "data") + ".csv"
a.click()

a.remove()

window.URL.revokeObjectURL(url)
}
2 changes: 2 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ export const SCHOOLS = [
export const ALERT_LEVELS = ["error", "warning", "success", "info"] as const

export const ABS_ORDER = "absorder" as const

export const SALT = "saltPoliNetwork" as const
17 changes: 17 additions & 0 deletions src/utils/strings.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { SALT } from "./constants"

export function capitalizeWords(str: string): string {
const words = str.split(/\b/)
const capitalizedWords = words.map(word => {
Expand All @@ -12,3 +14,18 @@ export function containsOnlyNumbers(input?: string): boolean {
if (!input) return false
return /^[0-9]+$/.test(input)
}

export async function sha256(input: string, salt = SALT): Promise<string> {
// encode as UTF-8
const msgBuffer = new TextEncoder().encode(input + salt)

// hash the input
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer)

// convert ArrayBuffer to Array
const hashArray = Array.from(new Uint8Array(hashBuffer))

// convert bytes to hex string
const hashHex = hashArray.map(b => ("00" + b.toString(16)).slice(-2)).join("")
return hashHex
}

0 comments on commit 0b22ea0

Please sign in to comment.