diff --git a/TODO.md b/TODO.md index 70ab671..f2e00e8 100644 --- a/TODO.md +++ b/TODO.md @@ -5,5 +5,9 @@ - [ ] chart of sum of each ingredient used in the last 7 days (will require quantity on ingredients_required) (for brown suger, corn syrup, sweetened condensed milk, butter, cream) - [/] self hosted postgres with DigitalOcean backups - [ ] if internet goes down fallback to local (???) -- [/] Easily searchable - [ ] Export current table to csv, pdf, html (use typst for non-csv) +- [ ] Add represetative to customers & suppliers +- [ ] Add point of contact for customers +- [ ] diagnose permissions looking disabled in custom mode +- [ ] orders displays nothing, check column value for label +- [ ] Milk icon for ITs diff --git a/backend/src/routes/user.rs b/backend/src/routes/user.rs index a7ccf25..67586bc 100644 --- a/backend/src/routes/user.rs +++ b/backend/src/routes/user.rs @@ -3,7 +3,6 @@ use crate::models::{ user::{User, UserCreate, UserIdentifier, UserNoPassword, UserUpdate}, }; use crate::routes::SortOrder; -use actix_identity::Identity; use actix_web::{ HttpResponse, Responder, Scope, web::{Data, Query}, diff --git a/frontend/components/forms/batch.tsx b/frontend/components/forms/batch.tsx index b485179..d22a17e 100644 --- a/frontend/components/forms/batch.tsx +++ b/frontend/components/forms/batch.tsx @@ -14,7 +14,6 @@ import { } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Option, AsyncCombobox } from "../ui/combobox" -import { DatePicker } from "../ui/date-picker" export async function getFlavors( query: string, diff --git a/frontend/components/forms/ingredient.tsx b/frontend/components/forms/ingredient.tsx index 2426e7f..7905da6 100644 --- a/frontend/components/forms/ingredient.tsx +++ b/frontend/components/forms/ingredient.tsx @@ -169,7 +169,7 @@ export function IngredientForm({ name="date_recieved" control={control} render={({ field }) => ( - + )} /> diff --git a/frontend/components/pages/batches.tsx b/frontend/components/pages/batches.tsx index 5e5901b..66ff378 100644 --- a/frontend/components/pages/batches.tsx +++ b/frontend/components/pages/batches.tsx @@ -11,6 +11,7 @@ import { ColumnFiltersState, } from "@tanstack/react-table" import { + createColumn, SortableFilterableColumnHeader, Table, TableBody, @@ -33,7 +34,7 @@ import { Card, CardContent } from "@/components/ui/card" import { Modal } from "@/components/modal" import { UserPermissions } from "@/lib/userPermissions" -import { MoreHorizontal, UserPlus2 } from "lucide-react" +import { Candy, MoreHorizontal } from "lucide-react" import { useQuery } from "@tanstack/react-query" import { useDebounce } from "use-debounce" import { TablePaginator } from "../tablePages" @@ -147,24 +148,16 @@ export function BatchesPage({ userPerms }: Props) { () => [ { accessorKey: "id", - header: "ID", - size: 250, - cell: ({ row }) => ( - - {row.getValue("id")} - - ), }, { accessorKey: "lot_number", header: ({ column }) => ( ), size: 250, @@ -175,10 +168,12 @@ export function BatchesPage({ userPerms }: Props) { accessorKey: "flavor", header: ({ column }) => ( ), size: 250, @@ -193,9 +188,11 @@ export function BatchesPage({ userPerms }: Props) { accessorKey: "created_at", header: ({ column }) => ( ), size: 150, @@ -258,7 +255,12 @@ export function BatchesPage({ userPerms }: Props) { data: rows, columns, pageCount, - state: { pagination, sorting, columnFilters }, + state: { + pagination, + sorting, + columnFilters, + columnVisibility: { id: false }, + }, onPaginationChange: setPagination, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, @@ -305,7 +307,7 @@ export function BatchesPage({ userPerms }: Props) { disabled={!userPerms.contains(PERM_CREATE)} onClick={() => setEditOpen(true)} > - New {ENTITY_NAME} + New {ENTITY_NAME} diff --git a/frontend/components/pages/customers.tsx b/frontend/components/pages/customers.tsx index caaddb1..227f758 100644 --- a/frontend/components/pages/customers.tsx +++ b/frontend/components/pages/customers.tsx @@ -11,6 +11,7 @@ import { ColumnFiltersState, } from "@tanstack/react-table" import { + createColumn, SortableFilterableColumnHeader, Table, TableBody, @@ -33,7 +34,7 @@ import { Card, CardContent } from "@/components/ui/card" import { Modal } from "@/components/modal" import { UserPermissions } from "@/lib/userPermissions" -import { MoreHorizontal, UserPlus2 } from "lucide-react" +import { MoreHorizontal, Users } from "lucide-react" import { useQuery } from "@tanstack/react-query" import { useDebounce } from "use-debounce" import { TablePaginator } from "../tablePages" @@ -109,24 +110,16 @@ export function CustomersPage({ userPerms }: { userPerms: UserPermissions }) { () => [ { accessorKey: "id", - header: "ID", - size: 150, - cell: ({ row }) => ( - - {row.getValue("id")} - - ), }, { accessorKey: "name", header: ({ column }) => ( ), size: 100, @@ -191,7 +184,12 @@ export function CustomersPage({ userPerms }: { userPerms: UserPermissions }) { data: rows, columns, pageCount, - state: { pagination, sorting, columnFilters }, + state: { + pagination, + sorting, + columnFilters, + columnVisibility: { id: false }, + }, onPaginationChange: setPagination, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, @@ -234,7 +232,7 @@ export function CustomersPage({ userPerms }: { userPerms: UserPermissions }) { disabled={!userPerms.contains(UserPermissions.CustomerCreate)} onClick={() => setEditOpen(true)} > - New Customer + New Customer diff --git a/frontend/components/pages/flavors.tsx b/frontend/components/pages/flavors.tsx index 2026212..9722178 100644 --- a/frontend/components/pages/flavors.tsx +++ b/frontend/components/pages/flavors.tsx @@ -11,6 +11,7 @@ import { ColumnFiltersState, } from "@tanstack/react-table" import { + createColumn, SortableFilterableColumnHeader, Table, TableBody, @@ -33,7 +34,7 @@ import { Card, CardContent } from "@/components/ui/card" import { Modal } from "@/components/modal" import { UserPermissions } from "@/lib/userPermissions" -import { MoreHorizontal, UserPlus2 } from "lucide-react" +import { ChefHat, Milk, MoreHorizontal } from "lucide-react" import { useQuery } from "@tanstack/react-query" import { useDebounce } from "use-debounce" import { TablePaginator } from "../tablePages" @@ -164,24 +165,16 @@ export function FlavorsPage({ userPerms }: Props) { () => [ { accessorKey: "id", - header: "ID", - size: 150, - cell: ({ row }) => ( - - {row.getValue("id")} - - ), }, { accessorKey: "name", header: ({ column }) => ( ), size: 150, @@ -253,7 +246,12 @@ export function FlavorsPage({ userPerms }: Props) { data: rows, columns, pageCount, - state: { pagination, sorting, columnFilters }, + state: { + pagination, + sorting, + columnFilters, + columnVisibility: { id: false }, + }, onPaginationChange: setPagination, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, @@ -300,7 +298,7 @@ export function FlavorsPage({ userPerms }: Props) { disabled={!userPerms.contains(PERM_CREATE)} onClick={() => setIngredientTypeOpen(true)} > - All Ingredient Types + All Ingredient Types diff --git a/frontend/components/pages/ingredients.tsx b/frontend/components/pages/ingredients.tsx index d8ab59f..dd4f493 100644 --- a/frontend/components/pages/ingredients.tsx +++ b/frontend/components/pages/ingredients.tsx @@ -11,6 +11,7 @@ import { ColumnFiltersState, } from "@tanstack/react-table" import { + createColumn, SortableFilterableColumnHeader, Table, TableBody, @@ -33,7 +34,7 @@ import { Card, CardContent } from "@/components/ui/card" import { Modal } from "@/components/modal" import { UserPermissions } from "@/lib/userPermissions" -import { MoreHorizontal, UserPlus2 } from "lucide-react" +import { FlaskConical, MoreHorizontal } from "lucide-react" import { useQuery } from "@tanstack/react-query" import { useDebounce } from "use-debounce" import { TablePaginator } from "../tablePages" @@ -164,25 +165,17 @@ export function IngredientsPage({ userPerms }: Props) { () => [ { accessorKey: "id", - header: "ID", - size: 250, - cell: ({ row }) => ( - - {row.getValue("id")} - - ), }, { accessorKey: "supplier", header: ({ column }) => ( ), size: 150, @@ -197,9 +190,11 @@ export function IngredientsPage({ userPerms }: Props) { accessorKey: "lot", header: ({ column }) => ( ), size: 150, @@ -210,10 +205,12 @@ export function IngredientsPage({ userPerms }: Props) { accessorKey: "ingredient_type", header: ({ column }) => ( ), size: 150, @@ -228,14 +225,18 @@ export function IngredientsPage({ userPerms }: Props) { accessorKey: "date_recieved", header: ({ column }) => ( ), size: 150, enableSorting: true, - enableColumnFilter: false, + enableColumnFilter: true, cell: ({ row }) => { const dateRecieved = row.getValue("date_recieved") as Date return {new Date(dateRecieved).toLocaleDateString()} @@ -245,14 +246,18 @@ export function IngredientsPage({ userPerms }: Props) { accessorKey: "date_opened", header: ({ column }) => ( ), size: 150, enableSorting: true, - enableColumnFilter: false, + enableColumnFilter: true, cell: ({ row }) => { const dateOpened = row.getValue("date_opened") as Date | undefined return ( @@ -268,14 +273,18 @@ export function IngredientsPage({ userPerms }: Props) { accessorKey: "date_finished", header: ({ column }) => ( ), size: 150, enableSorting: true, - enableColumnFilter: false, + enableColumnFilter: true, cell: ({ row }) => { const dateFinished = row.getValue("date_finished") as Date | undefined const dateOpened = row.getValue("date_opened") as Date | undefined @@ -383,7 +392,12 @@ export function IngredientsPage({ userPerms }: Props) { data: rows, columns, pageCount, - state: { pagination, sorting, columnFilters }, + state: { + pagination, + sorting, + columnFilters, + columnVisibility: { id: false }, + }, onPaginationChange: setPagination, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, @@ -430,7 +444,7 @@ export function IngredientsPage({ userPerms }: Props) { disabled={!userPerms.contains(PERM_CREATE)} onClick={() => setEditOpen(true)} > - New {ENTITY_NAME} + New {ENTITY_NAME} diff --git a/frontend/components/pages/orders.tsx b/frontend/components/pages/orders.tsx index a583725..09cb4f9 100644 --- a/frontend/components/pages/orders.tsx +++ b/frontend/components/pages/orders.tsx @@ -11,6 +11,7 @@ import { ColumnFiltersState, } from "@tanstack/react-table" import { + createColumn, SortableFilterableColumnHeader, Table, TableBody, @@ -169,24 +170,16 @@ export function OrdersPage({ userPerms }: Props) { () => [ { accessorKey: "id", - header: "ID", - size: 150, - cell: ({ row }) => ( - - {row.getValue("id")} - - ), }, { accessorKey: "invoice_number", header: ({ column }) => ( ), size: 150, @@ -197,10 +190,12 @@ export function OrdersPage({ userPerms }: Props) { accessorKey: "customer_id", header: ({ column }) => ( ), size: 150, @@ -215,10 +210,12 @@ export function OrdersPage({ userPerms }: Props) { accessorKey: "batch_ids", header: ({ column }) => ( ), size: 150, @@ -280,7 +277,12 @@ export function OrdersPage({ userPerms }: Props) { data: rows, columns, pageCount, - state: { pagination, sorting, columnFilters }, + state: { + pagination, + sorting, + columnFilters, + columnVisibility: { id: false }, + }, onPaginationChange: setPagination, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, diff --git a/frontend/components/pages/suppliers.tsx b/frontend/components/pages/suppliers.tsx index 98a8658..9d392d8 100644 --- a/frontend/components/pages/suppliers.tsx +++ b/frontend/components/pages/suppliers.tsx @@ -11,6 +11,7 @@ import { ColumnFiltersState, } from "@tanstack/react-table" import { + createColumn, SortableFilterableColumnHeader, Table, TableBody, @@ -117,24 +118,16 @@ export function SuppliersPage({ userPerms }: { userPerms: UserPermissions }) { () => [ { accessorKey: "id", - header: "ID", - size: 150, - cell: ({ row }) => ( - - {row.getValue("id")} - - ), }, { accessorKey: "name", header: ({ column }) => ( ), size: 100, @@ -145,9 +138,11 @@ export function SuppliersPage({ userPerms }: { userPerms: UserPermissions }) { accessorKey: "phone", header: ({ column }) => ( ), size: 60, @@ -158,9 +153,11 @@ export function SuppliersPage({ userPerms }: { userPerms: UserPermissions }) { accessorKey: "address", header: ({ column }) => ( ), size: 180, @@ -218,7 +215,12 @@ export function SuppliersPage({ userPerms }: { userPerms: UserPermissions }) { data: rows, columns, pageCount, - state: { pagination, sorting, columnFilters }, + state: { + pagination, + sorting, + columnFilters, + columnVisibility: { id: false }, + }, onPaginationChange: setPagination, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, diff --git a/frontend/components/pages/template b/frontend/components/pages/template deleted file mode 100644 index 7aeb634..0000000 --- a/frontend/components/pages/template +++ /dev/null @@ -1,346 +0,0 @@ -"use client" - -import { useEffect, useMemo, useState } from "react" -import { - ColumnDef, - flexRender, - getCoreRowModel, - SortingState, - useReactTable, - PaginationState, - ColumnFiltersState, -} from "@tanstack/react-table" -import { - SortableFilterableColumnHeader, - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, - TableRowSkeleton, -} from "@/components/ui/table" -import { Button } from "@/components/ui/button" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" -import { Card, CardContent } from "@/components/ui/card" -import { Modal } from "@/components/modal" - -import { UserPermissions } from "@/lib/userPermissions" -import { MoreHorizontal, UserPlus2 } from "lucide-react" -import { useQuery } from "@tanstack/react-query" -import { useDebounce } from "use-debounce" -import { TablePaginator } from "../tablePages" - -const ENTITY_NAME = "Item" -const ENTITY_PLURAL = "items" -const API_PATH = "/api/items" - -const SORTABLE_COLUMNS = ["name"] as const -const DEFAULT_SORT_BY = "name" - -const PERM_CREATE = UserPermissions.SuperAdmin -const PERM_UPDATE = UserPermissions.SuperAdmin -const PERM_DELETE = UserPermissions.SuperAdmin - -import { EntityForm, Entity } from "../forms/item" - -interface PaginatedResponse { - rows: Entity[] - total: number - page_count: number -} - -const DISPLAY_PROP: keyof Entity = "name" - -interface Props { - userPerms: UserPermissions -} - -export function ItemsPage({ userPerms }: Props) { - const [editOpen, setEditOpen] = useState(false) - const [deleteOpen, setDeleteOpen] = useState(false) - const [deleteError, setDeleteError] = useState(null) - const [entity, setEntity] = useState(null) - - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: 10, - }) - const [sorting, setSorting] = useState([]) - const [columnFilters, setColumnFilters] = useState([]) - - const [debouncedFilters] = useDebounce(columnFilters, 300) - - const nameFilter = - (debouncedFilters.find((f) => f.id === "name")?.value as string) ?? "" - - useEffect(() => { - setPagination((p) => ({ ...p, pageIndex: 0 })) - }, [debouncedFilters, sorting]) - - const limit = pagination.pageSize - const offset = pagination.pageIndex * pagination.pageSize - - const sort = sorting.find((s) => SORTABLE_COLUMNS.includes(s.id)) - const sortBy = sort?.id ?? DEFAULT_SORT_BY - const sortOrder = sorting.find((s) => SORTABLE_COLUMNS.includes(s.id))?.desc - ? "desc" - : "asc" - - const { data, isLoading, error, refetch } = useQuery({ - queryKey: [ - ENTITY_PLURAL, - limit, - offset, - sortBy, - sortOrder, - nameFilter, - debouncedFilters, - ], - queryFn: async () => { - const params = new URLSearchParams({ - limit: String(limit), - offset: String(offset), - sort_by: sortBy, - sort_order: sortOrder, - }) - - if (nameFilter) params.set("name", nameFilter) - - const res = await fetch(`${API_PATH}?${params.toString()}`) - if (!res.ok) throw new Error(`Failed to fetch ${ENTITY_PLURAL}`) - return res.json() - }, - }) - - const rows = data?.rows ?? [] - const pageCount = data?.page_count ?? 0 - - const columns = useMemo[]>( - () => [ - { - accessorKey: "id", - header: "ID", - size: 150, - cell: ({ row }) => ( - - {row.getValue("id")} - - ), - }, - { - accessorKey: "actions", - header: "Actions", - size: 60, - cell: ({ row }) => { - const item = row.original - return ( - - - - - - Actions - - { - setEditOpen(true) - setEntity(item) - }} - disabled={!userPerms.contains(PERM_UPDATE)} - className="cursor-pointer" - > - Edit - - { - setDeleteOpen(true) - setEntity(item) - }} - disabled={!userPerms.contains(PERM_DELETE)} - className="cursor-pointer" - > - Delete - - - - ) - }, - }, - ], - [userPerms] - ) - - // eslint-disable-next-line react-hooks/incompatible-library - const table = useReactTable({ - data: rows, - columns, - pageCount, - state: { pagination, sorting, columnFilters }, - onPaginationChange: setPagination, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - getCoreRowModel: getCoreRowModel(), - manualPagination: true, - manualSorting: true, - manualFiltering: true, - }) - - useEffect(() => { - if (!editOpen && !deleteOpen) setEntity(null) - }, [editOpen, deleteOpen]) - - async function deleteEntity(id: string) { - const res = await fetch(`${API_PATH}/${id}`, { - method: "DELETE", - }) - if (res.ok) { - refetch() - } else { - const data = await res.json() - setDeleteError(data.error) - } - } - - const displayName = entity?.[DISPLAY_PROP] as string | undefined - const entityTitle = displayName ?? ENTITY_NAME - - return ( - <> - - - {error ? ( -

{error.message}

- ) : ( -
-
- {deleteError && ( -

{deleteError}

- )} - -
- -
- - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ))} - - ))} - - - {isLoading ? ( - - ) : ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - )) - )} - -
- -
- table.setPageIndex(p)} - pageSize={pagination.pageSize} - onPageSizeChange={(s) => table.setPageSize(s)} - /> -
- - )} - - - - - - - - -
- - -
-
- - ) -} diff --git a/frontend/components/pages/users.tsx b/frontend/components/pages/users.tsx index 0fd49ba..7a6904c 100644 --- a/frontend/components/pages/users.tsx +++ b/frontend/components/pages/users.tsx @@ -20,6 +20,7 @@ import { TableHeader, TableRow, TableRowSkeleton, + createColumn, } from "@/components/ui/table" import { Button } from "@/components/ui/button" import { @@ -128,24 +129,16 @@ export function UsersPage({ userPerms }: { userPerms: UserPermissions }) { () => [ { accessorKey: "id", - header: "ID", - size: 280, - cell: ({ row }) => ( - - {row.getValue("id")} - - ), }, { accessorKey: "full_name", header: ({ column }) => ( ), size: 300, @@ -156,9 +149,11 @@ export function UsersPage({ userPerms }: { userPerms: UserPermissions }) { accessorKey: "username", header: ({ column }) => ( ), size: 200, @@ -232,7 +227,12 @@ export function UsersPage({ userPerms }: { userPerms: UserPermissions }) { data: rows, columns, pageCount, - state: { pagination, sorting, columnFilters }, + state: { + pagination, + sorting, + columnFilters, + columnVisibility: { id: false }, + }, onPaginationChange: setPagination, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, diff --git a/frontend/components/ui/date-picker.tsx b/frontend/components/ui/date-picker.tsx index 3c6ec14..158ea81 100644 --- a/frontend/components/ui/date-picker.tsx +++ b/frontend/components/ui/date-picker.tsx @@ -11,32 +11,61 @@ import { PopoverTrigger, } from "@/components/ui/popover" -export function DatePicker({ - date, - onDateChange, -}: { - date: Date | undefined +interface DatePickerProps { + value: Date | undefined onDateChange: (date: Date | undefined) => void -}) { + range?: false +} + +interface DatePickerRangeProps { + value: { from: Date | undefined; to?: Date } | undefined + onDateChange: ( + dates: { from: Date | undefined; to?: Date } | undefined + ) => void + range: true +} + +export function DatePicker(props: DatePickerProps | DatePickerRangeProps) { + const { value, onDateChange, range } = props + return ( - + {!range && ( + + )} + {range && ( + + )} ) diff --git a/frontend/components/ui/table.tsx b/frontend/components/ui/table.tsx index ed586ed..5e35062 100644 --- a/frontend/components/ui/table.tsx +++ b/frontend/components/ui/table.tsx @@ -8,6 +8,7 @@ import { Input } from "./input" import { Button } from "./button" import { Skeleton } from "./skeleton" import { AsyncCombobox, Option } from "./combobox" +import { DatePicker } from "./date-picker" function Table({ className, ...props }: React.ComponentProps<"table">) { return ( @@ -148,22 +149,119 @@ function FilterInput({ ) } -function SortableFilterableColumnHeader({ - column, - title, - placeholder, - get_options, -}: { +interface SFCHPBase { // eslint-disable-next-line @typescript-eslint/no-explicit-any column: any title: string placeholder?: string - get_options?: (query: string, page: number) => Promise -}) { +} + +interface DefaultColumn extends SFCHPBase { + type: "default" +} + +interface WithOptions extends SFCHPBase { + type: "select" + get_options: (query: string, page: number) => Promise +} + +interface Dates extends SFCHPBase { + type: "date" + from: boolean + to: boolean +} + +export function createColumn>( + config: T +): T extends { get_options: (query: string, page: number) => Promise } + ? WithOptions + : T extends { from: boolean; to: boolean } + ? Dates + : DefaultColumn { + if ("get_options" in config) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return { ...config, type: "select" } as any + } + if ("from" in config || "to" in config) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return { ...config, type: "date" } as any + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return { ...config, type: "default" } as any +} + +type SFCHP = DefaultColumn | WithOptions | Dates + +function SortableFilterableColumnHeader(props: SFCHP) { + const { column, title, placeholder, type } = props + const get_options = type === "select" ? props.get_options : undefined + const { from, to } = type === "date" ? props : {} const [showFilter, setShowFilter] = React.useState(false) const canSort = column.getCanSort() const canFilter = column.getCanFilter() + function getDateFilterInput() { + console.log(from, to) + if (type !== "date") return null + if (from && to) + return ( + column.setFilterValue(dates)} + range + /> + ) + if (from) + return ( + <> + {" "} + - + column.setFilterValue({ from: date })} + /> + + ) + if (to) + return ( + <> + column.setFilterValue({ to: date })} + />{" "} + - + + ) + return null + } + + function getFilterInput() { + switch (type) { + case "select": + return ( + column.setFilterValue(value)} + /> + ) + case "date": + return getDateFilterInput() + case "default": + default: + return ( + column.setFilterValue(value)} + placeholder={placeholder} + /> + ) + } + } + return (
@@ -183,20 +281,7 @@ function SortableFilterableColumnHeader({ )}
- {canFilter && showFilter && !get_options && ( - column.setFilterValue(value)} - placeholder={placeholder} - /> - )} - {canFilter && showFilter && get_options && ( - column.setFilterValue(value)} - /> - )} + {canFilter && showFilter ? getFilterInput() : null}
) } diff --git a/frontend/lib/session.ts b/frontend/lib/session.ts index 86e720a..0b834a4 100644 --- a/frontend/lib/session.ts +++ b/frontend/lib/session.ts @@ -18,6 +18,22 @@ interface UserResponse { permissions: string } +async function fetchSelf(cookie: string): Promise { + const res = await fetch(`${BACKEND_URL}/auth/self`, { + headers: { Cookie: cookie }, + cache: "no-store", + }) + + if (res.status === 429) { + const retryAfter = res.headers.get("retry-after") + const delay = retryAfter ? parseInt(retryAfter, 10) * 1000 : 1000 + await new Promise((r) => setTimeout(r, delay)) + return fetchSelf(cookie) + } + + return res +} + export async function getSession(): Promise { const cookieStore = await cookies() const cookie = cookieStore @@ -25,10 +41,7 @@ export async function getSession(): Promise { .map((c) => `${c.name}=${c.value}`) .join("; ") - const res = await fetch(`${BACKEND_URL}/auth/self`, { - headers: { Cookie: cookie }, - cache: "no-store", - }) + const res = await fetchSelf(cookie) if (!res.ok) return null const res_data: UserResponse = await res.json()