diff --git a/src/app/portal/accounts/[accountId]/edit/page.tsx b/src/app/portal/accounts/[accountId]/edit/page.tsx deleted file mode 100644 index 0750d32..0000000 --- a/src/app/portal/accounts/[accountId]/edit/page.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import AccountForm from "@/components/accounts/account-form"; -import { SimpleContentView } from "@/components/admin-panel/simple-content-view"; -import { findAccountById } from "@/lib/actions/accounts.action"; -import { deobfuscateToNumber, obfuscate } from "@/lib/endecode"; - -export default async function Page({ - params, -}: { - params: { accountId: string | "new" }; -}) { - const account = - params.accountId !== "new" - ? await findAccountById(deobfuscateToNumber(params.accountId)) - : undefined; - - const breadcrumbItems = [ - { title: "Dashboard", link: "/portal" }, - { title: "Accounts", link: "/portal/accounts" }, - ...(account - ? [ - { - title: `${account.name}`, - link: `/portal/accounts/${obfuscate(account.id)}`, - }, - { title: "Edit", link: "#" }, - ] - : [{ title: "Add", link: "#" }]), - ]; - - return ( - - - - ); -} diff --git a/src/app/portal/accounts/[accountId]/page.tsx b/src/app/portal/accounts/[accountId]/page.tsx deleted file mode 100644 index 563fa4a..0000000 --- a/src/app/portal/accounts/[accountId]/page.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { notFound } from "next/navigation"; - -import { AccountView } from "@/components/accounts/account-view"; -import { SimpleContentView } from "@/components/admin-panel/simple-content-view"; -import { findAccountById } from "@/lib/actions/accounts.action"; -import { deobfuscateToNumber } from "@/lib/endecode"; - -export default async function Page({ - params, -}: { - params: { accountId: string }; -}) { - const account = await findAccountById(deobfuscateToNumber(params.accountId)); - if (!account) { - notFound(); - } - - const breadcrumbItems = [ - { title: "Dashboard", link: "/portal" }, - { title: "Accounts", link: "/portal/accounts" }, - { title: account.name, link: "#" }, - ]; - - return ( - - - - ); -} diff --git a/src/app/portal/accounts/layout.tsx b/src/app/portal/accounts/layout.tsx deleted file mode 100644 index bf0e947..0000000 --- a/src/app/portal/accounts/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; - -import { ResourceProvider } from "@/providers/resource-provider"; - -export default function TeamsLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - -
{children}
-
- ); -} diff --git a/src/app/portal/accounts/page.tsx b/src/app/portal/accounts/page.tsx deleted file mode 100644 index 4ff3a85..0000000 --- a/src/app/portal/accounts/page.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Plus } from "lucide-react"; -import Link from "next/link"; -import React from "react"; - -import { AccountsTable } from "@/components/accounts/account-table"; -import { SimpleContentView } from "@/components/admin-panel/simple-content-view"; -import { Heading } from "@/components/heading"; -import { buttonVariants } from "@/components/ui/button"; -import { Separator } from "@/components/ui/separator"; -import { searchAccounts } from "@/lib/actions/accounts.action"; -import { cn } from "@/lib/utils"; -import { accountSearchParamsSchema } from "@/types/accounts"; - -const breadcrumbItems = [ - { title: "Dashboard", link: "/portal" }, - { title: "Accounts", link: "/portal/accounts" }, -]; - -type paramsProps = { - searchParams: { - [key: string]: string | string[] | undefined; - }; -}; - -const Page = ({ searchParams }: paramsProps) => { - const search = accountSearchParamsSchema.parse(searchParams); - const accountPromise = searchAccounts(search); - - return ( - -
- - - - New Account - -
- - -
- ); -}; - -export default Page; diff --git a/src/app/portal/contacts/[contactId]/edit/page.tsx b/src/app/portal/contacts/[contactId]/edit/page.tsx deleted file mode 100644 index c48fe95..0000000 --- a/src/app/portal/contacts/[contactId]/edit/page.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { SimpleContentView } from "@/components/admin-panel/simple-content-view"; -import ContactForm from "@/components/contacts/contact-form"; -import { findContactById } from "@/lib/actions/contacts.action"; -import { deobfuscateToNumber } from "@/lib/endecode"; - -const breadcrumbItems = [ - { title: "Dashboard", link: "/portal" }, - { title: "Contacts", link: "/portal/contacts" }, - { title: "Create", link: "/portal/contacts/new" }, -]; - -export default async function Page({ - params, -}: { - params: { contactId: string | "new" }; -}) { - const contact = - params.contactId !== "new" - ? await findContactById(deobfuscateToNumber(params.contactId)) - : undefined; - - return ( - - - - ); -} diff --git a/src/app/portal/contacts/[contactId]/page.tsx b/src/app/portal/contacts/[contactId]/page.tsx deleted file mode 100644 index d45e658..0000000 --- a/src/app/portal/contacts/[contactId]/page.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { notFound } from "next/navigation"; - -import { ContentLayout } from "@/components/admin-panel/content-layout"; -import { Breadcrumbs } from "@/components/breadcrumbs"; -import { ContactView } from "@/components/contacts/contact-view"; -import { findContactById } from "@/lib/actions/contacts.action"; -import { deobfuscateToNumber } from "@/lib/endecode"; - -const Page = async ({ params }: { params: { contactId: string } }) => { - const contact = await findContactById(deobfuscateToNumber(params.contactId)); - if (!contact) { - notFound(); - } - - const breadcrumbItems = [ - { title: "Dashboard", link: "/portal" }, - { title: "Contacts", link: "/portal/contacts" }, - { title: `${contact.firstName} ${contact.lastName}`, link: "#" }, - ]; - - return ( - - - - - ); -}; - -export default Page; diff --git a/src/app/portal/contacts/layout.tsx b/src/app/portal/contacts/layout.tsx deleted file mode 100644 index 3735cb1..0000000 --- a/src/app/portal/contacts/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; - -import { ResourceProvider } from "@/providers/resource-provider"; - -export default function TeamsLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - -
{children}
-
- ); -} diff --git a/src/app/portal/contacts/page.tsx b/src/app/portal/contacts/page.tsx deleted file mode 100644 index d2b4b67..0000000 --- a/src/app/portal/contacts/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { SimpleContentView } from "@/components/admin-panel/simple-content-view"; - -const breadcrumbItems = [ - { title: "Dashboard", link: "/portal" }, - { title: "Contacts", link: "/portal/contacts" }, -]; - -const ContactPage = () => { - return ( - -
Contacts
-
- ); -}; - -export default ContactPage; diff --git a/src/app/portal/files/layout.tsx b/src/app/portal/files/layout.tsx deleted file mode 100644 index 7c2865e..0000000 --- a/src/app/portal/files/layout.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react"; - -import { ResourceProvider } from "@/providers/resource-provider"; - -export default function TeamsLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - -
{children}
-
- ); -} diff --git a/src/app/portal/files/page.tsx b/src/app/portal/files/page.tsx deleted file mode 100644 index 131dbdc..0000000 --- a/src/app/portal/files/page.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Button } from "@/components/ui/button"; - -const FilesPage = () => { - return ( - <> -
-

Inventory

-
-
-
-

- You have no files -

-

Start uploading files

- -
-
- - ); -}; -export default FilesPage; diff --git a/src/app/portal/settings/accounts/industries/page.tsx b/src/app/portal/settings/accounts/industries/page.tsx deleted file mode 100644 index ac407b0..0000000 --- a/src/app/portal/settings/accounts/industries/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const AccountIndustriesSettingPage = () => { - return
Industries setting
; -}; - -export default AccountIndustriesSettingPage; diff --git a/src/app/portal/settings/accounts/types/page.tsx b/src/app/portal/settings/accounts/types/page.tsx deleted file mode 100644 index 047bd3a..0000000 --- a/src/app/portal/settings/accounts/types/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const AccountTypesSettingPage = () => { - return
Account Types
; -}; - -export default AccountTypesSettingPage; diff --git a/src/app/portal/settings/authorities/page.tsx b/src/app/portal/settings/authorities/page.tsx index 0a9b3e5..d8e55a7 100644 --- a/src/app/portal/settings/authorities/page.tsx +++ b/src/app/portal/settings/authorities/page.tsx @@ -2,15 +2,14 @@ import { Plus } from "lucide-react"; import { useRouter } from "next/navigation"; -import React, { useState } from "react"; +import React from "react"; import { SimpleContentView } from "@/components/admin-panel/simple-content-view"; -import { AuthoritiesTable } from "@/components/authorities/authority-table"; +import { AuthoritiesView } from "@/components/authorities/authority-list"; import { Heading } from "@/components/heading"; import { Button } from "@/components/ui/button"; import { Separator } from "@/components/ui/separator"; import { usePagePermission } from "@/hooks/use-page-permission"; -import { getAuthorities } from "@/lib/actions/authorities.action"; import { PermissionUtils } from "@/types/resources"; const breadcrumbItems = [ @@ -22,7 +21,6 @@ const breadcrumbItems = [ const AuthoritiesPage = () => { const router = useRouter(); const permissionLevel = usePagePermission(); - const [authorityPromise, setAuthorityPromise] = useState(getAuthorities); return ( @@ -38,10 +36,7 @@ const AuthoritiesPage = () => { )} - + ); }; diff --git a/src/components/accounts/account-delete-dialog.tsx b/src/components/accounts/account-delete-dialog.tsx deleted file mode 100644 index dfdd46f..0000000 --- a/src/components/accounts/account-delete-dialog.tsx +++ /dev/null @@ -1,15 +0,0 @@ -"use client"; - -import { - EntitiesDeleteDialog, - RowEntitiesDeleteDialogProps, -} from "@/components/shared/table-entity-delete-dialog"; -import { AccountDTO } from "@/types/accounts"; - -export function AccountDeleteDialog({ - ...props -}: RowEntitiesDeleteDialogProps) { - return EntitiesDeleteDialog({ - ...props, - }); -} diff --git a/src/components/accounts/account-form.tsx b/src/components/accounts/account-form.tsx deleted file mode 100644 index 70194eb..0000000 --- a/src/components/accounts/account-form.tsx +++ /dev/null @@ -1,130 +0,0 @@ -"use client"; - -import { zodResolver } from "@hookform/resolvers/zod"; -import { useRouter } from "next/navigation"; -import React from "react"; -import { useForm } from "react-hook-form"; - -import AccountIndustriesSelect from "@/components/accounts/account-industries-select"; -import AccountStatusSelect from "@/components/accounts/account-status-select"; -import AccountTypesSelect from "@/components/accounts/account-types-select"; -import { Heading } from "@/components/heading"; -import { Button } from "@/components/ui/button"; -import { - ExtInputField, - ExtTextAreaField, - FormProps, - SubmitButton, -} from "@/components/ui/ext-form"; -import { Form } from "@/components/ui/form"; -import { Separator } from "@/components/ui/separator"; -import { saveOrUpdateAccount } from "@/lib/actions/accounts.action"; -import { validateForm } from "@/lib/validator"; -import { AccountDTO, AccountDTOSchema } from "@/types/accounts"; - -export const AccountForm = ({ initialData }: FormProps) => { - const router = useRouter(); - - const form = useForm({ - resolver: zodResolver(AccountDTOSchema), - defaultValues: initialData, - }); - - async function onSubmit(account: AccountDTO) { - if (validateForm(account, AccountDTOSchema, form)) { - await saveOrUpdateAccount(isEdit, account); - } - } - - const isEdit = !!initialData; - const title = isEdit ? `Edit account ${initialData?.name}` : "Create account"; - const description = isEdit ? "Edit account" : "Add a new account"; - const submitText = isEdit ? "Save changes" : "Create"; - const submitTextWhileLoading = isEdit ? "Saving changes ..." : "Creating ..."; - - return ( -
-
- -
- -
- - - - - - - - - - - - - - -
- - -
- - -
- ); -}; - -export default AccountForm; diff --git a/src/components/accounts/account-id-select.tsx b/src/components/accounts/account-id-select.tsx deleted file mode 100644 index 7da56d6..0000000 --- a/src/components/accounts/account-id-select.tsx +++ /dev/null @@ -1,28 +0,0 @@ -"use client"; - -import React from "react"; - -import IdInputSelect from "@/components/shared/id-input-select"; - -type AccountFieldSelectProps = { - accountName: string | null; -}; - -const AccountSelectField: React.FC = ({ - accountName, -}) => { - const handleButtonClick = () => { - alert("Button clicked"); - }; - - return ( - - ); -}; - -export default AccountSelectField; diff --git a/src/components/accounts/account-industries-select.tsx b/src/components/accounts/account-industries-select.tsx deleted file mode 100644 index 5260395..0000000 --- a/src/components/accounts/account-industries-select.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; - -import ValuesQuerySelect from "@/components/shared/values-query-select"; -import { findAccountIndustries } from "@/lib/actions/accounts.action"; -import { EntityValueDefinition } from "@/types/commons"; -import { FormFieldProps } from "@/types/ui-components"; - -const AccountIndustriesSelect = ({ form, required }: FormFieldProps) => { - return ( - - form={form} - queryName="accountIndustries" - fieldName="industry" - fieldLabel="Industry" - fetchDataFn={findAccountIndustries} - valueKey="value" - renderTooltip={(entityValueDef: EntityValueDefinition) => - `${entityValueDef.description}` - } - renderOption={(entityValueDef: EntityValueDefinition) => - `${entityValueDef.value}` - } - required={required} - placeholder="Select industry" - noDataMessage="No industry found" - searchPlaceholder="Search industry..." - /> - ); -}; - -export default AccountIndustriesSelect; diff --git a/src/components/accounts/account-status-select.tsx b/src/components/accounts/account-status-select.tsx deleted file mode 100644 index 62f7b8b..0000000 --- a/src/components/accounts/account-status-select.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; - -import ValuesQuerySelect from "@/components/shared/values-query-select"; -import { findAccountStatuses } from "@/lib/actions/accounts.action"; -import { EntityValueDefinition } from "@/types/commons"; -import { FormFieldProps } from "@/types/ui-components"; - -const AccountStatusSelect = ({ form, required }: FormFieldProps) => { - return ( - - form={form} - queryName="accountStatuses" - fieldName="status" - fieldLabel="Status" - fetchDataFn={findAccountStatuses} - valueKey="value" - renderTooltip={(entityValueDef: EntityValueDefinition) => - `${entityValueDef.description}` - } - renderOption={(entityValueDef: EntityValueDefinition) => - `${entityValueDef.value}` - } - required={required} - placeholder="Select status" - noDataMessage="No status found" - searchPlaceholder="Search status..." - /> - ); -}; - -export default AccountStatusSelect; diff --git a/src/components/accounts/account-table-cell-action.tsx b/src/components/accounts/account-table-cell-action.tsx deleted file mode 100644 index bbd6aa3..0000000 --- a/src/components/accounts/account-table-cell-action.tsx +++ /dev/null @@ -1,54 +0,0 @@ -"use client"; -// import { AlertModal } from '@/components/modal/alert-modal'; -import { Row } from "@tanstack/react-table"; -import { Edit, MoreHorizontal, Trash } from "lucide-react"; -import { useRouter } from "next/navigation"; - -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { obfuscate } from "@/lib/endecode"; -import { AccountDTO } from "@/types/accounts"; - -export function AccountTableRowActions({ row }: { row: Row }) { - const router = useRouter(); - - function showDeleteConfirmDialog() {} - - return ( - <> - - - - - - Actions - - - router.push( - `/portal/accounts/${obfuscate(row.original.id!)}/edit`, - ) - } - > - Update - - - Delete - - - - - ); -} diff --git a/src/components/accounts/account-table-columns.tsx b/src/components/accounts/account-table-columns.tsx deleted file mode 100644 index 77b9788..0000000 --- a/src/components/accounts/account-table-columns.tsx +++ /dev/null @@ -1,133 +0,0 @@ -"use client"; - -import { ColumnDef } from "@tanstack/react-table"; -import Link from "next/link"; - -import { AccountTableRowActions } from "@/components/accounts/account-table-cell-action"; -import { Button } from "@/components/ui/button"; -import { Checkbox } from "@/components/ui/checkbox"; -import { DataTableColumnHeader } from "@/components/ui/table/data-table-column-header"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { formatDateTime, formatDateTimeDistanceToNow } from "@/lib/datetime"; -import { obfuscate } from "@/lib/endecode"; -import { AccountDTO } from "@/types/accounts"; - -export const accounts_columns_def: ColumnDef[] = [ - { - id: "select", - header: ({ table }) => ( - - table.toggleAllPageRowsSelected(!!value) - } - aria-label="Select all" - className="translate-y-0.5" - /> - ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label="Select row" - className="translate-y-0.5" - /> - ), - enableSorting: false, - enableHiding: false, - }, - { - accessorKey: "name", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
- -
- ), - }, - { - accessorKey: "type", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
{row.getValue("type")}
- ), - }, - { - accessorKey: "industry", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
{row.getValue("industry")}
- ), - }, - { - accessorKey: "status", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
{row.getValue("status")}
- ), - }, - { - accessorKey: "parentAccountName", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
- {row.original.parentAccountId ? ( - - ) : ( -
- )} -
- ), - }, - { - accessorKey: "createdAt", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - const field = new Date(row.getValue("createdAt")); - return ( - - - - {formatDateTimeDistanceToNow(field)} - - -

{formatDateTime(field)}

-
-
-
- ); - }, - }, - { - id: "actions", - cell: ({ row }) => , - size: 40, - }, -]; diff --git a/src/components/accounts/account-table-toolbar-actions.tsx b/src/components/accounts/account-table-toolbar-actions.tsx deleted file mode 100644 index d52c3de..0000000 --- a/src/components/accounts/account-table-toolbar-actions.tsx +++ /dev/null @@ -1,47 +0,0 @@ -"use client"; - -import { type Table } from "@tanstack/react-table"; -import { Download } from "lucide-react"; - -import { AccountDeleteDialog } from "@/components/accounts/account-delete-dialog"; -import { Button } from "@/components/ui/button"; -import { deleteAccounts } from "@/lib/actions/accounts.action"; -import { exportTableToCSV } from "@/lib/export"; -import { AccountDTO } from "@/types/accounts"; - -interface AccountTableToolbarActionsProps { - table: Table; -} - -export function AccountsTableToolbarActions({ - table, -}: AccountTableToolbarActionsProps) { - return ( -
- {table.getFilteredSelectedRowModel().rows.length > 0 ? ( - row.original)} - onSuccess={() => table.toggleAllRowsSelected(false)} - entityName="Account" - deleteEntitiesFn={deleteAccounts} - /> - ) : null} - -
- ); -} diff --git a/src/components/accounts/account-table.tsx b/src/components/accounts/account-table.tsx deleted file mode 100644 index 2b28936..0000000 --- a/src/components/accounts/account-table.tsx +++ /dev/null @@ -1,107 +0,0 @@ -"use client"; - -import React from "react"; - -import { accounts_columns_def } from "@/components/accounts/account-table-columns"; -import { AccountsTableToolbarActions } from "@/components/accounts/account-table-toolbar-actions"; -import { DataTable } from "@/components/ui/table/data-table"; -import { DataTableToolbar } from "@/components/ui/table/data-table-toolbar"; -import { useDataTable } from "@/hooks/use-data-table"; -import { useFetchData } from "@/hooks/use-fetch-data-values"; -import { - findAccountIndustries, - findAccountStatuses, - findAccountTypes, - searchAccounts, -} from "@/lib/actions/accounts.action"; -import { AccountDTO } from "@/types/accounts"; -import { DataTableFilterField } from "@/types/table"; - -interface AccountsTableProps { - accountsPromise: ReturnType; - enableAdvancedFilter: boolean; -} - -export function AccountsTable({ - accountsPromise, - enableAdvancedFilter = false, -}: AccountsTableProps) { - const { content: data, totalPages: pageCount } = React.use(accountsPromise); - const columns = React.useMemo(() => accounts_columns_def, []); - - const accountStatuses = useFetchData(findAccountStatuses); - const accountTypes = useFetchData(findAccountTypes); - const accountIndustries = useFetchData(findAccountIndustries); - - /** - * This component can render either a faceted filter or a search filter based on the `options` prop. - * - * @prop options - An array of objects, each representing a filter option. If provided, a faceted filter is rendered. If not, a search filter is rendered. - * - * Each `option` object has the following properties: - * @prop {string} label - The label for the filter option. - * @prop {string} value - The value for the filter option. - * @prop {React.ReactNode} [icon] - An optional icon to display next to the label. - * @prop {boolean} [withCount] - An optional boolean to display the count of the filter option. - */ - const filterFields: DataTableFilterField[] = [ - { - id: "name", - label: "Name", - placeholder: "Filter names...", - }, - // { - // id: "type", - // label: "Type", - // options: accountTypes.map((type) => ({ - // label: type, - // value: type, - // icon: undefined, - // withCount: true, - // })), - // }, - // { - // id: "industry", - // label: "Industry", - // options: accountIndustries.map((industry) => ({ - // label: industry, - // value: industry, - // icon: undefined, - // withCount: true, - // })), - // }, - // { - // id: "status", - // label: "Status", - // options: accountStatuses.map((status) => ({ - // label: status, - // value: status, - // icon: undefined, - // withCount: true, - // })), - // }, - ]; - - const { table } = useDataTable({ - data, - columns, - pageCount, - filterFields, - enableAdvancedFilter: enableAdvancedFilter, - initialState: { - sorting: [{ id: "createdAt", desc: true }], - columnPinning: { right: ["actions"] }, - }, - getRowId: (originalRow, index) => `${originalRow.id}-${index}`, - shallow: false, - clearOnDefault: true, - }); - - return ( - - - - - - ); -} diff --git a/src/components/accounts/account-types-select.tsx b/src/components/accounts/account-types-select.tsx deleted file mode 100644 index 0d183b2..0000000 --- a/src/components/accounts/account-types-select.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; - -import ValuesQuerySelect from "@/components/shared/values-query-select"; -import { findAccountTypes } from "@/lib/actions/accounts.action"; -import { EntityValueDefinition } from "@/types/commons"; -import { FormFieldProps } from "@/types/ui-components"; - -const AccountTypesSelect = ({ form, required }: FormFieldProps) => { - return ( - - form={form} - queryName="accountTypes" - fieldName="type" - fieldLabel="Type" - fetchDataFn={findAccountTypes} - valueKey="value" - renderTooltip={(entityValueDef: EntityValueDefinition) => - `${entityValueDef.description}` - } - renderOption={(entityValueDef: EntityValueDefinition) => - `${entityValueDef.value}` - } - required={required} - placeholder="Select type" - noDataMessage="No type found" - searchPlaceholder="Search type..." - /> - ); -}; - -export default AccountTypesSelect; diff --git a/src/components/accounts/account-view.tsx b/src/components/accounts/account-view.tsx deleted file mode 100644 index fb0800a..0000000 --- a/src/components/accounts/account-view.tsx +++ /dev/null @@ -1,171 +0,0 @@ -"use client"; - -import { ChevronLeft, ChevronRight, Edit, Plus } from "lucide-react"; -import Link from "next/link"; -import { usePathname, useRouter } from "next/navigation"; -import React, { useEffect, useState } from "react"; - -import { ContactsTable } from "@/components/contacts/contact-table"; -import { Badge } from "@/components/ui/badge"; -import { usePagePermission } from "@/hooks/use-page-permission"; -import { - findNextAccount, - findPreviousAccount, -} from "@/lib/actions/accounts.action"; -import { searchContacts } from "@/lib/actions/contacts.action"; -import { obfuscate } from "@/lib/endecode"; -import { navigateToRecord } from "@/lib/navigation-record"; -import { cn } from "@/lib/utils"; -import { AccountDTO } from "@/types/accounts"; -import { PageableResult } from "@/types/commons"; -import { ConTactDTO } from "@/types/contacts"; -import { PermissionUtils } from "@/types/resources"; - -import { Button, buttonVariants } from "../ui/button"; -import { Card, CardContent } from "../ui/card"; -import { ViewProps } from "../ui/ext-form"; - -export const AccountView: React.FC> = ({ - entity, -}: ViewProps) => { - const permissionLevel = usePagePermission(); - const router = useRouter(); - const pathname = usePathname(); - const [account, setAccount] = useState(entity); - const [contactPromise, setContactPromise] = useState< - Promise> - >( - searchContacts({ - filters: [{ field: "account.id", operator: "eq", value: account.id! }], - }), - ); - - useEffect(() => { - if (account && pathname !== `/portal/accounts/${obfuscate(account.id)}`) { - router.replace(`/portal/accounts/${obfuscate(account.id)}`); - } - }, [account, router]); - - const navigateToPreviousRecord = async () => { - const previousAccount = await navigateToRecord( - findPreviousAccount, - "You reach the first record", - account.id!, - ); - setAccount(previousAccount); - }; - - const navigateToNextRecord = async () => { - const nextAccount = await navigateToRecord( - findNextAccount, - "You reach the last record", - account.id!, - ); - setAccount(nextAccount); - }; - - return ( -
-
- -
{account.name}
- {PermissionUtils.canWrite(permissionLevel) && ( - - )} - - -
- - -
-
- Type:{" "} - - {account.type} - -
-
- Industry:{" "} - - {account.industry} - -
-
- Address Line: {account.addressLine1} - {account.city ? `, ${account.city}` : ""} - {account.state ? `, ${account.state}` : ""} - {account.postalCode ? `, ${account.postalCode}` : ""} - {account.country ? `, ${account.country}` : ""} -
-
Phone number: {account.phoneNumber}
-
- Status:{" "} - - {account.status} - -
-
- Parent Account:{" "} - {account.parentAccountId ? ( - - ) : ( - "" - )} -
-
-
-
- -
-
Contacts
-
- - New Contact - -
-
- -
- ); -}; diff --git a/src/components/authorities/authority-list.tsx b/src/components/authorities/authority-list.tsx new file mode 100644 index 0000000..fa1b793 --- /dev/null +++ b/src/components/authorities/authority-list.tsx @@ -0,0 +1,165 @@ +"use client"; + +import Link from "next/link"; +import React, { useEffect, useState } from "react"; + +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; // Add a badge for visual distinction +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { getAuthorities } from "@/lib/actions/authorities.action"; +import { obfuscate } from "@/lib/endecode"; +import { AuthorityDTO } from "@/types/authorities"; +import PaginationExt from "@/components/shared/pagination-ext"; +import { PermissionUtils } from "@/types/resources"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Ellipsis, Trash, Shield } from "lucide-react"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { usePagePermission } from "@/hooks/use-page-permission"; + +export function AuthoritiesView() { + const [authorities, setAuthorities] = useState>([]); + const [currentPage, setCurrentPage] = useState(1); + const [totalPages, setTotalPages] = useState(0); + const [totalElements, setTotalElements] = useState(0); + const [selectedAuthority, setSelectedAuthority] = + useState(null); + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const permissionLevel = usePagePermission(); + + useEffect(() => { + const fetchAuthorities = async () => { + getAuthorities().then((pageableResult) => { + setAuthorities(pageableResult.content); + setTotalPages(pageableResult.totalPages); + setTotalElements(pageableResult.totalElements); + }); + }; + + fetchAuthorities(); + }, []); + + function handleDeleteClick(authority: AuthorityDTO) { + setSelectedAuthority(authority); + setIsDialogOpen(true); + } + + function confirmDeleteAuthority() { + if (selectedAuthority) { + console.log(`Delete ${JSON.stringify(selectedAuthority)}`); + // Add actual delete logic here (e.g., API call) + } + setIsDialogOpen(false); + setSelectedAuthority(null); + } + + return ( +
+
+ {authorities?.map((authority) => ( +
+
+
+ + {authority.systemRole && ( + + + System Role + + )} +
+
{authority.description}
+
+ {PermissionUtils.canAccess(permissionLevel) && + !authority.systemRole && ( + + + + + + + + + handleDeleteClick(authority)} + > + Remove authority + + + +

+ This action will delete the role and unassign all + users from this role. +

+
+
+
+
+
+ )} +
+ ))} + setCurrentPage(page)} + /> +
+ + {/* Confirmation Dialog */} + + + + Confirm Deletion + +

