chore: update generated files to orval v8.3.0 and adjust related interfaces

This commit is contained in:
Abhimanyu Saharan
2026-02-12 18:04:35 +05:30
parent c73103d5c9
commit 2ebdead95b
232 changed files with 9434 additions and 6021 deletions

View File

@@ -11,6 +11,7 @@ interface TaskCardProps {
assignee?: string;
due?: string;
approvalsPendingCount?: number;
tags?: Array<{ id: string; name: string; color: string }>;
isBlocked?: boolean;
blockedByCount?: number;
onClick?: () => void;
@@ -27,6 +28,7 @@ export function TaskCard({
assignee,
due,
approvalsPendingCount = 0,
tags = [],
isBlocked = false,
blockedByCount = 0,
onClick,
@@ -61,6 +63,7 @@ export function TaskCard({
};
const priorityLabel = priority ? priority.toUpperCase() : "MEDIUM";
const visibleTags = tags.slice(0, 3);
return (
<div
@@ -115,6 +118,27 @@ export function TaskCard({
Waiting for lead review
</div>
) : null}
{visibleTags.length ? (
<div className="flex flex-wrap items-center gap-1.5">
{visibleTags.map((tag) => (
<span
key={tag.id}
className="inline-flex items-center gap-1 rounded-full border border-slate-200 bg-white px-2 py-0.5 text-[10px] font-semibold text-slate-700"
>
<span
className="h-1.5 w-1.5 rounded-full"
style={{ backgroundColor: `#${tag.color}` }}
/>
{tag.name}
</span>
))}
{tags.length > visibleTags.length ? (
<span className="text-[10px] font-semibold text-slate-500">
+{tags.length - visibleTags.length}
</span>
) : null}
</div>
) : null}
</div>
<div className="flex flex-shrink-0 flex-col items-end gap-2">
<span

View File

@@ -11,6 +11,7 @@ import {
Building2,
LayoutGrid,
Network,
Tags,
} from "lucide-react";
import { useAuth } from "@/auth/clerk";
@@ -110,6 +111,18 @@ export function DashboardSidebar() {
<LayoutGrid className="h-4 w-4" />
Boards
</Link>
<Link
href="/tags"
className={cn(
"flex items-center gap-3 rounded-lg px-3 py-2.5 text-slate-700 transition",
pathname.startsWith("/tags")
? "bg-blue-100 text-blue-800 font-medium"
: "hover:bg-slate-100",
)}
>
<Tags className="h-4 w-4" />
Tags
</Link>
<Link
href="/organization"
className={cn(

View File

@@ -25,6 +25,7 @@ type Task = {
assigned_agent_id?: string | null;
assignee?: string | null;
approvals_pending_count?: number;
tags?: Array<{ id: string; name: string; slug: string; color: string }>;
depends_on_task_ids?: string[];
blocked_by_task_ids?: string[];
is_blocked?: boolean;
@@ -453,6 +454,7 @@ export const TaskBoard = memo(function TaskBoard({
assignee={task.assignee ?? undefined}
due={formatDueDate(task.due_at)}
approvalsPendingCount={task.approvals_pending_count}
tags={task.tags}
isBlocked={task.is_blocked}
blockedByCount={task.blocked_by_task_ids?.length ?? 0}
onClick={() => onTaskSelect?.(task)}

View File

@@ -0,0 +1,214 @@
import { useMemo, useState } from "react";
import { ApiError } from "@/api/mutator";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
export type TaskTagFormValues = {
name: string;
slug: string;
color: string;
description: string;
};
type TaskTagFormProps = {
initialValues?: TaskTagFormValues;
onSubmit: (values: {
name: string;
slug: string;
color: string;
description: string | null;
}) => Promise<void>;
onCancel: () => void;
submitLabel: string;
submittingLabel: string;
isSubmitting: boolean;
};
const DEFAULT_VALUES: TaskTagFormValues = {
name: "",
slug: "",
color: "9e9e9e",
description: "",
};
const normalizeColorInput = (value: string) => {
const cleaned = value.trim().replace(/^#/, "").toLowerCase();
return /^[0-9a-f]{6}$/.test(cleaned) ? cleaned : "9e9e9e";
};
const slugify = (value: string) =>
value
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
const extractErrorMessage = (error: unknown, fallback: string) => {
if (error instanceof ApiError) return error.message || fallback;
if (error instanceof Error) return error.message || fallback;
return fallback;
};
export function TaskTagForm({
initialValues,
onSubmit,
onCancel,
submitLabel,
submittingLabel,
isSubmitting,
}: TaskTagFormProps) {
const resolvedInitial = initialValues ?? DEFAULT_VALUES;
const [name, setName] = useState(() => resolvedInitial.name);
const [slug, setSlug] = useState(() => resolvedInitial.slug);
const [color, setColor] = useState(() =>
normalizeColorInput(resolvedInitial.color),
);
const [description, setDescription] = useState(
() => resolvedInitial.description,
);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const previewColor = useMemo(() => normalizeColorInput(color), [color]);
const suggestedSlug = useMemo(() => slugify(name.trim()), [name]);
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const normalizedName = name.trim();
if (!normalizedName) {
setErrorMessage("Tag name is required.");
return;
}
const normalizedSlug = slugify(slug.trim() || normalizedName);
if (!normalizedSlug) {
setErrorMessage("Tag slug is required.");
return;
}
setErrorMessage(null);
try {
await onSubmit({
name: normalizedName,
slug: normalizedSlug,
color: normalizeColorInput(color),
description: description.trim() || null,
});
} catch (error) {
setErrorMessage(extractErrorMessage(error, "Unable to save tag."));
}
};
return (
<form
onSubmit={handleSubmit}
className="space-y-6 rounded-xl border border-slate-200 bg-white p-6 shadow-sm"
>
<div className="space-y-5">
<div className="rounded-xl border border-slate-200 bg-slate-50/40 p-4">
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<label className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Name
</label>
<Input
value={name}
onChange={(event) => setName(event.target.value)}
placeholder="e.g. Backend"
disabled={isSubmitting}
/>
</div>
<div className="space-y-2">
<div className="flex items-center justify-between gap-2">
<label className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Slug
</label>
<button
type="button"
onClick={() => setSlug(suggestedSlug)}
className="text-xs font-medium text-slate-500 underline underline-offset-2 transition hover:text-slate-700 disabled:cursor-not-allowed disabled:opacity-50"
disabled={!suggestedSlug || isSubmitting}
>
Use from name
</button>
</div>
<Input
value={slug}
onChange={(event) => setSlug(event.target.value)}
placeholder="backend"
disabled={isSubmitting}
/>
</div>
</div>
<p className="mt-2 text-xs text-slate-500">
Leave slug blank to auto-generate from the tag name.
</p>
</div>
<div className="grid gap-4 md:grid-cols-[1fr_auto]">
<div className="space-y-2">
<label className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Color
</label>
<div className="flex items-center rounded-lg border border-slate-200 bg-white px-3">
<span className="text-sm font-medium text-slate-400">#</span>
<Input
value={color}
onChange={(event) => setColor(event.target.value)}
placeholder="9e9e9e"
disabled={isSubmitting}
className="border-0 px-2 shadow-none focus-visible:ring-0"
/>
</div>
</div>
<div className="space-y-2">
<label className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Preview
</label>
<div className="inline-flex h-[42px] items-center gap-2 rounded-lg border border-slate-200 bg-white px-3">
<span
className="h-4 w-4 rounded border border-slate-300"
style={{ backgroundColor: `#${previewColor}` }}
/>
<span className="text-xs font-semibold text-slate-700">
#{previewColor.toUpperCase()}
</span>
</div>
</div>
</div>
<div className="space-y-2">
<label className="text-xs font-semibold uppercase tracking-wider text-slate-500">
Description
</label>
<Textarea
value={description}
onChange={(event) => setDescription(event.target.value)}
placeholder="Optional description"
className="min-h-[110px]"
disabled={isSubmitting}
/>
</div>
{errorMessage ? (
<div className="rounded-lg border border-rose-200 bg-rose-50 p-3 text-sm text-rose-700">
{errorMessage}
</div>
) : null}
</div>
<div className="flex justify-end gap-3">
<Button
type="button"
variant="outline"
onClick={onCancel}
disabled={isSubmitting}
>
Cancel
</Button>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? submittingLabel : submitLabel}
</Button>
</div>
</form>
);
}

View File

@@ -0,0 +1,181 @@
import { useMemo, useState } from "react";
import {
type ColumnDef,
type OnChangeFn,
type SortingState,
type Updater,
getCoreRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import { type TaskTagRead } from "@/api/generated/model";
import {
DataTable,
type DataTableEmptyState,
} from "@/components/tables/DataTable";
import { dateCell } from "@/components/tables/cell-formatters";
type TaskTagsTableProps = {
tags: TaskTagRead[];
isLoading?: boolean;
sorting?: SortingState;
onSortingChange?: OnChangeFn<SortingState>;
stickyHeader?: boolean;
onEdit?: (tag: TaskTagRead) => void;
onDelete?: (tag: TaskTagRead) => void;
emptyState?: Omit<DataTableEmptyState, "icon"> & {
icon?: DataTableEmptyState["icon"];
};
};
const DEFAULT_EMPTY_ICON = (
<svg
className="h-16 w-16 text-slate-300"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M4 6h16" />
<path d="M4 12h16" />
<path d="M4 18h10" />
</svg>
);
const normalizeColor = (value?: string | null) => {
const cleaned = (value ?? "").trim().replace(/^#/, "").toLowerCase();
if (!/^[0-9a-f]{6}$/.test(cleaned)) return "9e9e9e";
return cleaned;
};
export function TaskTagsTable({
tags,
isLoading = false,
sorting,
onSortingChange,
stickyHeader = false,
onEdit,
onDelete,
emptyState,
}: TaskTagsTableProps) {
const [internalSorting, setInternalSorting] = useState<SortingState>([
{ id: "name", desc: false },
]);
const resolvedSorting = sorting ?? internalSorting;
const handleSortingChange: OnChangeFn<SortingState> =
onSortingChange ??
((updater: Updater<SortingState>) => {
setInternalSorting(updater);
});
const columns = useMemo<ColumnDef<TaskTagRead>[]>(
() => [
{
accessorKey: "name",
header: "Tag",
cell: ({ row }) => {
const color = normalizeColor(row.original.color);
return (
<div className="space-y-1">
<div className="inline-flex items-center gap-2 rounded-full border border-slate-200 bg-white px-2.5 py-1 text-xs font-semibold text-slate-800">
<span
className="h-2 w-2 rounded-full"
style={{ backgroundColor: `#${color}` }}
/>
{row.original.name}
</div>
<p className="text-xs text-slate-500">
{row.original.slug}
{row.original.description
? ` · ${row.original.description}`
: ""}
</p>
</div>
);
},
},
{
accessorKey: "color",
header: "Color",
cell: ({ row }) => {
const color = normalizeColor(row.original.color);
return (
<div className="inline-flex items-center gap-2 text-xs text-slate-700">
<span
className="h-4 w-4 rounded border border-slate-300"
style={{ backgroundColor: `#${color}` }}
/>
#{color.toUpperCase()}
</div>
);
},
},
{
accessorKey: "task_count",
header: "Tasks",
cell: ({ row }) => (
<span className="text-sm font-medium text-slate-700">
{row.original.task_count ?? 0}
</span>
),
},
{
accessorKey: "updated_at",
header: "Updated",
cell: ({ row }) => dateCell(row.original.updated_at),
},
],
[],
);
// eslint-disable-next-line react-hooks/incompatible-library
const table = useReactTable({
data: tags,
columns,
state: {
sorting: resolvedSorting,
},
onSortingChange: handleSortingChange,
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
});
return (
<DataTable
table={table}
isLoading={isLoading}
stickyHeader={stickyHeader}
rowClassName="transition hover:bg-slate-50"
cellClassName="px-6 py-4 align-top"
rowActions={
onEdit || onDelete
? {
actions: [
...(onEdit
? [{ key: "edit", label: "Edit", onClick: onEdit }]
: []),
...(onDelete
? [{ key: "delete", label: "Delete", onClick: onDelete }]
: []),
],
}
: undefined
}
emptyState={
emptyState
? {
icon: emptyState.icon ?? DEFAULT_EMPTY_ICON,
title: emptyState.title,
description: emptyState.description,
actionHref: emptyState.actionHref,
actionLabel: emptyState.actionLabel,
}
: undefined
}
/>
);
}