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