feat: implement barebone zola templates

This commit is contained in:
Alexander Navarro 2024-11-10 21:45:59 +00:00
parent 9c20f5ed2e
commit f99a9ae2ac
198 changed files with 2434 additions and 227991 deletions

View file

@ -0,0 +1,197 @@
import React, { useMemo, useRef, useState } from 'react';
import usePagination, { Offset } from 'src/hooks/usePagination';
import { SelectFilter, NumberFilter, resolveFilterByType } from './Filters';
import type { DataItem, Header, Value, Filter } from './types';
import { HeaderType } from './types';
import styles from './Table.module.css';
interface Props {
data: DataItem[];
headers: Header[];
}
export default function Table({ data, headers }: Props): JSX.Element {
const [filters, setFilters] = useState<Record<string, Filter>>({});
const filtersId = useRef(crypto.randomUUID());
const filteredItems = useMemo(() => {
return data.filter((item) => {
return Object.entries(filters).every(([key, filter]) =>
resolveFilterByType(filter, item[key]),
);
});
}, [data, filters]);
const { items, changeOffset, getPaginationRange } = usePagination<DataItem>({
items: filteredItems,
limit: 10,
});
const handleUpdateFilters = (
name: string,
type: HeaderType,
value: Value,
): void => {
setFilters((prev) => {
if (value === null) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete prev[name];
return { ...prev };
}
return { ...prev, [name]: { value, type } };
});
};
function formatCell(data: DataItem, header: Header): JSX.Element {
// This formatting is only used because the source is trusted (private markdown files manage only by me)
// and because Astro don't allow me to pass JSX from an Astro file to a TSX file,
// so I have to pass the formatted row as a string.
// DON'T use this method on a public API
if (header.hasCustomCell && header.formatter) {
return (
<div dangerouslySetInnerHTML={{ __html: header.formatter(data) }} />
);
}
if (header.type === HeaderType.Multiple) {
return (
<ul className="text-start">
{data[header.key].map((item: JSX.Element, idx: number) => (
<li key={idx}>{item}</li>
))}
</ul>
);
}
return data[header.key];
}
function formatFilter(header: Header): JSX.Element {
const baseProps = {
key: header.key + filtersId.current,
keyData: header.key,
value: filters[header.key]?.value,
onChange: (value: Value) => {
handleUpdateFilters(header.key, header.type, value);
},
};
switch (header.type) {
case HeaderType.String:
return (
<input
onChange={(e) => {
baseProps.onChange(e.target.value);
}}
key={baseProps.key}
/>
);
case HeaderType.Number:
return (
<NumberFilter
{...baseProps}
onChange={(value: [string, number | null]) => {
handleUpdateFilters(header.key, header.type, value as Value);
}}
/>
);
case HeaderType.Select:
return <SelectFilter data={data} {...baseProps} />;
case HeaderType.Multiple:
return <SelectFilter {...baseProps} isMultiple data={data} />;
default:
break;
}
return <></>;
}
return (
<>
<section className="hstack">
<button
className="ml-auto"
onClick={() => {
setFilters({});
filtersId.current = crypto.randomUUID();
}}
>
Clear Filters
</button>
</section>
<section className="mt-1 overflow-scroll">
<table className={styles.table}>
<thead>
<tr>
{headers.map((item, idx) => (
<th key={idx}>
<div className="vstack">
{item.header}
{formatFilter(item)}
</div>
</th>
))}
</tr>
</thead>
<tbody>
{items.map((item, idx) => (
<tr key={idx}>
{headers.map((header, hidx) => (
<td key={hidx}>{formatCell(item, header)}</td>
))}
</tr>
))}
</tbody>
</table>
</section>
<section className="mt-1">
<button
onClick={() => {
changeOffset(Offset.First);
}}
>
First
</button>
<button
onClick={() => {
changeOffset(Offset.Prev);
}}
>
Prev
</button>
{getPaginationRange().map((item) => (
<button
className={item.current ? 'btn-primary' : ''}
key={item.page}
onClick={() => {
changeOffset(Offset.To, item.page);
}}
>
{item.page}
</button>
))}
<button
onClick={() => {
changeOffset(Offset.Next);
}}
>
Next
</button>
<button
onClick={() => {
changeOffset(Offset.Last);
}}
>
Last
</button>
</section>
</>
);
}