+ Are you sure you want to delete{" "} + {selectedAuthority?.descriptiveName}? This action + cannot be undone. +

+ + + + +
+
+
+ ); +} diff --git a/src/components/authorities/authority-table-cell-action.tsx b/src/components/authorities/authority-table-cell-action.tsx deleted file mode 100644 index bb75c31..0000000 --- a/src/components/authorities/authority-table-cell-action.tsx +++ /dev/null @@ -1,67 +0,0 @@ -"use client"; -// import { AlertModal } from '@/components/modal/alert-modal'; -import { Row } from "@tanstack/react-table"; -import { Edit, MoreHorizontal, Trash } from "lucide-react"; -import { useRouter } from "next/navigation"; - -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { obfuscate } from "@/lib/endecode"; -import { AuthorityDTO } from "@/types/authorities"; - -export function AuthorityTableRowActions({ row }: { row: Row }) { - const router = useRouter(); - - const authority = row.original; - return ( - <> - - - - - - Actions - - - router.push( - `/portal/settings/authorities/${obfuscate(row.original.name!)}/edit`, - ) - : undefined - } - > - Update - - console.log("Open menu") : undefined - } - > - Delete - - - - - ); -} diff --git a/src/components/authorities/authority-table-columns.tsx b/src/components/authorities/authority-table-columns.tsx deleted file mode 100644 index 358b673..0000000 --- a/src/components/authorities/authority-table-columns.tsx +++ /dev/null @@ -1,72 +0,0 @@ -"use client"; - -import { ColumnDef } from "@tanstack/react-table"; -import Link from "next/link"; - -import { AuthorityTableRowActions } from "@/components/authorities/authority-table-cell-action"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Checkbox } from "@/components/ui/checkbox"; -import { DataTableColumnHeader } from "@/components/ui/table/data-table-column-header"; -import { obfuscate } from "@/lib/endecode"; -import { AuthorityDTO } from "@/types/authorities"; - -export const authorities_columns_def: ColumnDef[] = [ - { - id: "select", - header: ({ table }) => ( - - table.toggleAllPageRowsSelected(!!value) - } - aria-label="Select all" - className="translate-y-0.5" - /> - ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label="Select row" - className="translate-y-0.5" - /> - ), - enableSorting: false, - enableHiding: false, - }, - { - accessorKey: "descriptiveName", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - const isSystemLabel = row.original.systemRole; - - return ( -
- {isSystemLabel && System} - -
- ); - }, - }, - { - accessorKey: "description", - header: ({ column }) => ( - - ), - cell: ({ row }) =>
{row.getValue("description")}
, - enableSorting: false, - }, - { - id: "actions", - cell: ({ row }) => , - }, -]; diff --git a/src/components/authorities/authority-table-toolbar-actions.tsx b/src/components/authorities/authority-table-toolbar-actions.tsx deleted file mode 100644 index 00c0d94..0000000 --- a/src/components/authorities/authority-table-toolbar-actions.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { type Table } from "@tanstack/react-table"; -import { Download } from "lucide-react"; - -import { Button } from "@/components/ui/button"; -import { exportTableToCSV } from "@/lib/export"; -import { AuthorityDTO } from "@/types/authorities"; - -interface AuthorityTableToolbarActionsProps { - table: Table; -} - -export function AuthoritiesTableToolbarActions({ - table, -}: AuthorityTableToolbarActionsProps) { - return ( -
- -
- ); -} diff --git a/src/components/authorities/authority-table.tsx b/src/components/authorities/authority-table.tsx deleted file mode 100644 index 4b2bf4a..0000000 --- a/src/components/authorities/authority-table.tsx +++ /dev/null @@ -1,86 +0,0 @@ -"use client"; - -import React, { useEffect, useState } from "react"; - -import { authorities_columns_def } from "@/components/authorities/authority-table-columns"; -import { AuthoritiesTableToolbarActions } from "@/components/authorities/authority-table-toolbar-actions"; -import { DataTable } from "@/components/ui/table/data-table"; -import { DataTableToolbar } from "@/components/ui/table/data-table-toolbar"; -import { useDataTable } from "@/hooks/use-data-table"; -import { getAuthorities } from "@/lib/actions/authorities.action"; -import { AuthorityDTO } from "@/types/authorities"; -import { DataTableFilterField } from "@/types/table"; - -interface AuthorityTableProps { - authoritiesPromise: ReturnType; - enableAdvancedFilter: boolean; -} - -export function AuthoritiesTable({ - authoritiesPromise, - enableAdvancedFilter = false, -}: AuthorityTableProps) { - const [data, setData] = useState>([]); - const [pageCount, setPageCount] = useState(0); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const fetchAuthorities = async () => { - try { - setLoading(true); - const pageResult = await authoritiesPromise; - setData(pageResult?.content); - setPageCount(pageResult?.totalPages); - } finally { - setLoading(false); - } - }; - - fetchAuthorities(); - }, [authoritiesPromise]); - - // Memoize the columns so they don't re-render on every render - const columns = React.useMemo(() => authorities_columns_def, []); - - /** - * This component can render either a faceted filter or a search filter based on the `options` prop. - * - * @prop options - An array of objects, each representing a filter option. If provided, a faceted filter is rendered. If not, a search filter is rendered. - * - * Each `option` object has the following properties: - * @prop {string} label - The label for the filter option. - * @prop {string} value - The value for the filter option. - * @prop {React.ReactNode} [icon] - An optional icon to display next to the label. - * @prop {boolean} [withCount] - An optional boolean to display the count of the filter option. - */ - const filterFields: DataTableFilterField[] = [ - { - label: "Name", - id: "name", - placeholder: "Filter names...", - }, - ]; - - const { table } = useDataTable({ - data, - columns, - pageCount, - filterFields, - enableAdvancedFilter: enableAdvancedFilter, - initialState: { - sorting: [{ id: "name", desc: true }], - columnPinning: { right: ["actions"] }, - }, - getRowId: (originalRow, index) => `${originalRow.name}-${index}`, - shallow: false, - clearOnDefault: true, - }); - - return ( - - - - - - ); -} diff --git a/src/components/contacts/contact-form.tsx b/src/components/contacts/contact-form.tsx deleted file mode 100644 index 29b070c..0000000 --- a/src/components/contacts/contact-form.tsx +++ /dev/null @@ -1,149 +0,0 @@ -"use client"; - -import { zodResolver } from "@hookform/resolvers/zod"; -import { useRouter, useSearchParams } from "next/navigation"; -import React from "react"; -import { useForm } from "react-hook-form"; - -import AccountSelectField from "@/components/accounts/account-id-select"; -import ContactStatusSelect from "@/components/contacts/contact-status-select"; -import { Heading } from "@/components/heading"; -import { - ExtInputField, - ExtTextAreaField, - FormProps, - SubmitButton, -} from "@/components/ui/ext-form"; -import { Form } from "@/components/ui/form"; -import { Separator } from "@/components/ui/separator"; -import { saveOrUpdateContact } from "@/lib/actions/contacts.action"; -import { deobfuscateToNumber } from "@/lib/endecode"; -import { validateForm } from "@/lib/validator"; -import { ConTactDTO, ContactDTOSchema } from "@/types/contacts"; - -import { Button } from "../ui/button"; - -export const ContactForm = ({ initialData }: FormProps) => { - const router = useRouter(); - - let accountId: number | null = null; - let accountName: string | null = null; - const searchParams = useSearchParams(); - const accountIdBase64 = searchParams.get("accountId"); - - if (accountIdBase64 !== null) { - accountId = deobfuscateToNumber(accountIdBase64); - accountName = searchParams.get("accountName"); - } - - const form = useForm({ - resolver: zodResolver(ContactDTOSchema), - defaultValues: { - ...initialData, - accountId: accountId, - accountName: accountName, - }, - }); - - async function onSubmit(contact: ConTactDTO) { - // contact.accountId = 1; - if (validateForm(contact, ContactDTOSchema, form)) { - await saveOrUpdateContact(isEdit, contact); - } - } - - const isEdit = !!initialData; - const title = isEdit - ? `Edit contact ${initialData?.firstName} ${initialData?.lastName}` - : "Create contact"; - const description = isEdit ? "Edit contact" : "Add a new contact"; - const submitText = isEdit ? "Save changes" : "Create"; - const submitTextWhileLoading = isEdit ? "Saving changes ..." : "Creating ..."; - - return ( -
-
- -
- -
- - - - - - - - - - - - - -
- - -
- - -
- ); -}; - -export default ContactForm; diff --git a/src/components/contacts/contact-status-select.tsx b/src/components/contacts/contact-status-select.tsx deleted file mode 100644 index 7dec566..0000000 --- a/src/components/contacts/contact-status-select.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; - -import ValuesQuerySelect from "@/components/shared/values-query-select"; -import { findContactStatuses } from "@/lib/actions/contacts.action"; -import { EntityValueDefinition } from "@/types/commons"; -import { FormFieldProps } from "@/types/ui-components"; - -const ContactStatusSelect = ({ form, required }: FormFieldProps) => { - return ( - - form={form} - queryName="contactStatuses" - fieldName="status" - fieldLabel="Status" - fetchDataFn={findContactStatuses} - valueKey="value" - renderTooltip={(entityValueDef: EntityValueDefinition) => - `${entityValueDef.description}` - } - renderOption={(entityValueDef: EntityValueDefinition) => - `${entityValueDef.value}` - } - required={required} - placeholder="Select status" - noDataMessage="No status found" - searchPlaceholder="Search status..." - /> - ); -}; - -export default ContactStatusSelect; diff --git a/src/components/contacts/contact-table-cell-action.tsx b/src/components/contacts/contact-table-cell-action.tsx deleted file mode 100644 index 9fd3ff5..0000000 --- a/src/components/contacts/contact-table-cell-action.tsx +++ /dev/null @@ -1,47 +0,0 @@ -"use client"; -// import { AlertModal } from '@/components/modal/alert-modal'; -import { Row } from "@tanstack/react-table"; -import { Edit, MoreHorizontal } from "lucide-react"; -import { useRouter } from "next/navigation"; - -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { obfuscate } from "@/lib/endecode"; -import { ConTactDTO } from "@/types/contacts"; - -export function DataTableRowActions({ row }: { row: Row }) { - const router = useRouter(); - - return ( - <> - - - - - - Actions - - - router.push(`/portal/contacts/${obfuscate(row.original.id)}/edit`) - } - > - Update - - {/* setOpen(true)}>*/} - {/* Delete*/} - {/**/} - - - - ); -} diff --git a/src/components/contacts/contact-table-columns.tsx b/src/components/contacts/contact-table-columns.tsx deleted file mode 100644 index c43e0bb..0000000 --- a/src/components/contacts/contact-table-columns.tsx +++ /dev/null @@ -1,104 +0,0 @@ -"use client"; - -import { ColumnDef } from "@tanstack/react-table"; -import Link from "next/link"; - -import { DataTableRowActions } from "@/components/contacts/contact-table-cell-action"; -import { Button } from "@/components/ui/button"; -import { Checkbox } from "@/components/ui/checkbox"; -import { DataTableColumnHeader } from "@/components/ui/table/data-table-column-header"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; -import { formatDateTime, formatDateTimeDistanceToNow } from "@/lib/datetime"; -import { obfuscate } from "@/lib/endecode"; -import { ConTactDTO } from "@/types/contacts"; - -export const contacts_columns_def: ColumnDef[] = [ - { - id: "select", - header: ({ table }) => ( - - table.toggleAllPageRowsSelected(!!value) - } - aria-label="Select all" - className="translate-y-[2px]" - /> - ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label="Select row" - className="translate-y-[2px]" - /> - ), - enableSorting: false, - enableHiding: false, - }, - { - accessorFn: (row) => `${row.firstName} ${row.lastName}`, - accessorKey: "name", - header: ({ column }) => ( - - ), - cell: ({ row }) => ( -
- -
- ), - }, - { - accessorKey: "email", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - return
{row.getValue("email")}
; - }, - filterFn: (row, id, value) => { - return value.includes(row.getValue(id)); - }, - }, - { - accessorKey: "status", - header: ({ column }) => ( - - ), - cell: ({ row }) =>
{row.getValue("status")}
, - }, - { - accessorKey: "createdAt", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - const field = new Date(row.getValue("createdAt")); - return ( - - - - {formatDateTimeDistanceToNow(field)} - - -

{formatDateTime(field)}

-
-
-
- ); - }, - }, - { - id: "actions", - cell: ({ row }) => , - }, -]; diff --git a/src/components/contacts/contact-table-toolbar-actions.tsx b/src/components/contacts/contact-table-toolbar-actions.tsx deleted file mode 100644 index ee9f42d..0000000 --- a/src/components/contacts/contact-table-toolbar-actions.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { type Table } from "@tanstack/react-table"; -import { Download } from "lucide-react"; - -import { Button } from "@/components/ui/button"; -import { exportTableToCSV } from "@/lib/export"; -import { ConTactDTO } from "@/types/contacts"; - -interface ContactsTableToolbarActionsProps { - table: Table; -} - -export function ContactsTableToolbarActions({ - table, -}: ContactsTableToolbarActionsProps) { - return ( -
- -
- ); -} diff --git a/src/components/contacts/contact-table.tsx b/src/components/contacts/contact-table.tsx deleted file mode 100644 index 0bf05eb..0000000 --- a/src/components/contacts/contact-table.tsx +++ /dev/null @@ -1,111 +0,0 @@ -"use client"; - -import React, { useEffect, useState } from "react"; - -import { contacts_columns_def } from "@/components/contacts/contact-table-columns"; -import { ContactsTableToolbarActions } from "@/components/contacts/contact-table-toolbar-actions"; -import { Skeleton } from "@/components/ui/skeleton"; -import { DataTable } from "@/components/ui/table/data-table"; -import { DataTableToolbar } from "@/components/ui/table/data-table-toolbar"; -import { useDataTable } from "@/hooks/use-data-table"; -import { useFetchData } from "@/hooks/use-fetch-data-values"; -import { - findContactStatuses, - searchContacts, -} from "@/lib/actions/contacts.action"; -import { ConTactDTO } from "@/types/contacts"; -import { DataTableFilterField } from "@/types/table"; - -interface ContactsTableProps { - contactPromise: ReturnType; - enableAdvancedFilter: boolean; -} - -export function ContactsTable({ - contactPromise, - enableAdvancedFilter = false, -}: ContactsTableProps) { - const [data, setData] = useState>([]); - const [pageCount, setPageCount] = useState(0); - const [loading, setLoading] = useState(true); - - // Fetch data using useEffect - useEffect(() => { - // Define async function to handle the promise - const fetchContacts = async () => { - try { - setLoading(true); - const pageResult = await contactPromise; - setData(pageResult?.content); - setPageCount(pageResult?.totalPages); - } finally { - setLoading(false); - } - }; - - fetchContacts(); // Call the async function - }, []); // Empty dependency array ensures it runs only once when component mounts - - // Memoize the columns so they don't re-render on every render - const columns = React.useMemo(() => contacts_columns_def, []); - - const contactStatuses = useFetchData(findContactStatuses); - - /** - * This component can render either a faceted filter or a search filter based on the `options` prop. - * - * @prop options - An array of objects, each representing a filter option. If provided, a faceted filter is rendered. If not, a search filter is rendered. - * - * Each `option` object has the following properties: - * @prop {string} label - The label for the filter option. - * @prop {string} value - The value for the filter option. - * @prop {React.ReactNode} [icon] - An optional icon to display next to the label. - * @prop {boolean} [withCount] - An optional boolean to display the count of the filter option. - */ - const filterFields: DataTableFilterField[] = [ - { - label: "First Name", - id: "firstName", - placeholder: "Filter names...", - }, - // { - // label: "Status", - // id: "status", - // options: contactStatuses.map((status) => ({ - // label: status, - // value: status, - // icon: undefined, - // withCount: true, - // })), - // }, - ]; - - const { table } = useDataTable({ - data, - columns, - pageCount, - filterFields, - enableAdvancedFilter: enableAdvancedFilter, - initialState: { - sorting: [{ id: "createdAt", desc: true }], - columnPinning: { right: ["actions"] }, - }, - getRowId: (originalRow, index) => `${originalRow.id}-${index}`, - shallow: false, - clearOnDefault: true, - }); - - if (loading) - return ( -
- -
- ); - return ( - - - - - - ); -} diff --git a/src/components/contacts/contact-view.tsx b/src/components/contacts/contact-view.tsx deleted file mode 100644 index 3a0a9af..0000000 --- a/src/components/contacts/contact-view.tsx +++ /dev/null @@ -1,94 +0,0 @@ -"use client"; - -import { ChevronLeft, ChevronRight, Edit } from "lucide-react"; -import { usePathname, useRouter } from "next/navigation"; -import React, { useState } from "react"; - -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { ViewProps } from "@/components/ui/ext-form"; -import { - findNextContact, - findPreviousContact, -} from "@/lib/actions/contacts.action"; -import { obfuscate } from "@/lib/endecode"; -import { navigateToRecord } from "@/lib/navigation-record"; -import { ConTactDTO } from "@/types/contacts"; - -export const ContactView: React.FC> = ({ - entity, -}: ViewProps) => { - const router = useRouter(); - const pathname = usePathname(); - - const [contact, setContact] = useState(entity); - - const navigateToPreviousRecord = async () => { - const previousContact = await navigateToRecord( - findPreviousContact, - "You reach the first record", - contact.id!, - ); - setContact(previousContact); - }; - - const navigateToNextRecord = async () => { - const nextContact = await navigateToRecord( - findNextContact, - "You reach the last record", - contact.id!, - ); - setContact(nextContact); - }; - - return ( -
-
- -
- {contact.firstName} {contact.lastName} -
- - -
- - -
-
- Account: {contact.accountName} -
-
Email: {contact.email}
-
Address Line: {contact.address}
-
City: {contact.city}
-
State: {contact.state}
-
Country: {contact.country}
-
- Status: {contact.status} -
-
-
-
-
- ); -}; diff --git a/src/components/dashboard/team-unresolved-tickets-priority-distribution.tsx b/src/components/dashboard/team-unresolved-tickets-priority-distribution.tsx index d7330ec..c4b3959 100644 --- a/src/components/dashboard/team-unresolved-tickets-priority-distribution.tsx +++ b/src/components/dashboard/team-unresolved-tickets-priority-distribution.tsx @@ -67,13 +67,13 @@ const TeamUnresolvedTicketsPriorityDistributionChart = () => { Unresolved Tickets by Team - + {loading ? ( -
+
) : chartData.length === 0 ? ( -
+

No data available to display.

) : ( diff --git a/src/components/shared/table-entity-delete-dialog.tsx b/src/components/shared/table-entity-delete-dialog.tsx deleted file mode 100644 index f6afd13..0000000 --- a/src/components/shared/table-entity-delete-dialog.tsx +++ /dev/null @@ -1,170 +0,0 @@ -"use client"; - -import { TrashIcon } from "@radix-ui/react-icons"; -import { type Row } from "@tanstack/react-table"; -import * as React from "react"; -import { toast } from "sonner"; - -import { Icons } from "@/components/icons"; -import { Button } from "@/components/ui/button"; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui/dialog"; -import { - Drawer, - DrawerClose, - DrawerContent, - DrawerDescription, - DrawerFooter, - DrawerHeader, - DrawerTitle, - DrawerTrigger, -} from "@/components/ui/drawer"; -import { useMediaQuery } from "@/hooks/use-media-query"; - -export interface RowEntitiesDeleteDialogProps< - TEntity extends Record, -> extends React.ComponentPropsWithoutRef { - entities: Row["original"][]; - showTrigger?: boolean; - onSuccess?: () => void; - deleteEntitiesFn: (ids: number[]) => Promise; // Pass a function to delete entities - entityName: string; // Pass the entity name for the UI text -} - -export function EntitiesDeleteDialog>({ - entities, - showTrigger = true, - onSuccess, - deleteEntitiesFn, - entityName, - ...props -}: RowEntitiesDeleteDialogProps) { - const [isDeletePending, startDeleteTransition] = React.useTransition(); - const isDesktop = useMediaQuery("(min-width: 640px)"); - - function onDelete() { - startDeleteTransition(async () => { - // Perform a runtime check to ensure each entity has an "id" field - const ids = entities.map((entity) => { - if ("id" in entity && typeof entity.id === "number") { - return entity.id; // Return the ID if it exists and is a string - } else { - throw new Error(`Entity does not have a valid "id" field`); - } - }); - - try { - await deleteEntitiesFn(ids); - } catch (error) { - toast.error( - `Cannot delete ${entityName}${entities.length > 1 ? "s" : ""}`, - ); - return; - } - - props.onOpenChange?.(false); - toast.success( - `${entityName}${entities.length > 1 ? "s are" : " is"} deleted`, - ); - onSuccess?.(); - }); - } - - if (isDesktop) { - return ( - - {showTrigger ? ( - - - - ) : null} - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete your{" "} - {entities.length} - {entities.length === 1 - ? ` ${entityName}` - : ` ${entityName}s`}{" "} - from our servers. - - - - - - - - - - - ); - } - - return ( - - {showTrigger ? ( - - - - ) : null} - - - Are you absolutely sure? - - This action cannot be undone. This will permanently delete your{" "} - {entities.length} - {entities.length === 1 ? ` ${entityName}` : ` ${entityName}s`} from - our servers. - - - - - - - - - - - ); -} diff --git a/src/components/teams/team-dashboard-kpis.tsx b/src/components/teams/team-dashboard-kpis.tsx index 9c49bf5..ce2ed04 100644 --- a/src/components/teams/team-dashboard-kpis.tsx +++ b/src/components/teams/team-dashboard-kpis.tsx @@ -45,7 +45,7 @@ const TeamDashboardTopSection = ({ teamId }: { teamId: number }) => { { title: "Total Tickets", description: "All tickets received", - value: totalTickets, + value: totalTickets ?? 0, color: "text-gray-700 dark:text-gray-300", link: "#", tooltip: "View all team tickets.", @@ -53,7 +53,7 @@ const TeamDashboardTopSection = ({ teamId }: { teamId: number }) => { { title: "Pending Tickets", description: "Tickets yet to be addressed", - value: pendingTickets, + value: pendingTickets ?? 0, color: "text-yellow-500", link: "#", tooltip: "View tickets that are still pending.", @@ -61,7 +61,7 @@ const TeamDashboardTopSection = ({ teamId }: { teamId: number }) => { { title: "Completed Tickets", description: "Successfully resolved tickets", - value: completedTickets, + value: completedTickets ?? 0, color: "text-green-500", link: "#", tooltip: "View tickets that have been resolved.", @@ -69,7 +69,7 @@ const TeamDashboardTopSection = ({ teamId }: { teamId: number }) => { { title: "Overdue Tickets", description: "Tickets past their deadline", - value: overDueTickets, + value: overDueTickets ?? 0, color: "text-red-500", link: "#", tooltip: "View overdue tickets that need attention.", diff --git a/src/components/ui/table/data-table-advanced-toolbar.tsx b/src/components/ui/table/data-table-advanced-toolbar.tsx deleted file mode 100644 index 9368247..0000000 --- a/src/components/ui/table/data-table-advanced-toolbar.tsx +++ /dev/null @@ -1,94 +0,0 @@ -"use client"; - -import { type Table } from "@tanstack/react-table"; -import * as React from "react"; - -import { DataTableFilterList } from "@/components/ui/table/data-table-filter-list"; -import { DataTableSortList } from "@/components/ui/table/data-table-sort-list"; -import { DataTableViewOptions } from "@/components/ui/table/data-table-view-options"; -import { cn } from "@/lib/utils"; -import { DataTableAdvancedFilterField } from "@/types/table"; - -interface DataTableAdvancedToolbarProps - extends React.HTMLAttributes { - /** - * The table instance returned from useDataTable hook with pagination, sorting, filtering, etc. - * @type Table - */ - table: Table; - - /** - * An array of filter field configurations for the data table. - * @type DataTableAdvancedFilterField[] - * @example - * const filterFields = [ - * { - * id: 'name', - * label: 'Name', - * type: 'text', - * placeholder: 'Filter by name...' - * }, - * { - * id: 'status', - * label: 'Status', - * type: 'select', - * options: [ - * { label: 'Active', value: 'active', count: 10 }, - * { label: 'Inactive', value: 'inactive', count: 5 } - * ] - * } - * ] - */ - filterFields: DataTableAdvancedFilterField[]; - - /** - * Debounce time (ms) for filter updates to enhance performance during rapid input. - * @default 300 - */ - debounceMs?: number; - - /** - * Shallow mode keeps query states client-side, avoiding server calls. - * Setting to `false` triggers a network request with the updated querystring. - * @default true - */ - shallow?: boolean; -} - -export function DataTableAdvancedToolbar({ - table, - filterFields = [], - debounceMs = 300, - shallow = true, - children, - className, - ...props -}: DataTableAdvancedToolbarProps) { - return ( -
-
- - -
-
- {children} - -
-
- ); -} diff --git a/src/components/ui/table/data-table-column-header.tsx b/src/components/ui/table/data-table-column-header.tsx deleted file mode 100644 index cae219e..0000000 --- a/src/components/ui/table/data-table-column-header.tsx +++ /dev/null @@ -1,109 +0,0 @@ -"use client"; - -import { SelectIcon } from "@radix-ui/react-select"; -import { type Column } from "@tanstack/react-table"; -import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from "lucide-react"; - -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, -} from "@/components/ui/select"; -import { cn } from "@/lib/utils"; - -interface DataTableColumnHeaderProps - extends React.HTMLAttributes { - column: Column; - title: string; -} - -export function DataTableColumnHeader({ - column, - title, - className, -}: DataTableColumnHeaderProps) { - if (!column.getCanSort() && !column.getCanHide()) { - return
{title}
; - } - - const ascValue = `${column.id}-asc`; - const descValue = `${column.id}-desc`; - const hideValue = `${column.id}-hide`; - - return ( -
- -
- ); -} diff --git a/src/components/ui/table/data-table-faceted-filter.tsx b/src/components/ui/table/data-table-faceted-filter.tsx deleted file mode 100644 index 0d88c0b..0000000 --- a/src/components/ui/table/data-table-faceted-filter.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import type { Column } from "@tanstack/react-table"; -import { Check, PlusCircle } from "lucide-react"; - -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, - CommandSeparator, -} from "@/components/ui/command"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { Separator } from "@/components/ui/separator"; -import { cn } from "@/lib/utils"; -import { Option } from "@/types/table"; - -interface DataTableFacetedFilterProps { - column?: Column; - title?: string; - options: Option[]; -} - -export function DataTableFacetedFilter({ - column, - title, - options, -}: DataTableFacetedFilterProps) { - const unknownValue = column?.getFilterValue(); - const selectedValues = new Set( - Array.isArray(unknownValue) ? unknownValue : [], - ); - - return ( - - - - - - - - - No results found. - - {options.map((option) => { - const isSelected = selectedValues.has(option.value); - - return ( - { - if (isSelected) { - selectedValues.delete(option.value); - } else { - selectedValues.add(option.value); - } - const filterValues = Array.from(selectedValues); - column?.setFilterValue( - filterValues.length ? filterValues : undefined, - ); - }} - > -
-
- {option.icon && ( -
- ); - })} -
- {selectedValues.size > 0 && ( - <> - - - column?.setFilterValue(undefined)} - className="justify-center text-center" - > - Clear filters - - - - )} -
-
-
-
- ); -} diff --git a/src/components/ui/table/data-table-filter-list.tsx b/src/components/ui/table/data-table-filter-list.tsx deleted file mode 100644 index 6df83da..0000000 --- a/src/components/ui/table/data-table-filter-list.tsx +++ /dev/null @@ -1,767 +0,0 @@ -"use client"; - -import { type Table } from "@tanstack/react-table"; -import { - CalendarIcon, - Check, - ChevronsUpDown, - GripVertical, - ListFilter, - Trash2, -} from "lucide-react"; -import { customAlphabet } from "nanoid"; -import { parseAsStringEnum, useQueryState } from "nuqs"; -import * as React from "react"; - -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { Calendar } from "@/components/ui/calendar"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { - FacetedFilter, - FacetedFilterContent, - FacetedFilterEmpty, - FacetedFilterGroup, - FacetedFilterInput, - FacetedFilterItem, - FacetedFilterList, - FacetedFilterTrigger, -} from "@/components/ui/faceted-filter"; -import { Input } from "@/components/ui/input"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { - Sortable, - SortableDragHandle, - SortableItem, -} from "@/components/ui/sortable"; -import { dataTableConfig } from "@/config/data-table"; -import { useDebouncedCallback } from "@/hooks/use-debounced-callback"; -import { getDefaultFilterOperator, getFilterOperators } from "@/lib/data-table"; -import { getFiltersStateParser } from "@/lib/parsers"; -import { cn, formatDate } from "@/lib/utils"; -import { - DataTableAdvancedFilterField, - Filter, - FilterOperator, - JoinOperator, - StringKeyOf, -} from "@/types/table"; - -interface DataTableFilterListProps { - table: Table; - filterFields: DataTableAdvancedFilterField[]; - debounceMs: number; - shallow?: boolean; -} - -export function DataTableFilterList({ - table, - filterFields, - debounceMs, - shallow, -}: DataTableFilterListProps) { - const id = React.useId(); - const [filters, setFilters] = useQueryState( - "filters", - getFiltersStateParser(table.getRowModel().rows[0]?.original) - .withDefault([]) - .withOptions({ - clearOnDefault: true, - shallow, - }), - ); - - const [joinOperator, setJoinOperator] = useQueryState( - "joinOperator", - parseAsStringEnum(["and", "or"]).withDefault("and").withOptions({ - clearOnDefault: true, - shallow, - }), - ); - - const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs); - - function addFilter() { - const filterField = filterFields[0]; - - if (!filterField) return; - - void setFilters([ - ...filters, - { - id: filterField.id, - value: "", - type: filterField.type, - operator: getDefaultFilterOperator(filterField.type), - rowId: customAlphabet( - "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", - 6, - )(), - }, - ]); - } - - function updateFilter({ - rowId, - field, - debounced = false, - }: { - rowId: string; - field: Partial>; - debounced?: boolean; - }) { - const updateFunction = debounced ? debouncedSetFilters : setFilters; - updateFunction((prevFilters) => { - const updatedFilters = prevFilters.map((filter) => { - if (filter.rowId === rowId) { - const updatedFilter = { ...filter, ...field }; - if ("type" in field) { - updatedFilter.value = ""; - } - if (field.operator === "isEmpty" || field.operator === "isNotEmpty") { - updatedFilter.value = ""; - } - return updatedFilter; - } - return filter; - }); - - return updatedFilters; - }); - } - - function removeFilter(rowId: string) { - const updatedFilters = filters.filter((filter) => filter.rowId !== rowId); - void setFilters(updatedFilters); - } - - function moveFilter(activeIndex: number, overIndex: number) { - void setFilters((prevFilters) => { - const newFilters = [...prevFilters]; - const [removed] = newFilters.splice(activeIndex, 1); - if (!removed) return prevFilters; - newFilters.splice(overIndex, 0, removed); - return newFilters; - }); - } - - function renderFilterInput({ - filter, - inputId, - }: { - filter: Filter; - inputId: string; - }) { - const filterField = filterFields.find((f) => f.id === filter.id); - - if (!filterField) return null; - - if (filter.operator === "isEmpty" || filter.operator === "isNotEmpty") { - return ( -
- ); - } - - switch (filter.type) { - case "text": - case "number": - return ( - - updateFilter({ - rowId: filter.rowId, - field: { value: event.target.value }, - debounced: true, - }) - } - /> - ); - case "select": - return ( - - - - - - - - No options found. - - {filterField?.options?.map((option) => ( - { - updateFilter({ rowId: filter.rowId, field: { value } }); - setTimeout(() => { - document.getElementById(inputId)?.click(); - }, 0); - }} - > - {option.icon && ( - - ))} - - - - - ); - case "multi-select": - const selectedValues = new Set( - Array.isArray(filter.value) ? filter.value : [], - ); - - return ( - - - - - - - - No options found. - - {filterField?.options?.map((option) => ( - { - const currentValue = Array.isArray(filter.value) - ? filter.value - : []; - const newValue = currentValue.includes(value) - ? currentValue.filter((v) => v !== value) - : [...currentValue, value]; - updateFilter({ - rowId: filter.rowId, - field: { value: newValue }, - }); - }} - > - {option.icon && ( - - ))} - - - - - ); - case "date": - const dateValue = Array.isArray(filter.value) - ? filter.value.filter(Boolean) - : [filter.value, filter.value].filter(Boolean); - - const displayValue = - filter.operator === "isBetween" && dateValue.length === 2 - ? `${formatDate(dateValue[0] ?? new Date())} - ${formatDate( - dateValue[1] ?? new Date(), - )}` - : dateValue[0] - ? formatDate(dateValue[0]) - : "Pick a date"; - - return ( - - - - - - {filter.operator === "isBetween" ? ( - { - updateFilter({ - rowId: filter.rowId, - field: { - value: date - ? [ - date.from?.toISOString() ?? "", - date.to?.toISOString() ?? "", - ] - : [], - }, - }); - }} - initialFocus - numberOfMonths={1} - /> - ) : ( - { - updateFilter({ - rowId: filter.rowId, - field: { value: date?.toISOString() ?? "" }, - }); - - setTimeout(() => { - document.getElementById(inputId)?.click(); - }, 0); - }} - initialFocus - /> - )} - - - ); - case "boolean": { - if (Array.isArray(filter.value)) return null; - - return ( - - ); - } - default: - return null; - } - } - - return ( - ({ id: item.rowId }))} - onMove={({ activeIndex, overIndex }) => - moveFilter(activeIndex, overIndex) - } - overlay={ -
-
-
-
-
-
-
-
- } - > - - - - - 0 ? "gap-3.5" : "gap-2", - )} - > - {filters.length > 0 ? ( -

Filters

- ) : ( -
-

No filters applied

-

- Add filters to refine your results. -

-
- )} -
- {filters.map((filter, index) => { - const filterId = `${id}-filter-${filter.rowId}`; - const joinOperatorListboxId = `${filterId}-join-operator-listbox`; - const fieldListboxId = `${filterId}-field-listbox`; - const fieldTriggerId = `${filterId}-field-trigger`; - const operatorListboxId = `${filterId}-operator-listbox`; - const inputId = `${filterId}-input`; - - return ( - -
-
- {index === 0 ? ( - - Where - - ) : index === 1 ? ( - - ) : ( - - {joinOperator} - - )} -
- - - - - - document.getElementById(fieldTriggerId)?.focus() - } - > - - - - No fields found. - - {filterFields.map((field) => ( - { - const column = filterFields.find( - (col) => col.id === value, - ); - if (column) { - updateFilter({ - rowId: filter.rowId, - field: { - id: value as StringKeyOf, - type: column.type, - operator: getDefaultFilterOperator( - column.type, - ), - }, - }); - } - }} - > - - {field.label} - - - - ))} - - - - - - -
- {renderFilterInput({ filter, inputId })} -
- - - -
-
- ); - })} -
-
- - {filters.length > 0 ? ( - - ) : null} -
-
-
- - ); -} diff --git a/src/components/ui/table/data-table-pagination.tsx b/src/components/ui/table/data-table-pagination.tsx deleted file mode 100644 index b28f1da..0000000 --- a/src/components/ui/table/data-table-pagination.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { type Table } from "@tanstack/react-table"; -import { - ChevronLeft, - ChevronRight, - ChevronsLeft, - ChevronsRight, -} from "lucide-react"; - -import { Button } from "@/components/ui/button"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; - -interface DataTablePaginationProps { - table: Table; - pageSizeOptions?: number[]; -} - -export function DataTablePagination({ - table, - pageSizeOptions = [10, 20, 30, 40, 50], -}: DataTablePaginationProps) { - return ( -
-
- {table.getFilteredSelectedRowModel().rows.length} of{" "} - {table.getFilteredRowModel().rows.length} row(s) selected. -
-
-
-

Rows per page

- -
-
- Page {table.getState().pagination.pageIndex + 1} of{" "} - {table.getPageCount()} -
-
- - - - -
-
-
- ); -} diff --git a/src/components/ui/table/data-table-skeleton.tsx b/src/components/ui/table/data-table-skeleton.tsx deleted file mode 100644 index 73be90d..0000000 --- a/src/components/ui/table/data-table-skeleton.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import { Skeleton } from "@/components/ui/skeleton"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { cn } from "@/lib/utils"; - -interface DataTableSkeletonProps extends React.HTMLAttributes { - /** - * The number of columns in the table. - * @type number - */ - columnCount: number; - - /** - * The number of rows in the table. - * @default 10 - * @type number | undefined - */ - rowCount?: number; - - /** - * The number of searchable columns in the table. - * @default 0 - * @type number | undefined - */ - searchableColumnCount?: number; - - /** - * The number of filterable columns in the table. - * @default 0 - * @type number | undefined - */ - filterableColumnCount?: number; - - /** - * Flag to show the table view options. - * @default undefined - * @type boolean | undefined - */ - showViewOptions?: boolean; - - /** - * The width of each cell in the table. - * The length of the array should be equal to the columnCount. - * Any valid CSS width value is accepted. - * @default ["auto"] - * @type string[] | undefined - */ - cellWidths?: string[]; - - /** - * Flag to show the pagination bar. - * @default true - * @type boolean | undefined - */ - withPagination?: boolean; - - /** - * Flag to prevent the table cells from shrinking. - * @default false - * @type boolean | undefined - */ - shrinkZero?: boolean; -} - -export function DataTableSkeleton(props: DataTableSkeletonProps) { - const { - columnCount, - rowCount = 10, - searchableColumnCount = 0, - filterableColumnCount = 0, - showViewOptions = true, - cellWidths = ["auto"], - withPagination = true, - shrinkZero = false, - className, - ...skeletonProps - } = props; - - return ( -
-
-
- {searchableColumnCount > 0 - ? Array.from({ length: searchableColumnCount }).map((_, i) => ( - - )) - : null} - {filterableColumnCount > 0 - ? Array.from({ length: filterableColumnCount }).map((_, i) => ( - - )) - : null} -
- {showViewOptions ? ( - - ) : null} -
-
- - - {Array.from({ length: 1 }).map((_, i) => ( - - {Array.from({ length: columnCount }).map((_, j) => ( - - - - ))} - - ))} - - - {Array.from({ length: rowCount }).map((_, i) => ( - - {Array.from({ length: columnCount }).map((_, j) => ( - - - - ))} - - ))} - -
-
- {withPagination ? ( -
- -
-
- - -
-
- -
-
- - - - -
-
-
- ) : null} -
- ); -} diff --git a/src/components/ui/table/data-table-sort-list.tsx b/src/components/ui/table/data-table-sort-list.tsx deleted file mode 100644 index 6cb4cce..0000000 --- a/src/components/ui/table/data-table-sort-list.tsx +++ /dev/null @@ -1,348 +0,0 @@ -"use client"; - -import type { SortDirection, Table } from "@tanstack/react-table"; -import { - ArrowDownUp, - Check, - ChevronsUpDown, - GripVertical, - Trash2, -} from "lucide-react"; -import { useQueryState } from "nuqs"; -import * as React from "react"; - -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { - Sortable, - SortableDragHandle, - SortableItem, -} from "@/components/ui/sortable"; -import { dataTableConfig } from "@/config/data-table"; -import { useDebouncedCallback } from "@/hooks/use-debounced-callback"; -import { getSortingStateParser } from "@/lib/parsers"; -import { cn, toSentenceCase } from "@/lib/utils"; -import { - ExtendedColumnSort, - ExtendedSortingState, - StringKeyOf, -} from "@/types/table"; - -interface DataTableSortListProps { - table: Table; - debounceMs: number; - shallow?: boolean; -} - -export function DataTableSortList({ - table, - debounceMs, - shallow, -}: DataTableSortListProps) { - const id = React.useId(); - - const initialSorting = (table.initialState.sorting ?? - []) as ExtendedSortingState; - - const [sorting, setSorting] = useQueryState( - "sort", - getSortingStateParser(table.getRowModel().rows[0]?.original) - .withDefault(initialSorting) - .withOptions({ - clearOnDefault: true, - shallow, - }), - ); - - const uniqueSorting = sorting.filter( - (sort, index, self) => index === self.findIndex((t) => t.id === sort.id), - ); - - const debouncedSetSorting = useDebouncedCallback(setSorting, debounceMs); - - const sortableColumns = table - .getAllColumns() - .filter((column) => column.getCanSort()) - .map((column) => ({ - id: column.id, - label: toSentenceCase(column.id), - selected: sorting?.some((s) => s.id === column.id), - })); - - function addSort() { - const firstAvailableColumn = sortableColumns.find( - (column) => !sorting.some((s) => s.id === column.id), - ); - if (!firstAvailableColumn) return; - - void setSorting([ - ...sorting, - { - id: firstAvailableColumn.id as StringKeyOf, - desc: false, - }, - ]); - } - - function updateSort({ - field, - index, - debounced = false, - }: { - field: Partial>; - index: number; - debounced?: boolean; - }) { - const updateFunction = debounced ? debouncedSetSorting : setSorting; - - updateFunction((prevSorting) => { - if (!prevSorting) return prevSorting; - - const updatedSorting = prevSorting.map((sort, i) => - i === index ? { ...sort, ...field } : sort, - ); - return updatedSorting; - }); - } - - function removeSort(id: string) { - void setSorting((prevSorting) => - prevSorting.filter((item) => item.id !== id), - ); - } - - return ( - -
-
-
-
-
- } - > - - - - - 0 ? "gap-3.5" : "gap-2", - )} - > - {uniqueSorting.length > 0 ? ( -

Sort by

- ) : ( -
-

No sorting applied

-

- Add sorting to organize your results. -

-
- )} -
-
- {uniqueSorting.map((sort, index) => { - const sortId = `${id}-sort-${sort.id}`; - const fieldListboxId = `${sortId}-field-listbox`; - const fieldTriggerId = `${sortId}-field-trigger`; - const directionListboxId = `${sortId}-direction-listbox`; - - return ( - -
- - - - - - document.getElementById(fieldTriggerId)?.focus() - } - > - - - - No fields found. - - {sortableColumns - .filter( - (column) => - !column.selected || column.id === sort.id, - ) - .map((column) => ( - - updateSort({ - field: { - id: value as StringKeyOf, - }, - index, - }) - } - > - - {column.label} - - - - ))} - - - - - - - - - -
-
- ); - })} -
-
-
- - {sorting.length > 0 ? ( - - ) : null} -
-
-
- - ); -} diff --git a/src/components/ui/table/data-table-toolbar.tsx b/src/components/ui/table/data-table-toolbar.tsx deleted file mode 100644 index 031daa6..0000000 --- a/src/components/ui/table/data-table-toolbar.tsx +++ /dev/null @@ -1,119 +0,0 @@ -"use client"; - -import type { Table } from "@tanstack/react-table"; -import { X } from "lucide-react"; -import * as React from "react"; - -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { DataTableFacetedFilter } from "@/components/ui/table/data-table-faceted-filter"; -import { DataTableViewOptions } from "@/components/ui/table/data-table-view-options"; -import { cn } from "@/lib/utils"; -import { DataTableFilterField } from "@/types/table"; - -interface DataTableToolbarProps - extends React.HTMLAttributes { - table: Table; - /** - * An array of filter field configurations for the data table. - * When options are provided, a faceted filter is rendered. - * Otherwise, a search filter is rendered. - * - * @example - * const filterFields = [ - * { - * id: 'name', - * label: 'Name', - * placeholder: 'Filter by name...' - * }, - * { - * id: 'status', - * label: 'Status', - * options: [ - * { label: 'Active', value: 'active', icon: ActiveIcon, count: 10 }, - * { label: 'Inactive', value: 'inactive', icon: InactiveIcon, count: 5 } - * ] - * } - * ] - */ - filterFields?: DataTableFilterField[]; -} - -export function DataTableToolbar({ - table, - filterFields = [], - children, - className, - ...props -}: DataTableToolbarProps) { - const isFiltered = table.getState().columnFilters.length > 0; - - // Memoize computation of searchableColumns and filterableColumns - const { searchableColumns, filterableColumns } = React.useMemo(() => { - return { - searchableColumns: filterFields.filter((field) => !field.options), - filterableColumns: filterFields.filter((field) => field.options), - }; - }, [filterFields]); - - return ( -
-
- {searchableColumns.length > 0 && - searchableColumns.map( - (column) => - table.getColumn(column.id ? String(column.id) : "") && ( - - table - .getColumn(String(column.id)) - ?.setFilterValue(event.target.value) - } - className="h-8 w-40 lg:w-64" - /> - ), - )} - {filterableColumns.length > 0 && - filterableColumns.map( - (column) => - table.getColumn(column.id ? String(column.id) : "") && ( - - ), - )} - {isFiltered && ( - - )} -
-
- {children} - -
-
- ); -} diff --git a/src/components/ui/table/data-table-view-options.tsx b/src/components/ui/table/data-table-view-options.tsx deleted file mode 100644 index 98a85a8..0000000 --- a/src/components/ui/table/data-table-view-options.tsx +++ /dev/null @@ -1,91 +0,0 @@ -"use client"; - -import type { Table } from "@tanstack/react-table"; -import { Check, ChevronsUpDown, Settings2 } from "lucide-react"; -import * as React from "react"; - -import { Button } from "@/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList, -} from "@/components/ui/command"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; -import { cn, toSentenceCase } from "@/lib/utils"; - -interface DataTableViewOptionsProps { - table: Table; -} - -export function DataTableViewOptions({ - table, -}: DataTableViewOptionsProps) { - const triggerRef = React.useRef(null); - - return ( - - - - - triggerRef.current?.focus()} - > - - - - No columns found. - - {table - .getAllColumns() - .filter( - (column) => - typeof column.accessorFn !== "undefined" && - column.getCanHide(), - ) - .map((column) => { - return ( - - column.toggleVisibility(!column.getIsVisible()) - } - > - - {toSentenceCase(column.id)} - - - - ); - })} - - - - - - ); -} diff --git a/src/components/ui/table/data-table.tsx b/src/components/ui/table/data-table.tsx deleted file mode 100644 index 1a92d94..0000000 --- a/src/components/ui/table/data-table.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { flexRender, type Table as TanstackTable } from "@tanstack/react-table"; -import * as React from "react"; - -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { DataTablePagination } from "@/components/ui/table/data-table-pagination"; -import { getCommonPinningStyles } from "@/lib/data-table"; -import { cn } from "@/lib/utils"; - -interface DataTableProps extends React.HTMLAttributes { - /** - * The table instance returned from useDataTable hook with pagination, sorting, filtering, etc. - * @type TanstackTable - */ - table: TanstackTable; - - /** - * The floating bar to render at the bottom of the table on row selection. - * @default null - * @type React.ReactNode | null - * @example floatingBar={} - */ - floatingBar?: React.ReactNode | null; -} - -export function DataTable({ - table, - floatingBar = null, - children, - className, - ...props -}: DataTableProps) { - return ( -
- {children} -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), - )} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ))} - - )) - ) : ( - - - No results. - - - )} - -
-
-
- - {table.getFilteredSelectedRowModel().rows.length > 0 && floatingBar} -
-
- ); -} diff --git a/src/config/data-table.ts b/src/config/data-table.ts deleted file mode 100644 index 50c3423..0000000 --- a/src/config/data-table.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Pickaxe, SquareSquare } from "lucide-react"; - -export type DataTableConfig = typeof dataTableConfig; - -export const dataTableConfig = { - featureFlags: [ - { - label: "Advanced table", - value: "advancedTable" as const, - icon: Pickaxe, - tooltipTitle: "Toggle advanced table", - tooltipDescription: "A filter and sort builder to filter and sort rows.", - }, - { - label: "Floating bar", - value: "floatingBar" as const, - icon: SquareSquare, - tooltipTitle: "Toggle floating bar", - tooltipDescription: "A floating bar that sticks to the top of the table.", - }, - ], - textOperators: [ - { label: "Contains", value: "iLike" as const }, - { label: "Does not contain", value: "notILike" as const }, - { label: "Is", value: "eq" as const }, - { label: "Is not", value: "ne" as const }, - { label: "Is empty", value: "isEmpty" as const }, - { label: "Is not empty", value: "isNotEmpty" as const }, - ], - numericOperators: [ - { label: "Is", value: "eq" as const }, - { label: "Is not", value: "ne" as const }, - { label: "Is less than", value: "lt" as const }, - { label: "Is less than or equal to", value: "lte" as const }, - { label: "Is greater than", value: "gt" as const }, - { label: "Is greater than or equal to", value: "gte" as const }, - { label: "Is empty", value: "isEmpty" as const }, - { label: "Is not empty", value: "isNotEmpty" as const }, - ], - dateOperators: [ - { label: "Is", value: "eq" as const }, - { label: "Is not", value: "ne" as const }, - { label: "Is before", value: "lt" as const }, - { label: "Is after", value: "gt" as const }, - { label: "Is on or before", value: "lte" as const }, - { label: "Is on or after", value: "gte" as const }, - { label: "Is between", value: "isBetween" as const }, - { label: "Is relative to today", value: "isRelativeToToday" as const }, - { label: "Is empty", value: "isEmpty" as const }, - { label: "Is not empty", value: "isNotEmpty" as const }, - ], - selectOperators: [ - { label: "Is", value: "eq" as const }, - { label: "Is not", value: "ne" as const }, - { label: "Is empty", value: "isEmpty" as const }, - { label: "Is not empty", value: "isNotEmpty" as const }, - ], - booleanOperators: [ - { label: "Is", value: "eq" as const }, - { label: "Is not", value: "ne" as const }, - ], - joinOperators: [ - { label: "And", value: "and" as const }, - { label: "Or", value: "or" as const }, - ], - sortOrders: [ - { label: "Asc", value: "asc" as const }, - { label: "Desc", value: "desc" as const }, - ], - columnTypes: [ - "text", - "number", - "date", - "boolean", - "select", - "multi-select", - ] as const, - globalOperators: [ - "iLike", - "notILike", - "eq", - "ne", - "isEmpty", - "isNotEmpty", - "lt", - "lte", - "gt", - "gte", - "isBetween", - "isRelativeToToday", - "and", - "or", - ] as const, -}; diff --git a/src/hooks/use-data-table.ts b/src/hooks/use-data-table.ts deleted file mode 100644 index 88e4a27..0000000 --- a/src/hooks/use-data-table.ts +++ /dev/null @@ -1,356 +0,0 @@ -"use client"; - -import { - type ColumnFiltersState, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - type PaginationState, - type RowSelectionState, - type SortingState, - type TableOptions, - type TableState, - type Updater, - useReactTable, - type VisibilityState, -} from "@tanstack/react-table"; -import { - parseAsArrayOf, - parseAsInteger, - parseAsString, - type Parser, - useQueryState, - type UseQueryStateOptions, - useQueryStates, -} from "nuqs"; -import * as React from "react"; - -import { useDebouncedCallback } from "@/hooks/use-debounced-callback"; -import { getSortingStateParser } from "@/lib/parsers"; -import { DataTableFilterField, ExtendedSortingState } from "@/types/table"; - -interface UseDataTableProps - extends Omit< - TableOptions, - | "state" - | "pageCount" - | "getCoreRowModel" - | "manualFiltering" - | "manualPagination" - | "manualSorting" - >, - Required, "pageCount">> { - /** - * Defines filter fields for the table. Supports both dynamic faceted filters and search filters. - * - Faceted filters are rendered when `options` are provided for a filter field. - * - Otherwise, search filters are rendered. - * - * The indie filter field `value` represents the corresponding column name in the database table. - * @default [] - * @type { label: string, value: keyof TData, placeholder?: string, options?: { label: string, value: string, icon?: React.ComponentType<{ className?: string }> }[] }[] - * @example - * ```ts - * // Render a search filter - * const filterFields = [ - * { label: "Title", value: "title", placeholder: "Search titles" } - * ]; - * // Render a faceted filter - * const filterFields = [ - * { - * label: "Status", - * value: "status", - * options: [ - * { label: "Todo", value: "todo" }, - * { label: "In Progress", value: "in-progress" }, - * ] - * } - * ]; - * ``` - */ - filterFields?: DataTableFilterField[]; - - /** - * Determines how query updates affect history. - * `push` creates a new history entry; `replace` (default) updates the current entry. - * @default "replace" - */ - history?: "push" | "replace"; - - /** - * Indicates whether the page should scroll to the top when the URL changes. - * @default false - */ - scroll?: boolean; - - /** - * Shallow mode keeps query states client-side, avoiding server calls. - * Setting to `false` triggers a network request with the updated querystring. - * @default true - */ - shallow?: boolean; - - /** - * Maximum time (ms) to wait between URL query string updates. - * Helps with browser rate-limiting. Minimum effective value is 50ms. - * @default 50 - */ - throttleMs?: number; - - /** - * Debounce time (ms) for filter updates to enhance performance during rapid input. - * @default 300 - */ - debounceMs?: number; - - /** - * Observe Server Component loading states for non-shallow updates. - * Pass `startTransition` from `React.useTransition()`. - * Sets `shallow` to `false` automatically. - * So shallow: true` and `startTransition` cannot be used at the same time. - * @see https://react.dev/reference/react/useTransition - */ - startTransition?: React.TransitionStartFunction; - - /** - * Clear URL query key-value pair when state is set to default. - * Keep URL meaning consistent when defaults change. - * @default false - */ - clearOnDefault?: boolean; - - /** - * Enable notion like column filters. - * Advanced filters and column filters cannot be used at the same time. - * @default false - * @type boolean - */ - enableAdvancedFilter?: boolean; - - initialState?: Omit, "sorting"> & { - // Extend to make the sorting id typesafe - sorting?: ExtendedSortingState; - }; -} - -export function useDataTable({ - pageCount = -1, - filterFields = [], - enableAdvancedFilter = false, - history = "replace", - scroll = false, - shallow = true, - throttleMs = 50, - debounceMs = 300, - clearOnDefault = false, - startTransition, - initialState, - ...props -}: UseDataTableProps) { - const queryStateOptions = React.useMemo< - Omit, "parse"> - >(() => { - return { - history, - scroll, - shallow, - throttleMs, - debounceMs, - clearOnDefault, - startTransition, - }; - }, [ - history, - scroll, - shallow, - throttleMs, - debounceMs, - clearOnDefault, - startTransition, - ]); - - const [rowSelection, setRowSelection] = React.useState( - initialState?.rowSelection ?? {}, - ); - const [columnVisibility, setColumnVisibility] = - React.useState(initialState?.columnVisibility ?? {}); - - const [page, setPage] = useQueryState( - "page", - parseAsInteger.withOptions(queryStateOptions).withDefault(1), - ); - const [perPage, setPerPage] = useQueryState( - "perPage", - parseAsInteger - .withOptions(queryStateOptions) - .withDefault(initialState?.pagination?.pageSize ?? 10), - ); - const [sorting, setSorting] = useQueryState( - "sort", - getSortingStateParser() - .withOptions(queryStateOptions) - .withDefault(initialState?.sorting ?? []), - ); - - // Create parsers for each filter field - const filterParsers = React.useMemo(() => { - return filterFields.reduce< - Record | Parser> - >((acc, field) => { - if (field.options) { - // Faceted filter - acc[field.id] = parseAsArrayOf(parseAsString, ",").withOptions( - queryStateOptions, - ); - } else { - // Search filter - acc[field.id] = parseAsString.withOptions(queryStateOptions); - } - return acc; - }, {}); - }, [filterFields, queryStateOptions]); - - const [filterValues, setFilterValues] = useQueryStates(filterParsers); - - const debouncedSetFilterValues = useDebouncedCallback( - setFilterValues, - debounceMs, - ); - - // Paginate - const pagination: PaginationState = { - pageIndex: page - 1, // zero-based index -> one-based index - pageSize: perPage, - }; - - function onPaginationChange(updaterOrValue: Updater) { - if (typeof updaterOrValue === "function") { - const newPagination = updaterOrValue(pagination); - void setPage(newPagination.pageIndex + 1); - void setPerPage(newPagination.pageSize); - } else { - void setPage(updaterOrValue.pageIndex + 1); - void setPerPage(updaterOrValue.pageSize); - } - } - - // Sort - function onSortingChange(updaterOrValue: Updater) { - if (typeof updaterOrValue === "function") { - const newSorting = updaterOrValue(sorting) as ExtendedSortingState; - void setSorting(newSorting); - } - } - - // Filter - const initialColumnFilters: ColumnFiltersState = React.useMemo(() => { - return enableAdvancedFilter - ? [] - : Object.entries(filterValues).reduce( - (filters, [key, value]) => { - if (value !== null) { - filters.push({ - id: key, - value: Array.isArray(value) ? value : [value], - }); - } - return filters; - }, - [], - ); - }, [filterValues, enableAdvancedFilter]); - - const [columnFilters, setColumnFilters] = - React.useState(initialColumnFilters); - - // Memoize computation of searchableColumns and filterableColumns - const { searchableColumns, filterableColumns } = React.useMemo(() => { - return enableAdvancedFilter - ? { searchableColumns: [], filterableColumns: [] } - : { - searchableColumns: filterFields.filter((field) => !field.options), - filterableColumns: filterFields.filter((field) => field.options), - }; - }, [filterFields, enableAdvancedFilter]); - - const onColumnFiltersChange = React.useCallback( - (updaterOrValue: Updater) => { - // Don't process filters if advanced filtering is enabled - if (enableAdvancedFilter) return; - - setColumnFilters((prev) => { - const next = - typeof updaterOrValue === "function" - ? updaterOrValue(prev) - : updaterOrValue; - - const filterUpdates = next.reduce< - Record - >((acc, filter) => { - if (searchableColumns.find((col) => col.id === filter.id)) { - // For search filters, use the value directly - acc[filter.id] = filter.value as string; - } else if (filterableColumns.find((col) => col.id === filter.id)) { - // For faceted filters, use the array of values - acc[filter.id] = filter.value as string[]; - } - return acc; - }, {}); - - prev.forEach((prevFilter) => { - if (!next.some((filter) => filter.id === prevFilter.id)) { - filterUpdates[prevFilter.id] = null; - } - }); - - void setPage(1); - - debouncedSetFilterValues(filterUpdates); - return next; - }); - }, - [ - debouncedSetFilterValues, - enableAdvancedFilter, - filterableColumns, - searchableColumns, - setPage, - ], - ); - - const table = useReactTable({ - ...props, - initialState, - pageCount, - state: { - pagination, - sorting, - columnVisibility, - rowSelection, - columnFilters: enableAdvancedFilter ? [] : columnFilters, - }, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onPaginationChange, - onSortingChange, - onColumnFiltersChange, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: enableAdvancedFilter - ? undefined - : getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: enableAdvancedFilter ? undefined : getFacetedRowModel(), - getFacetedUniqueValues: enableAdvancedFilter - ? undefined - : getFacetedUniqueValues(), - manualPagination: true, - manualSorting: true, - manualFiltering: true, - }); - - return { table }; -} diff --git a/src/hooks/use-fetch-data-values.ts b/src/hooks/use-fetch-data-values.ts deleted file mode 100644 index cbfc6bf..0000000 --- a/src/hooks/use-fetch-data-values.ts +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; - -import React from "react"; - -import { EntityValueDefinition } from "@/types/commons"; - -// Generic hook to load data -export const useFetchData = ( - fetchData: () => Promise>, -) => { - const [items, setItems] = React.useState>([]); - - const loadItems = React.useCallback(async () => { - const data = await fetchData(); - if (data) { - setItems(data.map((it) => it.value)); // Apply transformation to the fetched data - } - }, [fetchData]); - - React.useEffect(() => { - loadItems(); - }, [loadItems]); - - return items; -}; diff --git a/src/lib/actions/accounts.action.ts b/src/lib/actions/accounts.action.ts deleted file mode 100644 index c3fafc1..0000000 --- a/src/lib/actions/accounts.action.ts +++ /dev/null @@ -1,107 +0,0 @@ -"use server"; - -import { revalidatePath, unstable_noStore as noStore } from "next/cache"; -import { redirect } from "next/navigation"; - -import { - deleteExec, - doAdvanceSearch, - get, - post, - put, -} from "@/lib/actions/commons.action"; -import { findEntitiesFilterOptions } from "@/lib/actions/shared.action"; -import { BACKEND_API } from "@/lib/constants"; -import { - AccountDTO, - AccountDTOSchema, - AccountSearchParams, -} from "@/types/accounts"; -import { EntityValueDefinition, PageableResult } from "@/types/commons"; - -export const findAccounts = async (): Promise> => { - return get>(`${BACKEND_API}/api/crm/accounts`); -}; - -export const findAccountStatuses = async (): Promise< - Array -> => { - return get>( - `${BACKEND_API}/api/crm/values?entityType=account&&valueKey=status`, - ); -}; - -export const findAccountStatusesFilterOptions = async () => { - return findEntitiesFilterOptions(findAccountStatuses); -}; - -export const findAccountTypes = async (): Promise< - Array -> => { - return get>( - `${BACKEND_API}/api/crm/values?entityType=account&&valueKey=type`, - ); -}; - -export const findAccountTypesFilterOptions = async () => { - return findEntitiesFilterOptions(findAccountTypes); -}; - -export const findAccountIndustries = async (): Promise< - Array -> => { - return get>( - `${BACKEND_API}/api/crm/values?entityType=account&&valueKey=industry`, - ); -}; - -export const findAccountIndustriesFilterOptions = async () => { - return findEntitiesFilterOptions(findAccountIndustries); -}; - -export const saveOrUpdateAccount = async ( - isEdit: boolean, - account: AccountDTO, -): Promise => { - const validation = AccountDTOSchema.safeParse(account); - - if (validation.success) { - if (isEdit) { - await put( - `${BACKEND_API}/api/crm/accounts/${account.id}`, - account, - ); - } else { - await post( - `${BACKEND_API}/api/crm/accounts`, - account, - ); - } - redirect("/portal/accounts"); - } -}; - -export const findAccountById = async (accountId: number) => { - return get(`${BACKEND_API}/api/crm/accounts/${accountId}`); -}; - -export const findPreviousAccount = async (accountId: number) => { - return get( - `${BACKEND_API}/api/crm/accounts/${accountId}/previous`, - ); -}; - -export const findNextAccount = async (accountId: number) => { - return get(`${BACKEND_API}/api/crm/accounts/${accountId}/next`); -}; - -export async function searchAccounts(input: AccountSearchParams) { - noStore(); - return doAdvanceSearch(`${BACKEND_API}/api/crm/accounts/search`); -} - -export async function deleteAccounts(ids: number[]) { - revalidatePath("/"); - - return deleteExec(`${BACKEND_API}/api/crm/accounts`, ids).then(() => {}); -} diff --git a/src/lib/actions/authorities.action.ts b/src/lib/actions/authorities.action.ts index 5d22e14..0aa868e 100644 --- a/src/lib/actions/authorities.action.ts +++ b/src/lib/actions/authorities.action.ts @@ -25,6 +25,12 @@ export const createAuthority = async (authority: AuthorityDTO) => { ); }; +export const deleteAuthority = async (authority_name: string) => { + return deleteExec( + `${BACKEND_API}/api/authorities/${authority_name}`, + ); +}; + export const findPermissionsByAuthorityName = async (authorityName: string) => { return get>( `${BACKEND_API}/api/authority-permissions/${authorityName}`, diff --git a/src/lib/actions/contacts.action.ts b/src/lib/actions/contacts.action.ts deleted file mode 100644 index 3e0cb52..0000000 --- a/src/lib/actions/contacts.action.ts +++ /dev/null @@ -1,76 +0,0 @@ -"use server"; - -import { redirect } from "next/navigation"; - -import { doAdvanceSearch, get, post, put } from "@/lib/actions/commons.action"; -import { findEntitiesFilterOptions } from "@/lib/actions/shared.action"; -import { BACKEND_API } from "@/lib/constants"; -import { EntityValueDefinition, PageableResult } from "@/types/commons"; -import { ConTactDTO, ContactDTOSchema } from "@/types/contacts"; -import { QueryDTO } from "@/types/query"; - -export const findContactById = async ( - contactId: number, -): Promise => { - return get(`${BACKEND_API}/api/crm/contacts/${contactId}`); -}; - -export const findContactsByAccountId = async ( - accountId: number, -): Promise> => { - return get>( - `${BACKEND_API}/api/crm/contacts/account/${accountId}`, - ); -}; - -export const findContactStatuses = async (): Promise< - Array -> => { - return get>( - `${BACKEND_API}/api/crm/values?entityType=contact&&valueKey=status`, - ); -}; - -export const findContactStatusesFilterOptions = async () => { - return findEntitiesFilterOptions(findContactStatuses); -}; - -export async function searchContacts(query: QueryDTO) { - return doAdvanceSearch( - `${BACKEND_API}/api/crm/contacts/search`, - query, - ); -} - -export const saveOrUpdateContact = async ( - isEdit: boolean, - contact: ConTactDTO, -): Promise => { - const validation = ContactDTOSchema.safeParse(contact); - - if (validation.success) { - if (isEdit) { - await put( - `${BACKEND_API}/api/crm/contacts/${contact.id}`, - contact, - ); - } else { - await post( - `${BACKEND_API}/api/crm/contacts`, - contact, - ); - } - - redirect("/portal/contacts"); - } -}; - -export const findPreviousContact = async (contactId: number) => { - return get( - `${BACKEND_API}/api/crm/contacts/previous/${contactId}`, - ); -}; - -export const findNextContact = async (contactId: number) => { - return get(`${BACKEND_API}/api/crm/contacts/next/${contactId}`); -}; diff --git a/src/lib/data-table.ts b/src/lib/data-table.ts deleted file mode 100644 index 0dd168d..0000000 --- a/src/lib/data-table.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { type Column } from "@tanstack/react-table"; - -import { dataTableConfig } from "@/config/data-table"; -import { ColumnType, Filter, FilterOperator } from "@/types/table"; - -/** - * Generate common pinning styles for a table column. - * - * This function calculates and returns CSS properties for pinned columns in a data table. - * It handles both left and right pinning, applying appropriate styles for positioning, - * shadows, and z-index. The function also considers whether the column is the last left-pinned - * or first right-pinned column to apply specific shadow effects. - * - * @param options - The options for generating pinning styles. - * @param options.column - The column object for which to generate styles. - * @param options.withBorder - Whether to show a box shadow between pinned and scrollable columns. - * @returns A React.CSSProperties object containing the calculated styles. - */ -export function getCommonPinningStyles({ - column, - withBorder = false, -}: { - column: Column; - /** - * Show box shadow between pinned and scrollable columns. - * @default false - */ - withBorder?: boolean; -}): React.CSSProperties { - const isPinned = column.getIsPinned(); - const isLastLeftPinnedColumn = - isPinned === "left" && column.getIsLastColumn("left"); - const isFirstRightPinnedColumn = - isPinned === "right" && column.getIsFirstColumn("right"); - - return { - boxShadow: withBorder - ? isLastLeftPinnedColumn - ? "-4px 0 4px -4px hsl(var(--border)) inset" - : isFirstRightPinnedColumn - ? "4px 0 4px -4px hsl(var(--border)) inset" - : undefined - : undefined, - left: isPinned === "left" ? `${column.getStart("left")}px` : undefined, - right: isPinned === "right" ? `${column.getAfter("right")}px` : undefined, - opacity: isPinned ? 0.97 : 1, - position: isPinned ? "sticky" : "relative", - background: isPinned ? "hsl(var(--background))" : "hsl(var(--background))", - width: column.getSize(), - zIndex: isPinned ? 1 : 0, - }; -} - -/** - * Determine the default filter operator for a given column type. - * - * This function returns the most appropriate default filter operator based on the - * column's data type. For text columns, it returns 'iLike' (case-insensitive like), - * while for all other types, it returns 'eq' (equality). - * - * @param columnType - The type of the column (e.g., 'text', 'number', 'date', etc.). - * @returns The default FilterOperator for the given column type. - */ -export function getDefaultFilterOperator( - columnType: ColumnType, -): FilterOperator { - if (columnType === "text") { - return "iLike"; - } - - return "eq"; -} - -/** - * Retrieve the list of applicable filter operators for a given column type. - * - * This function returns an array of filter operators that are relevant and applicable - * to the specified column type. It uses a predefined mapping of column types to - * operator lists, falling back to text operators if an unknown column type is provided. - * - * @param columnType - The type of the column for which to get filter operators. - * @returns An array of objects, each containing a label and value for a filter operator. - */ -export function getFilterOperators(columnType: ColumnType) { - const operatorMap: Record< - ColumnType, - { label: string; value: FilterOperator }[] - > = { - text: dataTableConfig.textOperators, - number: dataTableConfig.numericOperators, - select: dataTableConfig.selectOperators, - "multi-select": dataTableConfig.selectOperators, - boolean: dataTableConfig.booleanOperators, - date: dataTableConfig.dateOperators, - }; - - return operatorMap[columnType] ?? dataTableConfig.textOperators; -} - -/** - * Filters out invalid or empty filters from an array of filters. - * - * This function processes an array of filters and returns a new array - * containing only the valid filters. A filter is considered valid if: - * - It has an 'isEmpty' or 'isNotEmpty' operator, or - * - Its value is not empty (for array values, at least one element must be present; - * for other types, the value must not be an empty string, null, or undefined) - * - * @param filters - An array of Filter objects to be validated. - * @returns A new array containing only the valid filters. - */ -export function getValidFilters( - filters: Filter[], -): Filter[] { - return filters.filter( - (filter) => - filter.operator === "isEmpty" || - filter.operator === "isNotEmpty" || - (Array.isArray(filter.value) - ? filter.value.length > 0 - : filter.value !== "" && - filter.value !== null && - filter.value !== undefined), - ); -} diff --git a/src/lib/menu-list.ts b/src/lib/menu-list.ts index 492f940..bad942f 100644 --- a/src/lib/menu-list.ts +++ b/src/lib/menu-list.ts @@ -1,4 +1,12 @@ -import { BookLock, Layers, LayoutGrid, LucideIcon, Users } from "lucide-react"; +import { + BookLock, + Layers, + LayoutGrid, + LucideIcon, + ShieldCheck, + UserCog, + Users, +} from "lucide-react"; import { Permission } from "@/providers/permissions-provider"; @@ -74,11 +82,6 @@ export function getMenuList( { groupLabel: "", menus: [ - // { - // href: "/portal/accounts", - // label: "Accounts", - // icon: Building2, - // }, { href: "/portal/teams", label: "Teams", @@ -97,7 +100,7 @@ export function getMenuList( { href: "/portal/settings/authorities", label: "Authorities", - icon: BookLock, + icon: ShieldCheck, }, ], }, diff --git a/src/lib/parsers.ts b/src/lib/parsers.ts deleted file mode 100644 index 18334ab..0000000 --- a/src/lib/parsers.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { type Row } from "@tanstack/react-table"; -import { createParser } from "nuqs/server"; -import { z } from "zod"; - -import { dataTableConfig } from "@/config/data-table"; -import { ExtendedSortingState, Filter } from "@/types/table"; - -export const sortingItemSchema = z.object({ - id: z.string(), - desc: z.boolean(), -}); - -/** - * Creates a parser for TanStack Table sorting state. - * @param originalRow The original row data to validate sorting keys against. - * @returns A parser for TanStack Table sorting state. - */ -export const getSortingStateParser = ( - originalRow?: Row["original"], -) => { - const validKeys = originalRow ? new Set(Object.keys(originalRow)) : null; - - return createParser>({ - parse: (value) => { - try { - const parsed = JSON.parse(value); - const result = z.array(sortingItemSchema).safeParse(parsed); - - if (!result.success) return null; - - if (validKeys && result.data.some((item) => !validKeys.has(item.id))) { - return null; - } - - return result.data as ExtendedSortingState; - } catch { - return null; - } - }, - serialize: (value) => JSON.stringify(value), - eq: (a, b) => - a.length === b.length && - a.every( - (item, index) => - item.id === b[index]?.id && item.desc === b[index]?.desc, - ), - }); -}; - -export const filterSchema = z.object({ - id: z.string(), - value: z.union([z.string(), z.array(z.string())]), - type: z.enum(dataTableConfig.columnTypes), - operator: z.enum(dataTableConfig.globalOperators), - rowId: z.string(), -}); - -/** - * Create a parser for data table filters. - * @param originalRow The original row data to create the parser for. - * @returns A parser for data table filters state. - */ -export const getFiltersStateParser = (originalRow?: Row["original"]) => { - const validKeys = originalRow ? new Set(Object.keys(originalRow)) : null; - - return createParser[]>({ - parse: (value) => { - try { - const parsed = JSON.parse(value); - const result = z.array(filterSchema).safeParse(parsed); - - if (!result.success) return null; - - if (validKeys && result.data.some((item) => !validKeys.has(item.id))) { - return null; - } - - return result.data as Filter[]; - } catch { - return null; - } - }, - serialize: (value) => JSON.stringify(value), - eq: (a, b) => - a.length === b.length && - a.every( - (filter, index) => - filter.id === b[index]?.id && - filter.value === b[index]?.value && - filter.type === b[index]?.type && - filter.operator === b[index]?.operator, - ), - }); -}; diff --git a/src/types/accounts.ts b/src/types/accounts.ts deleted file mode 100644 index 4bbf09e..0000000 --- a/src/types/accounts.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { z } from "zod"; - -export const AccountDTOSchema = z.object({ - id: z.number().nullish(), - name: z.string().min(1), - type: z.string().min(1), - industry: z.string().min(1), - email: z.string().email().nullish(), - addressLine1: z.string().nullish(), - addressLine2: z.string().nullish(), - city: z.string().nullish(), - state: z.string().nullish(), - postalCode: z.string().nullish(), - country: z.string().nullish(), - phoneNumber: z.string().nullish(), - status: z.string().min(1), - parentAccountId: z.number().optional(), - parentAccountName: z.string().optional(), - website: z - .union([ - z.string().url(), // Full URL with scheme - z.string().regex(/^(https?:\/\/)?(www\.)?[\w-]+(\.[\w-]+)+.*$/), // Allow optional scheme - z.string().length(0), // Allow empty string - ]) - .optional(), - annualRevenue: z.string().nullish(), - createdAt: z.string().nullish(), - updatedAt: z.string().nullish(), - notes: z.string().nullish(), -}); - -export type AccountDTO = z.infer; - -export const accountSearchParamsSchema = z.object({ - page: z.coerce.number().default(1), // page number - size: z.coerce.number().default(10), // size per page - sort: z.string().optional(), - name: z.string().optional(), - status: z.string().optional(), - industry: z.string().optional(), - type: z.string().optional(), - operator: z.enum(["and", "or"]).optional(), -}); - -export type AccountSearchParams = z.infer; diff --git a/src/types/authorities.ts b/src/types/authorities.ts index 0d77cf6..32fa79b 100644 --- a/src/types/authorities.ts +++ b/src/types/authorities.ts @@ -6,6 +6,7 @@ export const AuthorityDTOSchema = z descriptiveName: z.string().min(1), systemRole: z.boolean().default(false), description: z.string().optional(), + usersCount: z.onumber(), }) .transform((data) => { // If `name` is null, set it to `descriptiveName` diff --git a/src/types/contacts.ts b/src/types/contacts.ts deleted file mode 100644 index dd2c482..0000000 --- a/src/types/contacts.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { z } from "zod"; - -export const ContactDTOSchema = z.object({ - id: z.number().nullish(), - accountId: z.number().nullish(), - accountName: z.string().nullish(), - firstName: z.string().min(1), - lastName: z.string().min(1), - email: z.string().email().nullish(), - address: z.string().nullish(), - city: z.string().nullish(), - state: z.string().nullish(), - postalCode: z.string().nullish(), - country: z.string().nullish(), - phoneNumber: z.string().nullish(), - position: z.string().nullish(), - createdAt: z.string().nullish(), - updatedAt: z.string().nullish(), - status: z.string().min(1), - notes: z.string().nullish(), -}); - -export type ConTactDTO = z.infer; - -export type ContactSearchSchema = { - account_id: number; -}; diff --git a/src/types/resources.ts b/src/types/resources.ts index 5490275..a519823 100644 --- a/src/types/resources.ts +++ b/src/types/resources.ts @@ -1,11 +1,4 @@ -export type ResourceId = - | "Users" - | "Files" - | "Accounts" - | "Contacts" - | "Teams" - | "Organizations" - | "Authorities"; +export type ResourceId = "Users" | "Teams" | "Organizations" | "Authorities"; export type PermissionLevel = "NONE" | "READ" | "WRITE" | "ACCESS"; diff --git a/src/types/table.ts b/src/types/table.ts deleted file mode 100644 index 61162f6..0000000 --- a/src/types/table.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { ColumnSort, Row } from "@tanstack/react-table"; -import { type z } from "zod"; - -import { type DataTableConfig } from "@/config/data-table"; -import { type filterSchema } from "@/lib/parsers"; - -export type Prettify = { - [K in keyof T]: T[K]; -} & {}; - -export type StringKeyOf = Extract; - -export interface SearchParams { - [key: string]: string | string[] | undefined; -} - -export interface Option { - label: string; - value: string; - icon?: React.ComponentType<{ className?: string }>; - count?: number; -} - -export interface ExtendedColumnSort extends Omit { - id: StringKeyOf; -} - -export type ExtendedSortingState = ExtendedColumnSort[]; - -export type ColumnType = DataTableConfig["columnTypes"][number]; - -export type FilterOperator = DataTableConfig["globalOperators"][number]; - -export type JoinOperator = DataTableConfig["joinOperators"][number]["value"]; - -export interface DataTableFilterField { - id: StringKeyOf; - label: string; - placeholder?: string; - options?: Option[]; -} - -export interface DataTableAdvancedFilterField - extends DataTableFilterField { - type: ColumnType; -} - -export type Filter = Prettify< - Omit, "id"> & { - id: StringKeyOf; - } ->; - -export interface DataTableRowAction { - row: Row; - type: "update" | "delete"; -}