mirror of
https://github.com/soconnor0919/beenvoice.git
synced 2026-02-05 08:16:31 -05:00
Begin dark mode!
This commit is contained in:
@@ -1,27 +1,32 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon, Search } from "lucide-react"
|
||||
import * as React from "react";
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
Search,
|
||||
} from "lucide-react";
|
||||
|
||||
import { cn } from "~/lib/utils"
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
function Select({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
||||
}
|
||||
|
||||
function SelectGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
||||
}
|
||||
|
||||
function SelectValue({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
||||
}
|
||||
|
||||
function SelectTrigger({
|
||||
@@ -30,15 +35,15 @@ function SelectTrigger({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||
size?: "sm" | "default"
|
||||
size?: "sm" | "default";
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
data-slot="select-trigger"
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"flex w-full items-center justify-between gap-2 rounded-md border border-gray-200 bg-gray-50 px-3 h-10 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-emerald-500 focus-visible:ring-emerald-500 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground",
|
||||
className
|
||||
"data-[placeholder]:text-muted-foreground flex h-10 w-full items-center justify-between gap-2 rounded-md border border-gray-200 bg-gray-50 px-3 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-emerald-500 focus-visible:ring-[3px] focus-visible:ring-emerald-500 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -47,7 +52,7 @@ function SelectTrigger({
|
||||
<ChevronDownIcon className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectContent({
|
||||
@@ -64,7 +69,7 @@ function SelectContent({
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
@@ -74,7 +79,7 @@ function SelectContent({
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
@@ -82,7 +87,7 @@ function SelectContent({
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectLabel({
|
||||
@@ -95,7 +100,7 @@ function SelectLabel({
|
||||
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectItem({
|
||||
@@ -108,7 +113,7 @@ function SelectItem({
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@@ -119,7 +124,7 @@ function SelectItem({
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectSeparator({
|
||||
@@ -132,7 +137,7 @@ function SelectSeparator({
|
||||
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectScrollUpButton({
|
||||
@@ -144,13 +149,13 @@ function SelectScrollUpButton({
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectScrollDownButton({
|
||||
@@ -162,13 +167,13 @@ function SelectScrollDownButton({
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Enhanced SelectContent with search functionality
|
||||
@@ -208,7 +213,7 @@ function SelectContentWithSearch({
|
||||
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-96 min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-hidden rounded-md border shadow-md",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
onEscapeKeyDown={(e) => {
|
||||
@@ -226,11 +231,11 @@ function SelectContentWithSearch({
|
||||
{...props}
|
||||
>
|
||||
{onSearchChange && (
|
||||
<div className="flex items-center px-3 py-2 border-b">
|
||||
<div className="border-border flex items-center border-b px-3 py-2">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
className="flex h-8 w-full rounded-md bg-transparent py-2 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 border-0 focus:ring-0 focus:outline-none"
|
||||
className="placeholder:text-muted-foreground text-foreground flex h-8 w-full rounded-md border-0 bg-transparent py-2 text-sm outline-none focus:ring-0 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
placeholder={searchPlaceholder}
|
||||
value={searchValue}
|
||||
onChange={(e) => onSearchChange(e.target.value)}
|
||||
@@ -240,7 +245,11 @@ function SelectContentWithSearch({
|
||||
e.stopPropagation();
|
||||
}
|
||||
// Prevent arrow keys from moving focus away from search
|
||||
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
|
||||
if (
|
||||
["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(
|
||||
e.key,
|
||||
)
|
||||
) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}}
|
||||
@@ -255,7 +264,9 @@ function SelectContentWithSearch({
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport className="p-1">
|
||||
{filteredOptions && filteredOptions.length === 0 ? (
|
||||
<div className="px-3 py-2 text-sm text-muted-foreground select-none">No results found</div>
|
||||
<div className="text-muted-foreground px-3 py-2 text-sm select-none">
|
||||
No results found
|
||||
</div>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
@@ -263,7 +274,7 @@ function SelectContentWithSearch({
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Searchable Select component
|
||||
@@ -284,21 +295,21 @@ function SearchableSelect({
|
||||
options,
|
||||
searchPlaceholder = "Search...",
|
||||
className,
|
||||
disabled
|
||||
disabled,
|
||||
}: SearchableSelectProps) {
|
||||
const [searchValue, setSearchValue] = React.useState("");
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
|
||||
|
||||
const filteredOptions = React.useMemo(() => {
|
||||
if (!searchValue) return options;
|
||||
return options.filter(option =>
|
||||
option.label.toLowerCase().includes(searchValue.toLowerCase())
|
||||
return options.filter((option) =>
|
||||
option.label.toLowerCase().includes(searchValue.toLowerCase()),
|
||||
);
|
||||
}, [options, searchValue]);
|
||||
|
||||
// Convert empty string to placeholder value for display
|
||||
const displayValue = value === "" ? "__placeholder__" : value;
|
||||
|
||||
|
||||
// Convert placeholder value back to empty string when selected
|
||||
const handleValueChange = (newValue: string) => {
|
||||
const actualValue = newValue === "__placeholder__" ? "" : newValue;
|
||||
@@ -309,9 +320,9 @@ function SearchableSelect({
|
||||
};
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={displayValue}
|
||||
onValueChange={handleValueChange}
|
||||
<Select
|
||||
value={displayValue}
|
||||
onValueChange={handleValueChange}
|
||||
disabled={disabled}
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
@@ -353,4 +364,4 @@ export {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
SearchableSelect,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { cn } from "~/lib/utils"
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
@@ -7,37 +7,43 @@ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
||||
className={cn("bg-muted animate-pulse rounded-md", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Dashboard skeleton components
|
||||
export function DashboardStatsSkeleton() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
|
||||
<div className="mb-8 grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<div key={i} className="shadow-xl border-0 bg-white/80 backdrop-blur-sm rounded-xl p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div
|
||||
key={i}
|
||||
className="rounded-xl border-0 bg-white/80 p-6 shadow-xl backdrop-blur-sm dark:bg-gray-800/80"
|
||||
>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<Skeleton className="h-4 w-24" />
|
||||
<Skeleton className="h-8 w-8 rounded-lg" />
|
||||
</div>
|
||||
<Skeleton className="h-8 w-16 mb-2" />
|
||||
<Skeleton className="mb-2 h-8 w-16" />
|
||||
<Skeleton className="h-3 w-32" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function DashboardCardsSkeleton() {
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 mb-8">
|
||||
<div className="mb-8 grid grid-cols-1 gap-8 lg:grid-cols-2">
|
||||
{Array.from({ length: 2 }).map((_, i) => (
|
||||
<div key={i} className="shadow-xl border-0 bg-white/80 backdrop-blur-sm rounded-xl p-6">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<div
|
||||
key={i}
|
||||
className="rounded-xl border-0 bg-white/80 p-6 shadow-xl backdrop-blur-sm dark:bg-gray-800/80"
|
||||
>
|
||||
<div className="mb-4 flex items-center gap-2">
|
||||
<Skeleton className="h-8 w-8 rounded-lg" />
|
||||
<Skeleton className="h-6 w-32" />
|
||||
</div>
|
||||
<Skeleton className="h-4 w-full mb-4" />
|
||||
<Skeleton className="mb-4 h-4 w-full" />
|
||||
<div className="flex gap-3">
|
||||
<Skeleton className="h-10 w-24" />
|
||||
<Skeleton className="h-10 w-32" />
|
||||
@@ -45,20 +51,20 @@ export function DashboardCardsSkeleton() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export function DashboardActivitySkeleton() {
|
||||
return (
|
||||
<div className="shadow-xl border-0 bg-white/80 backdrop-blur-sm rounded-xl p-6">
|
||||
<Skeleton className="h-6 w-32 mb-6" />
|
||||
<div className="text-center py-12">
|
||||
<Skeleton className="h-20 w-20 rounded-full mx-auto mb-4" />
|
||||
<Skeleton className="h-6 w-48 mx-auto mb-2" />
|
||||
<Skeleton className="h-4 w-64 mx-auto" />
|
||||
<div className="rounded-xl border-0 bg-white/80 p-6 shadow-xl backdrop-blur-sm dark:bg-gray-800/80">
|
||||
<Skeleton className="mb-6 h-6 w-32" />
|
||||
<div className="py-12 text-center">
|
||||
<Skeleton className="mx-auto mb-4 h-20 w-20 rounded-full" />
|
||||
<Skeleton className="mx-auto mb-2 h-6 w-48" />
|
||||
<Skeleton className="mx-auto h-4 w-64" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Table skeleton components
|
||||
@@ -66,17 +72,17 @@ export function TableSkeleton({ rows = 5 }: { rows?: number }) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Search and filters */}
|
||||
<div className="flex flex-col sm:flex-row gap-4">
|
||||
<div className="flex flex-col gap-4 sm:flex-row">
|
||||
<Skeleton className="h-10 w-64" />
|
||||
<div className="flex gap-2">
|
||||
<Skeleton className="h-10 w-24" />
|
||||
<Skeleton className="h-10 w-24" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Table */}
|
||||
<div className="border rounded-lg">
|
||||
<div className="p-4 border-b">
|
||||
<div className="rounded-lg border">
|
||||
<div className="border-b p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Skeleton className="h-4 w-32" />
|
||||
<div className="flex gap-2">
|
||||
@@ -85,7 +91,7 @@ export function TableSkeleton({ rows = 5 }: { rows?: number }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="p-4">
|
||||
<div className="space-y-3">
|
||||
{Array.from({ length: rows }).map((_, i) => (
|
||||
@@ -101,7 +107,7 @@ export function TableSkeleton({ rows = 5 }: { rows?: number }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Pagination */}
|
||||
<div className="flex items-center justify-between">
|
||||
<Skeleton className="h-4 w-32" />
|
||||
@@ -112,7 +118,7 @@ export function TableSkeleton({ rows = 5 }: { rows?: number }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Form skeleton components
|
||||
@@ -121,36 +127,36 @@ export function FormSkeleton() {
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Skeleton className="h-4 w-20 mb-2" />
|
||||
<Skeleton className="mb-2 h-4 w-20" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<Skeleton className="h-4 w-24 mb-2" />
|
||||
<Skeleton className="mb-2 h-4 w-24" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<Skeleton className="h-4 w-16 mb-2" />
|
||||
<Skeleton className="mb-2 h-4 w-16" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div>
|
||||
<Skeleton className="h-4 w-20 mb-2" />
|
||||
<Skeleton className="mb-2 h-4 w-20" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<Skeleton className="h-4 w-16 mb-2" />
|
||||
<Skeleton className="mb-2 h-4 w-16" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Skeleton className="h-10 w-24" />
|
||||
<Skeleton className="h-10 w-24" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Invoice view skeleton
|
||||
@@ -158,16 +164,16 @@ export function InvoiceViewSkeleton() {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-start">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
<Skeleton className="h-4 w-64" />
|
||||
</div>
|
||||
<Skeleton className="h-10 w-32" />
|
||||
</div>
|
||||
|
||||
|
||||
{/* Client info */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
<div className="space-y-3">
|
||||
<Skeleton className="h-5 w-24" />
|
||||
<Skeleton className="h-4 w-full" />
|
||||
@@ -180,10 +186,10 @@ export function InvoiceViewSkeleton() {
|
||||
<Skeleton className="h-4 w-3/4" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Items table */}
|
||||
<div className="border rounded-lg">
|
||||
<div className="p-4 border-b">
|
||||
<div className="rounded-lg border">
|
||||
<div className="border-b p-4">
|
||||
<Skeleton className="h-5 w-32" />
|
||||
</div>
|
||||
<div className="p-4">
|
||||
@@ -200,7 +206,7 @@ export function InvoiceViewSkeleton() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* Total */}
|
||||
<div className="flex justify-end">
|
||||
<div className="space-y-2">
|
||||
@@ -209,7 +215,7 @@ export function InvoiceViewSkeleton() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
export { Skeleton };
|
||||
|
||||
@@ -104,10 +104,10 @@ interface Business {
|
||||
}
|
||||
|
||||
const statusColors = {
|
||||
draft: "bg-gray-100 text-gray-800",
|
||||
sent: "bg-blue-100 text-blue-800",
|
||||
paid: "bg-green-100 text-green-800",
|
||||
overdue: "bg-red-100 text-red-800",
|
||||
draft: "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300",
|
||||
sent: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400",
|
||||
paid: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",
|
||||
overdue: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400",
|
||||
} as const;
|
||||
|
||||
const statusLabels = {
|
||||
@@ -503,7 +503,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50"
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
onClick={() => handleSort("name")}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -515,10 +515,10 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
)}
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700">
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700 dark:text-gray-300">
|
||||
Email
|
||||
</TableHead>
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700">
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700 dark:text-gray-300">
|
||||
Phone
|
||||
</TableHead>
|
||||
<TableHead className="w-8 px-4 py-4"></TableHead>
|
||||
@@ -536,7 +536,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50"
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
onClick={() => handleSort("invoiceNumber")}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -549,7 +549,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50"
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
onClick={() => handleSort("client.name")}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -562,7 +562,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50"
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
onClick={() => handleSort("status")}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -575,7 +575,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50"
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
onClick={() => handleSort("totalAmount")}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -588,7 +588,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50"
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
onClick={() => handleSort("dueDate")}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -615,21 +615,21 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50"
|
||||
className="cursor-pointer px-4 py-4 text-base font-semibold text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
onClick={() => handleSort("name")}
|
||||
>
|
||||
Name
|
||||
</TableHead>
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700">
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700 dark:text-gray-300">
|
||||
Email
|
||||
</TableHead>
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700">
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700 dark:text-gray-300">
|
||||
Phone
|
||||
</TableHead>
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700">
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700 dark:text-gray-300">
|
||||
Website
|
||||
</TableHead>
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700">
|
||||
<TableHead className="px-4 py-4 text-base font-semibold text-gray-700 dark:text-gray-300">
|
||||
Default
|
||||
</TableHead>
|
||||
<TableHead className="w-8 px-4 py-4"></TableHead>
|
||||
@@ -691,7 +691,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={colSpan}
|
||||
className="py-12 text-center text-gray-500"
|
||||
className="py-12 text-center text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
{resource === "clients" ? (
|
||||
@@ -701,8 +701,10 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
) : (
|
||||
<FileText className="mb-2 h-8 w-8 text-emerald-400" />
|
||||
)}
|
||||
<div className="text-lg font-semibold">No {resource} found</div>
|
||||
<div className="mb-2 text-gray-500">
|
||||
<div className="text-lg font-semibold dark:text-gray-300">
|
||||
No {resource} found
|
||||
</div>
|
||||
<div className="mb-2 text-gray-500 dark:text-gray-400">
|
||||
Get started by adding your first{" "}
|
||||
{getSingularResourceName(resource).toLowerCase()}.
|
||||
</div>
|
||||
@@ -728,7 +730,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
<TableRow
|
||||
key={client.id}
|
||||
data-selected={selected.includes(client.id)}
|
||||
className="group cursor-pointer transition-colors hover:bg-emerald-50/60"
|
||||
className="group cursor-pointer transition-colors hover:bg-emerald-50/60 dark:hover:bg-emerald-900/20"
|
||||
onClick={(e) => {
|
||||
if (
|
||||
(e.target as HTMLElement).closest(
|
||||
@@ -750,7 +752,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
className="data-[state=checked]:border-emerald-600 data-[state=checked]:bg-emerald-600"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 text-base font-medium text-gray-900 group-hover:text-emerald-700">
|
||||
<TableCell className="px-4 py-4 text-base font-medium text-gray-900 group-hover:text-emerald-700 dark:text-white dark:group-hover:text-emerald-400">
|
||||
<Link
|
||||
href={`/dashboard/clients/${client.id}/edit`}
|
||||
className="hover:underline"
|
||||
@@ -758,10 +760,10 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
{client.name}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 text-gray-700">
|
||||
<TableCell className="px-4 py-4 text-gray-700 dark:text-gray-300">
|
||||
{client.email}
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 text-gray-700">
|
||||
<TableCell className="px-4 py-4 text-gray-700 dark:text-gray-300">
|
||||
{client.phone}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
@@ -797,7 +799,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
<TableRow
|
||||
key={invoice.id}
|
||||
data-selected={selected.includes(invoice.id)}
|
||||
className="group cursor-pointer transition-colors hover:bg-emerald-50/60"
|
||||
className="group cursor-pointer transition-colors hover:bg-emerald-50/60 dark:hover:bg-emerald-900/20"
|
||||
onClick={(e) => {
|
||||
if (
|
||||
(e.target as HTMLElement).closest(
|
||||
@@ -819,7 +821,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
className="data-[state=checked]:border-emerald-600 data-[state=checked]:bg-emerald-600"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 text-base font-medium text-gray-900 group-hover:text-emerald-700">
|
||||
<TableCell className="px-4 py-4 text-base font-medium text-gray-900 group-hover:text-emerald-700 dark:text-white dark:group-hover:text-emerald-400">
|
||||
<Link
|
||||
href={`/dashboard/invoices/${invoice.id}`}
|
||||
className="hover:underline"
|
||||
@@ -827,7 +829,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
{invoice.invoiceNumber}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 text-gray-700">
|
||||
<TableCell className="px-4 py-4 text-gray-700 dark:text-gray-300">
|
||||
{invoice.client?.name}
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4">
|
||||
@@ -837,10 +839,10 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
{statusLabels[invoice.status]}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 font-medium text-gray-700">
|
||||
<TableCell className="px-4 py-4 font-medium text-gray-700 dark:text-gray-300">
|
||||
{formatCurrency(invoice.totalAmount)}
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 text-gray-700">
|
||||
<TableCell className="px-4 py-4 text-gray-700 dark:text-gray-300">
|
||||
{formatDate(invoice.dueDate)}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
@@ -885,7 +887,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
<TableRow
|
||||
key={business.id}
|
||||
data-selected={selected.includes(business.id)}
|
||||
className="group cursor-pointer transition-colors hover:bg-emerald-50/60"
|
||||
className="group cursor-pointer transition-colors hover:bg-emerald-50/60 dark:hover:bg-emerald-900/20"
|
||||
onClick={(e) => {
|
||||
if (
|
||||
(e.target as HTMLElement).closest(
|
||||
@@ -907,7 +909,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
className="data-[state=checked]:border-emerald-600 data-[state=checked]:bg-emerald-600"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 text-base font-medium text-gray-900 group-hover:text-emerald-700">
|
||||
<TableCell className="px-4 py-4 text-base font-medium text-gray-900 group-hover:text-emerald-700 dark:text-white dark:group-hover:text-emerald-400">
|
||||
<Link
|
||||
href={`/dashboard/businesses/${business.id}/edit`}
|
||||
className="hover:underline"
|
||||
@@ -915,18 +917,18 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
{business.name}
|
||||
</Link>
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 text-gray-700">
|
||||
<TableCell className="px-4 py-4 text-gray-700 dark:text-gray-300">
|
||||
{business.email}
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 text-gray-700">
|
||||
<TableCell className="px-4 py-4 text-gray-700 dark:text-gray-300">
|
||||
{business.phone}
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 text-gray-700">
|
||||
<TableCell className="px-4 py-4 text-gray-700 dark:text-gray-300">
|
||||
{business.website}
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-4 text-gray-700">
|
||||
<TableCell className="px-4 py-4 text-gray-700 dark:text-gray-300">
|
||||
{business.isDefault ? (
|
||||
<span className="rounded-full bg-emerald-100 px-2 py-1 text-xs font-medium text-emerald-800">
|
||||
<span className="rounded-full bg-emerald-100 px-2 py-1 text-xs font-medium text-emerald-800 dark:bg-emerald-900/30 dark:text-emerald-400">
|
||||
Default
|
||||
</span>
|
||||
) : (
|
||||
@@ -968,7 +970,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
{/* Controls */}
|
||||
<div className="mb-4 flex flex-wrap items-center gap-3 rounded-lg border border-gray-200 bg-white/90 p-4 shadow-sm">
|
||||
<div className="mb-4 flex flex-wrap items-center gap-3 rounded-lg border border-gray-200 bg-white/90 p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800/90">
|
||||
{/* Left side - View controls and filters */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
@@ -996,7 +998,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-48">
|
||||
<DropdownMenuItem className="font-medium text-gray-700">
|
||||
<DropdownMenuItem className="font-medium text-gray-700 dark:text-gray-300">
|
||||
Filters
|
||||
</DropdownMenuItem>
|
||||
{resource === "invoices" && (
|
||||
@@ -1005,7 +1007,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
onClick={() => setStatusFilter("all")}
|
||||
className={
|
||||
statusFilter === "all"
|
||||
? "bg-emerald-50 text-emerald-700"
|
||||
? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
@@ -1015,7 +1017,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
onClick={() => setStatusFilter("draft")}
|
||||
className={
|
||||
statusFilter === "draft"
|
||||
? "bg-emerald-50 text-emerald-700"
|
||||
? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
@@ -1025,7 +1027,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
onClick={() => setStatusFilter("sent")}
|
||||
className={
|
||||
statusFilter === "sent"
|
||||
? "bg-emerald-50 text-emerald-700"
|
||||
? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
@@ -1035,7 +1037,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
onClick={() => setStatusFilter("paid")}
|
||||
className={
|
||||
statusFilter === "paid"
|
||||
? "bg-emerald-50 text-emerald-700"
|
||||
? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
@@ -1045,7 +1047,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
onClick={() => setStatusFilter("overdue")}
|
||||
className={
|
||||
statusFilter === "overdue"
|
||||
? "bg-emerald-50 text-emerald-700"
|
||||
? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
@@ -1065,7 +1067,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
placeholder={`Search ${resource}...`}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-48 sm:w-64"
|
||||
className="w-48 sm:w-64 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
/>
|
||||
<Button variant="outline" size="icon">
|
||||
<Search className="h-4 w-4" />
|
||||
@@ -1075,7 +1077,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
{/* Batch actions */}
|
||||
{selected.length > 0 && (
|
||||
<>
|
||||
<span className="hidden text-sm text-gray-500 sm:inline">
|
||||
<span className="hidden text-sm text-gray-500 sm:inline dark:text-gray-400">
|
||||
{selected.length} selected
|
||||
</span>
|
||||
{resource === "invoices" && (
|
||||
@@ -1124,7 +1126,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
</div>
|
||||
{/* Table View */}
|
||||
{view === "table" && (
|
||||
<div className="overflow-hidden rounded-2xl border border-gray-200 bg-white/90 shadow-xl">
|
||||
<div className="overflow-hidden rounded-2xl border border-gray-200 bg-white/90 shadow-xl dark:border-gray-700 dark:bg-gray-800/90">
|
||||
<Table className="w-full">
|
||||
<TableHeader>
|
||||
<TableRow>{renderTableHeaders()}</TableRow>
|
||||
@@ -1135,9 +1137,9 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
)}
|
||||
{/* Pagination Controls */}
|
||||
{view === "table" && totalPages > 1 && (
|
||||
<div className="mt-4 mb-4 flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-gray-200 bg-white/90 p-4 shadow-sm">
|
||||
<div className="mt-4 mb-4 flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-gray-200 bg-white/90 p-4 shadow-sm dark:border-gray-700 dark:bg-gray-800/90">
|
||||
{/* Left side - Page info and items per page */}
|
||||
<div className="flex items-center gap-3 text-sm text-gray-600">
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span className="hidden sm:inline">
|
||||
Showing {startIndex + 1} to{" "}
|
||||
{Math.min(endIndex, filteredAndSortedData.length)} of{" "}
|
||||
@@ -1154,7 +1156,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
setItemsPerPage(Number(e.target.value));
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="h-8 w-20 rounded-md border border-gray-300 bg-white px-2 py-1 text-sm focus:border-emerald-500 focus:ring-emerald-500 sm:w-28"
|
||||
className="h-8 w-20 rounded-md border border-gray-300 bg-white px-2 py-1 text-sm focus:border-emerald-500 focus:ring-emerald-500 sm:w-28 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
|
||||
>
|
||||
<option value={5}>5</option>
|
||||
<option value={10}>10</option>
|
||||
@@ -1220,7 +1222,10 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
pageNum === currentPage + 2
|
||||
) {
|
||||
return (
|
||||
<span key={pageNum} className="px-1 text-gray-400 sm:px-2">
|
||||
<span
|
||||
key={pageNum}
|
||||
className="px-1 text-gray-400 sm:px-2 dark:text-gray-500"
|
||||
>
|
||||
...
|
||||
</span>
|
||||
);
|
||||
@@ -1251,7 +1256,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
Array.from({ length: 6 }).map((_, index) => (
|
||||
<div
|
||||
key={`skeleton-card-${index}`}
|
||||
className="flex flex-col gap-2 rounded-2xl border border-gray-200 bg-white/90 p-4 shadow-xl"
|
||||
className="flex flex-col gap-2 rounded-2xl border border-gray-200 bg-white/90 p-4 shadow-xl dark:border-gray-700 dark:bg-gray-800/90"
|
||||
>
|
||||
<Skeleton className="h-6 w-32" />
|
||||
<Skeleton className="h-4 w-40" />
|
||||
@@ -1259,7 +1264,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
</div>
|
||||
))
|
||||
) : filteredAndSortedData.length === 0 ? (
|
||||
<div className="col-span-full flex flex-col items-center py-16 text-gray-500">
|
||||
<div className="col-span-full flex flex-col items-center py-16 text-gray-500 dark:text-gray-400">
|
||||
{resource === "clients" ? (
|
||||
<UserPlus className="mb-2 h-8 w-8 text-emerald-400" />
|
||||
) : resource === "businesses" ? (
|
||||
@@ -1267,8 +1272,10 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
) : (
|
||||
<FileText className="mb-2 h-8 w-8 text-emerald-400" />
|
||||
)}
|
||||
<div className="text-lg font-semibold">No {resource} found</div>
|
||||
<div className="mb-2 text-gray-500">
|
||||
<div className="text-lg font-semibold dark:text-gray-300">
|
||||
No {resource} found
|
||||
</div>
|
||||
<div className="mb-2 text-gray-500 dark:text-gray-400">
|
||||
Get started by adding your first{" "}
|
||||
{getSingularResourceName(resource).toLowerCase()}.
|
||||
</div>
|
||||
@@ -1289,13 +1296,17 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
return (
|
||||
<div
|
||||
key={client.id}
|
||||
className="flex cursor-pointer flex-col gap-2 rounded-2xl border border-gray-200 bg-white/90 p-4 shadow-xl transition-colors hover:bg-emerald-50/60"
|
||||
className="flex cursor-pointer flex-col gap-2 rounded-2xl border border-gray-200 bg-white/90 p-4 shadow-xl transition-colors hover:bg-emerald-50/60 dark:border-gray-700 dark:bg-gray-800/90 dark:hover:bg-emerald-900/20"
|
||||
>
|
||||
<div className="text-lg font-semibold text-gray-900 group-hover:text-emerald-700">
|
||||
<div className="text-lg font-semibold text-gray-900 group-hover:text-emerald-700 dark:text-white dark:group-hover:text-emerald-400">
|
||||
{client.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-700">{client.email}</div>
|
||||
<div className="text-sm text-gray-700">{client.phone}</div>
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||
{client.email}
|
||||
</div>
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||
{client.phone}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else if (resource === "invoices") {
|
||||
@@ -1303,15 +1314,15 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
return (
|
||||
<div
|
||||
key={invoice.id}
|
||||
className="flex cursor-pointer flex-col gap-2 rounded-2xl border border-gray-200 bg-white/90 p-4 shadow-xl transition-colors hover:bg-emerald-50/60"
|
||||
className="flex cursor-pointer flex-col gap-2 rounded-2xl border border-gray-200 bg-white/90 p-4 shadow-xl transition-colors hover:bg-emerald-50/60 dark:border-gray-700 dark:bg-gray-800/90 dark:hover:bg-emerald-900/20"
|
||||
>
|
||||
<div className="text-lg font-semibold text-gray-900 group-hover:text-emerald-700">
|
||||
<div className="text-lg font-semibold text-gray-900 group-hover:text-emerald-700 dark:text-white dark:group-hover:text-emerald-400">
|
||||
{invoice.invoiceNumber}
|
||||
</div>
|
||||
<div className="text-sm text-gray-700">
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||
{invoice.client?.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-700">
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||
{formatCurrency(invoice.totalAmount)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1321,15 +1332,15 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
return (
|
||||
<div
|
||||
key={business.id}
|
||||
className="flex cursor-pointer flex-col gap-2 rounded-2xl border border-gray-200 bg-white/90 p-4 shadow-xl transition-colors hover:bg-emerald-50/60"
|
||||
className="flex cursor-pointer flex-col gap-2 rounded-2xl border border-gray-200 bg-white/90 p-4 shadow-xl transition-colors hover:bg-emerald-50/60 dark:border-gray-700 dark:bg-gray-800/90 dark:hover:bg-emerald-900/20"
|
||||
>
|
||||
<div className="text-lg font-semibold text-gray-900 group-hover:text-emerald-700">
|
||||
<div className="text-lg font-semibold text-gray-900 group-hover:text-emerald-700 dark:text-white dark:group-hover:text-emerald-400">
|
||||
{business.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-700">
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||
{business.email}
|
||||
</div>
|
||||
<div className="text-sm text-gray-700">
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||
{business.phone}
|
||||
</div>
|
||||
</div>
|
||||
@@ -1341,15 +1352,15 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
)}
|
||||
{/* Confirmation Dialog */}
|
||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||
<DialogContent className="border-0 bg-white/95 shadow-2xl backdrop-blur-sm">
|
||||
<DialogContent className="border-0 bg-white/95 shadow-2xl backdrop-blur-sm dark:bg-gray-800/95">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-xl font-bold text-gray-800">
|
||||
<DialogTitle className="text-xl font-bold text-gray-800 dark:text-white">
|
||||
Delete{" "}
|
||||
{resource.slice(0, -1).charAt(0).toUpperCase() +
|
||||
resource.slice(0, -1).slice(1)}
|
||||
{itemToDelete === "batch" ? "s" : ""}
|
||||
</DialogTitle>
|
||||
<DialogDescription className="text-gray-600">
|
||||
<DialogDescription className="text-gray-600 dark:text-gray-300">
|
||||
{itemToDelete === "batch"
|
||||
? `Are you sure you want to delete the selected ${resource}? This action cannot be undone.`
|
||||
: `Are you sure you want to delete this ${resource.slice(0, -1)}? This action cannot be undone.`}
|
||||
@@ -1359,7 +1370,7 @@ export function UniversalTable({ resource }: UniversalTableProps) {
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setDeleteDialogOpen(false)}
|
||||
className="border-gray-300 text-gray-700 hover:bg-gray-50"
|
||||
className="border-gray-300 text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-300 dark:hover:bg-gray-800"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user