-
Notifications
You must be signed in to change notification settings - Fork 596
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
682 additions
and
3 deletions.
There are no files selected for viewing
44 changes: 44 additions & 0 deletions
44
apps/dashboard/src/app/[locale]/(app)/(sidebar)/customers/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { CustomersHeader } from "@/components/customers-header"; | ||
import { ErrorFallback } from "@/components/error-fallback"; | ||
import { CustomersTable } from "@/components/tables/customers"; | ||
import { CustomersSkeleton } from "@/components/tables/customers/skeleton"; | ||
import type { Metadata } from "next"; | ||
import { ErrorBoundary } from "next/dist/client/components/error-boundary"; | ||
import { Suspense } from "react"; | ||
import { searchParamsCache } from "./search-params"; | ||
|
||
export const metadata: Metadata = { | ||
title: "Customers | Midday", | ||
}; | ||
|
||
export default async function Page({ | ||
searchParams, | ||
}: { | ||
searchParams: Record<string, string | string[] | undefined>; | ||
}) { | ||
const { | ||
q: query, | ||
sort, | ||
start, | ||
end, | ||
page, | ||
} = searchParamsCache.parse(searchParams); | ||
|
||
return ( | ||
<div className="flex flex-col pt-6 gap-6"> | ||
<CustomersHeader /> | ||
|
||
<ErrorBoundary errorComponent={ErrorFallback}> | ||
<Suspense fallback={<CustomersSkeleton />}> | ||
<CustomersTable | ||
query={query} | ||
sort={sort} | ||
start={start} | ||
end={end} | ||
page={page} | ||
/> | ||
</Suspense> | ||
</ErrorBoundary> | ||
</div> | ||
); | ||
} |
14 changes: 14 additions & 0 deletions
14
apps/dashboard/src/app/[locale]/(app)/(sidebar)/customers/search-params.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { | ||
createSearchParamsCache, | ||
parseAsArrayOf, | ||
parseAsInteger, | ||
parseAsString, | ||
} from "nuqs/server"; | ||
|
||
export const searchParamsCache = createSearchParamsCache({ | ||
page: parseAsInteger.withDefault(0), | ||
q: parseAsString.withDefault(""), | ||
sort: parseAsArrayOf(parseAsString), | ||
start: parseAsString, | ||
end: parseAsString, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { InvoiceSearchFilter } from "@/components/invoice-search-filter"; | ||
import { getCustomers } from "@midday/supabase/cached-queries"; | ||
import { OpenInvoiceSheet } from "./open-invoice-sheet"; | ||
|
||
export async function CustomersHeader() { | ||
return ( | ||
<div className="flex items-center justify-between"> | ||
<InvoiceSearchFilter /> | ||
|
||
<div className="hidden sm:block"> | ||
<OpenInvoiceSheet /> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
123 changes: 123 additions & 0 deletions
123
apps/dashboard/src/components/tables/customers/columns.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
"use client"; | ||
|
||
import { useCustomerParams } from "@/hooks/use-customer-params"; | ||
import { Avatar, AvatarFallback, AvatarImageNext } from "@midday/ui/avatar"; | ||
import { Button } from "@midday/ui/button"; | ||
import { | ||
DropdownMenu, | ||
DropdownMenuContent, | ||
DropdownMenuItem, | ||
DropdownMenuTrigger, | ||
} from "@midday/ui/dropdown-menu"; | ||
import { DotsHorizontalIcon } from "@radix-ui/react-icons"; | ||
import type { ColumnDef } from "@tanstack/react-table"; | ||
import * as React from "react"; | ||
|
||
export type Customer = { | ||
id: string; | ||
name: string; | ||
customer_name?: string; | ||
website: string; | ||
contact_person?: string; | ||
email: string; | ||
invoices: number; | ||
projects: number; | ||
tags: { id: string; name: string }[]; | ||
}; | ||
|
||
export const columns: ColumnDef<Customer>[] = [ | ||
{ | ||
header: "Name", | ||
accessorKey: "name", | ||
cell: ({ row }) => { | ||
const name = row.original.name ?? row.original.customer_name; | ||
|
||
if (!name) return "-"; | ||
|
||
return ( | ||
<div className="flex items-center space-x-2"> | ||
<Avatar className="size-5"> | ||
{row.original.website && ( | ||
<AvatarImageNext | ||
src={`https://img.logo.dev/${row.original.website}?token=pk_X-1ZO13GSgeOoUrIuJ6GMQ&size=60`} | ||
alt={`${name} logo`} | ||
width={20} | ||
height={20} | ||
quality={100} | ||
/> | ||
)} | ||
<AvatarFallback className="text-[9px] font-medium"> | ||
{name?.[0]} | ||
</AvatarFallback> | ||
</Avatar> | ||
<span className="truncate">{name}</span> | ||
</div> | ||
); | ||
}, | ||
}, | ||
{ | ||
header: "Contact person", | ||
accessorKey: "contact_person", | ||
cell: ({ row }) => row.getValue("contact_person") ?? "-", | ||
}, | ||
{ | ||
header: "Email", | ||
accessorKey: "email", | ||
cell: ({ row }) => row.getValue("email") ?? "-", | ||
}, | ||
{ | ||
header: "Invoices", | ||
accessorKey: "invoices", | ||
cell: ({ row }) => row.getValue("invoices") ?? "-", | ||
}, | ||
{ | ||
header: "Projects", | ||
accessorKey: "projects", | ||
cell: ({ row }) => row.getValue("projects") ?? "-", | ||
}, | ||
{ | ||
header: "Tags", | ||
accessorKey: "tags", | ||
cell: ({ row }) => row.getValue("tags") ?? "-", | ||
}, | ||
{ | ||
id: "actions", | ||
header: "Actions", | ||
cell: ({ row }) => { | ||
const { setParams } = useCustomerParams(); | ||
|
||
return ( | ||
<div> | ||
<DropdownMenu> | ||
<DropdownMenuTrigger asChild className="relative"> | ||
<Button variant="ghost" className="h-8 w-8 p-0"> | ||
<DotsHorizontalIcon className="h-4 w-4" /> | ||
</Button> | ||
</DropdownMenuTrigger> | ||
|
||
<DropdownMenuContent align="end"> | ||
<DropdownMenuItem | ||
onClick={() => | ||
setParams({ | ||
customerId: row.original.id, | ||
}) | ||
} | ||
> | ||
Edit customer | ||
</DropdownMenuItem> | ||
|
||
<DropdownMenuItem | ||
onClick={() => | ||
table.options.meta?.deleteCustomer(row.original.id) | ||
} | ||
className="text-[#FF3638]" | ||
> | ||
Delete | ||
</DropdownMenuItem> | ||
</DropdownMenuContent> | ||
</DropdownMenu> | ||
</div> | ||
); | ||
}, | ||
}, | ||
]; |
57 changes: 57 additions & 0 deletions
57
apps/dashboard/src/components/tables/customers/empty-states.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
"use client"; | ||
|
||
import { useCustomerParams } from "@/hooks/use-customer-params"; | ||
import { Button } from "@midday/ui/button"; | ||
|
||
export function EmptyState() { | ||
const { setParams } = useCustomerParams(); | ||
|
||
return ( | ||
<div className="flex items-center justify-center "> | ||
<div className="flex flex-col items-center mt-40"> | ||
<div className="text-center mb-6 space-y-2"> | ||
<h2 className="font-medium text-lg">No customers</h2> | ||
<p className="text-[#606060] text-sm"> | ||
You haven't created any customers yet. <br /> | ||
Go ahead and create your first one. | ||
</p> | ||
</div> | ||
|
||
<Button | ||
variant="outline" | ||
onClick={() => | ||
setParams({ | ||
createCustomer: true, | ||
}) | ||
} | ||
> | ||
Create customer | ||
</Button> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export function NoResults() { | ||
const { setParams } = useCustomerParams(); | ||
|
||
return ( | ||
<div className="flex items-center justify-center "> | ||
<div className="flex flex-col items-center mt-40"> | ||
<div className="text-center mb-6 space-y-2"> | ||
<h2 className="font-medium text-lg">No results</h2> | ||
<p className="text-[#606060] text-sm"> | ||
Try another search, or adjusting the filters | ||
</p> | ||
</div> | ||
|
||
<Button | ||
variant="outline" | ||
onClick={() => setParams(null, { shallow: false })} | ||
> | ||
Clear filters | ||
</Button> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { getCustomers } from "@midday/supabase/cached-queries"; | ||
import { EmptyState, NoResults } from "./empty-states"; | ||
import { DataTable } from "./table"; | ||
|
||
type Props = { | ||
page: number; | ||
query?: string | null; | ||
sort?: string[] | null; | ||
start?: string | null; | ||
end?: string | null; | ||
}; | ||
|
||
const pageSize = 25; | ||
|
||
export async function CustomersTable({ query, sort, start, end, page }: Props) { | ||
const filter = { | ||
start, | ||
end, | ||
}; | ||
|
||
async function loadMore({ from, to }: { from: number; to: number }) { | ||
"use server"; | ||
|
||
return getCustomers({ | ||
// to, | ||
// from: from + 1, | ||
// searchQuery: query, | ||
// sort, | ||
// filter, | ||
}); | ||
} | ||
|
||
const { data, meta } = await getCustomers({ | ||
searchQuery: query, | ||
sort, | ||
filter, | ||
to: pageSize, | ||
}); | ||
|
||
const hasNextPage = Boolean( | ||
meta?.count && meta.count / (page + 1) > pageSize, | ||
); | ||
|
||
if (!data?.length) { | ||
if ( | ||
query?.length || | ||
Object.values(filter).some((value) => value !== null) | ||
) { | ||
return <NoResults />; | ||
} | ||
|
||
return <EmptyState />; | ||
} | ||
|
||
return ( | ||
<DataTable | ||
data={data} | ||
// loadMore={loadMore} | ||
pageSize={pageSize} | ||
hasNextPage={hasNextPage} | ||
page={page} | ||
/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
"use client"; | ||
|
||
import { cn } from "@midday/ui/cn"; | ||
import { TableCell, TableRow } from "@midday/ui/table"; | ||
import { type Row, flexRender } from "@tanstack/react-table"; | ||
import type { Customer } from "./columns"; | ||
|
||
type Props = { | ||
row: Row<Customer>; | ||
setOpen: (id?: string) => void; | ||
}; | ||
|
||
export function CustomerRow({ row, setOpen }: Props) { | ||
return ( | ||
<> | ||
<TableRow | ||
className="hover:bg-transparent cursor-default h-[45px] cursor-pointer" | ||
key={row.id} | ||
> | ||
{row.getVisibleCells().map((cell, index) => ( | ||
<TableCell | ||
key={cell.id} | ||
onClick={() => | ||
index !== row.getVisibleCells().length - 1 && setOpen(row.id) | ||
} | ||
> | ||
{flexRender(cell.column.columnDef.cell, cell.getContext())} | ||
</TableCell> | ||
))} | ||
</TableRow> | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function CustomersSkeleton() { | ||
return <div>CustomersSkeleton</div>; | ||
} |
Oops, something went wrong.