From ed18af046be35cb56d43281a944c1a010f3d6007 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 15 Oct 2026 12:58:37 +0100 Subject: [PATCH] Add company and status to users and the clients table - Migration 00004 adds a nullable company column to users - Migration 00005 adds a user_status enum (active, suspended, closed) defaulting to active - The clients API returns company and status - The clients table shows Company and a Status badge --- internal/database/clients.go | 4 +++- migrations/00004_users_company.sql | 5 +++++ migrations/00005_users_status.sql | 9 +++++++++ web/src/api/clients/clients.ts | 3 +++ web/src/routes/admin/clients.tsx | 20 +++++++++++++++++++- 5 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 migrations/00004_users_company.sql create mode 100644 migrations/00005_users_status.sql diff --git a/internal/database/clients.go b/internal/database/clients.go index 61b99bf..f791f88 100644 --- a/internal/database/clients.go +++ b/internal/database/clients.go @@ -14,6 +14,8 @@ type Client struct { Email string `db:"email" json:"email"` GivenName string `db:"given_name" json:"given_name"` FamilyName string `db:"family_name" json:"family_name"` + Company *string `db:"company" json:"company"` + Status string `db:"status" json:"status"` CreatedAt time.Time `db:"created_at" json:"created_at"` } @@ -40,7 +42,7 @@ func (s *Store) ListClients(ctx context.Context, p ListClientsParams) ([]Client, } rows, _ := s.pool.Query(ctx, ` - SELECT u.id, u.email, u.given_name, u.family_name, u.created_at, count(*) OVER () AS total + SELECT u.id, u.email, u.given_name, u.family_name, u.company, u.status, u.created_at, count(*) OVER () AS total FROM users u WHERE strpos(lower(u.given_name || ' ' || u.family_name || ' ' || u.email), lower($1)) > 0 ORDER BY `+order+`, u.id diff --git a/migrations/00004_users_company.sql b/migrations/00004_users_company.sql new file mode 100644 index 0000000..8f98574 --- /dev/null +++ b/migrations/00004_users_company.sql @@ -0,0 +1,5 @@ +-- +goose Up +ALTER TABLE users ADD COLUMN company text; + +-- +goose Down +ALTER TABLE users DROP COLUMN company; diff --git a/migrations/00005_users_status.sql b/migrations/00005_users_status.sql new file mode 100644 index 0000000..18bd486 --- /dev/null +++ b/migrations/00005_users_status.sql @@ -0,0 +1,9 @@ +-- +goose Up +CREATE TYPE user_status AS ENUM ('active', 'suspended', 'closed'); + +ALTER TABLE users ADD COLUMN status user_status NOT NULL DEFAULT 'active'; + +-- +goose Down +ALTER TABLE users DROP COLUMN status; + +DROP TYPE user_status; diff --git a/web/src/api/clients/clients.ts b/web/src/api/clients/clients.ts index 55694ca..d42ed8b 100644 --- a/web/src/api/clients/clients.ts +++ b/web/src/api/clients/clients.ts @@ -5,9 +5,12 @@ export type Client = { email: string; given_name: string; family_name: string; + company: string | null; + status: ClientStatus; created_at: string; }; +export type ClientStatus = "active" | "suspended" | "closed"; export type ClientSort = "name" | "created_at"; export type SortOrder = "asc" | "desc"; diff --git a/web/src/routes/admin/clients.tsx b/web/src/routes/admin/clients.tsx index edc6b2b..8638c93 100644 --- a/web/src/routes/admin/clients.tsx +++ b/web/src/routes/admin/clients.tsx @@ -11,7 +11,7 @@ import { } from "@tanstack/react-table"; import { Permission } from "@/api/auth/permissions"; -import { CLIENTS_PER_PAGE, clientsQuery, type Client, type ClientSort, type SortOrder } from "@/api/clients/clients"; +import { CLIENTS_PER_PAGE, clientsQuery, type Client, type ClientSort, type ClientStatus, type SortOrder } from "@/api/clients/clients"; import { Add01Icon, ArrowLeft01Icon, ArrowRight01Icon, HugeiconsIcon } from "@/components/icons"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -43,6 +43,12 @@ export const Route = createFileRoute("/admin/clients")({ const date = new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "short", year: "numeric" }); +const STATUS_CLASS: Record = { + active: "bg-emerald-300/10 text-emerald-200", + suspended: "bg-amber-300/10 text-amber-200", + closed: "bg-rose-300/10 text-rose-200", +}; + const NO_CLIENTS: Client[] = []; const features = tableFeatures({ rowPaginationFeature, rowSortingFeature }); @@ -58,6 +64,18 @@ const columns = helper.columns([ ), }), + helper.accessor("company", { + header: "Company", + enableSorting: false, + cell: ({ getValue }) => getValue() ?? —, + }), + helper.accessor("status", { + header: "Status", + enableSorting: false, + cell: ({ getValue }) => ( + {getValue()} + ), + }), helper.accessor("created_at", { header: "Created", sortDescFirst: true,