diff --git a/apps/admin-panel/app/command-menu.tsx b/apps/admin-panel/app/command-menu.tsx new file mode 100644 index 000000000..8e3530862 --- /dev/null +++ b/apps/admin-panel/app/command-menu.tsx @@ -0,0 +1,360 @@ +"use client" + +import React from "react" +import { usePathname, useRouter } from "next/navigation" +import { HiPlus } from "react-icons/hi" + +import { CreateCustomerDialog } from "./customers/create" +import { CreateDepositDialog } from "./deposits/create" +import { WithdrawalInitiateDialog } from "./withdrawals/initiate" +import { CreateCreditFacilityDialog } from "./credit-facilities/create" +import { CreditFacilityPartialPaymentDialog } from "./credit-facilities/partial-payment" +import { CreateUserDialog } from "./users/create" +import { CreateTermsTemplateDialog } from "./terms-templates/create" +import { CreateCommitteeDialog } from "./committees/create" +import { CreditFacilityDisbursalInitiateDialog } from "./disbursals/create" + +import { PATH_CONFIGS, useCreateContext } from "./create" + +import { + navDashboardItems, + navLoansItems, + navCustomersItems, + navTransactionItems, + navAdminItems, + navFinanceItems, +} from "@/components/app-sidebar/nav-items" + +import { + Command, + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, +} from "@/ui/command" +import { CreditFacilityStatus } from "@/lib/graphql/generated" + +const isItemAllowedOnCurrentPath = ( + allowedPaths: (string | RegExp)[], + currentPath: string, +) => { + return allowedPaths.some((path) => { + if (typeof path === "string") { + return path === currentPath + } else if (path instanceof RegExp) { + return path.test(currentPath) + } + return false + }) +} + +const allNavItems = [ + ...navDashboardItems, + ...navLoansItems, + ...navCustomersItems, + ...navTransactionItems, + ...navAdminItems, + ...navFinanceItems, +] + +const CommandMenu = () => { + const router = useRouter() + const pathName = usePathname() + + const [open, setOpen] = React.useState(false) + const [pages, setPages] = React.useState<"main" | "navigation">("main") + + const [createCustomer, setCreateCustomer] = React.useState(false) + const [createDeposit, setCreateDeposit] = React.useState(false) + const [createWithdrawal, setCreateWithdrawal] = React.useState(false) + const [createFacility, setCreateFacility] = React.useState(false) + const [initiateDisbursal, setInitiateDisbursal] = React.useState(false) + const [makePayment, setMakePayment] = React.useState(false) + const [openCreateUserDialog, setOpenCreateUserDialog] = React.useState(false) + const [openCreateTermsTemplateDialog, setOpenCreateTermsTemplateDialog] = + React.useState(false) + const [openCreateCommitteeDialog, setOpenCreateCommitteeDialog] = React.useState(false) + + const { customer, facility, setCustomer } = useCreateContext() + + const userIsInCustomerDetailsPage = Boolean(pathName.match(/^\/customers\/.+$/)) + const setCustomerToNullIfNotInCustomerDetails = () => { + if (!userIsInCustomerDetailsPage) setCustomer(null) + } + + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault() + setPages("main") + setOpen((open) => !open) + } + if (e.shiftKey && e.key === "N") { + const activeElement = document.activeElement?.tagName?.toLowerCase() + const ignoredElements = ["input", "textarea", "select"] + if (activeElement && !ignoredElements.includes(activeElement)) { + e.preventDefault() + setOpen((open) => !open) + setPages("navigation") + } + } + } + document.addEventListener("keydown", down) + return () => document.removeEventListener("keydown", down) + }, []) + + const menuItems = [ + { + label: "Create Deposit", + icon: HiPlus, + action: () => { + if (!customer) return + setCreateDeposit(true) + setOpen(false) + }, + allowedPaths: [PATH_CONFIGS.CUSTOMER_DETAILS], + }, + { + label: "Create Withdrawal", + icon: HiPlus, + action: () => { + if (!customer) return + setCreateWithdrawal(true) + setOpen(false) + }, + allowedPaths: [PATH_CONFIGS.CUSTOMER_DETAILS], + }, + { + label: "Create Customer", + icon: HiPlus, + action: () => { + setCreateCustomer(true) + setOpen(false) + }, + allowedPaths: [PATH_CONFIGS.CUSTOMERS, PATH_CONFIGS.CUSTOMER_DETAILS], + }, + { + label: "Create Credit Facility", + icon: HiPlus, + action: () => { + if (!customer) return + setCreateFacility(true) + setOpen(false) + }, + allowedPaths: [PATH_CONFIGS.CUSTOMER_DETAILS], + }, + { + label: "Create Disbursal", + icon: HiPlus, + action: () => { + if (!facility) return + setInitiateDisbursal(true) + setOpen(false) + }, + allowedPaths: [PATH_CONFIGS.CREDIT_FACILITY_DETAILS], + condition: () => facility?.status === CreditFacilityStatus.Active, + }, + { + label: "Make Payment", + icon: HiPlus, + action: () => { + if (!facility) return + setMakePayment(true) + setOpen(false) + }, + allowedPaths: [PATH_CONFIGS.CREDIT_FACILITY_DETAILS], + condition: () => facility?.status === CreditFacilityStatus.Active, + }, + { + label: "Create User", + icon: HiPlus, + action: () => { + setOpenCreateUserDialog(true) + setOpen(false) + }, + allowedPaths: [PATH_CONFIGS.USERS, PATH_CONFIGS.USER_DETAILS], + }, + { + label: "Create Terms Template", + icon: HiPlus, + action: () => { + setOpenCreateTermsTemplateDialog(true) + setOpen(false) + }, + allowedPaths: [PATH_CONFIGS.TERMS_TEMPLATES, PATH_CONFIGS.TERMS_TEMPLATE_DETAILS], + }, + { + label: "Create Committee", + icon: HiPlus, + action: () => { + setOpenCreateCommitteeDialog(true) + setOpen(false) + }, + allowedPaths: [PATH_CONFIGS.COMMITTEES, PATH_CONFIGS.COMMITTEE_DETAILS], + }, + ] + + const availableItems = menuItems.filter((item) => + isItemAllowedOnCurrentPath(item.allowedPaths, pathName), + ) + + return ( + <> + + + + + No results found. + + {pages === "main" ? ( + <> + {availableItems.length > 0 && ( + <> + + + {availableItems.map((item) => ( + { + item.action() + }} + > + + {item.label} + + ))} + + + )} + + + + + Navigation + + Shift +N + + + } + > + {allNavItems.map((item) => ( + { + router.push(item.url) + setOpen(false) + }} + className="flex items-center gap-2" + > + + {item.title} + + ))} + + + ) : ( + + {allNavItems.map((item) => ( + { + setOpen(false) + router.push(item.url) + }} + className="flex items-center gap-2" + > + + {item.title} + + ))} + + )} + + + + + + + + + + + + + {customer && ( + <> + { + setCustomerToNullIfNotInCustomerDetails() + setCreateDeposit(false) + }} + depositAccountId={customer.depositAccount.depositAccountId} + /> + + { + setCustomerToNullIfNotInCustomerDetails() + setCreateWithdrawal(false) + }} + depositAccountId={customer.depositAccount.depositAccountId} + /> + + { + setCustomerToNullIfNotInCustomerDetails() + setCreateFacility(false) + }} + customerId={customer.customerId} + /> + + )} + + {facility && ( + <> + { + setInitiateDisbursal(false) + }} + /> + + { + setMakePayment(false) + }} + /> + + )} + + ) +} + +export { CommandMenu } diff --git a/apps/admin-panel/app/create.tsx b/apps/admin-panel/app/create.tsx index b550a613f..83373b1d5 100644 --- a/apps/admin-panel/app/create.tsx +++ b/apps/admin-panel/app/create.tsx @@ -98,7 +98,7 @@ const CreateButton = () => { } const isButtonDisabled = () => { - if (pathName.includes("credit-facilities")) { + if (PATH_CONFIGS.CREDIT_FACILITY_DETAILS.test(pathName)) { return !facility || facility.status !== CreditFacilityStatus.Active } return false @@ -225,7 +225,11 @@ const CreateButton = () => { }} > - diff --git a/apps/admin-panel/app/credit-facilities/[credit-facility-id]/layout.tsx b/apps/admin-panel/app/credit-facilities/[credit-facility-id]/layout.tsx index b9496fb07..7b913477f 100644 --- a/apps/admin-panel/app/credit-facilities/[credit-facility-id]/layout.tsx +++ b/apps/admin-panel/app/credit-facilities/[credit-facility-id]/layout.tsx @@ -55,10 +55,10 @@ gql` ` const TABS = [ - { url: "/", tabLabel: "Overview" }, - { url: "/terms", tabLabel: "Terms" }, - { url: "/transactions", tabLabel: "Transactions" }, - { url: "/disbursals", tabLabel: "Disbursals" }, + { id: "1", url: "/", tabLabel: "Overview" }, + { id: "2", url: "/terms", tabLabel: "Terms" }, + { id: "3", url: "/transactions", tabLabel: "Transactions" }, + { id: "4", url: "/disbursals", tabLabel: "Disbursals" }, ] export default function CreditFacilityLayout({ @@ -144,7 +144,12 @@ export default function CreditFacilityLayout({ creditFacilityDetails={data.creditFacility} refetch={refetch} /> - + {TABS.map((tab) => ( diff --git a/apps/admin-panel/app/credit-facilities/create.tsx b/apps/admin-panel/app/credit-facilities/create.tsx index edc16fd79..e61f1f86a 100644 --- a/apps/admin-panel/app/credit-facilities/create.tsx +++ b/apps/admin-panel/app/credit-facilities/create.tsx @@ -359,13 +359,14 @@ export const CreateCreditFacilityDialog: React.FC -
setUseTemplateTerms(false)} className="mt-2 flex items-center space-x-2 ml-2 cursor-pointer text-sm hover:underline w-fit" >
Credit Facility Terms
-
+ = ({ custom { label: "Telegram", value: ( -
+
+ + ), }, { diff --git a/apps/admin-panel/app/customers/[customer-id]/layout.tsx b/apps/admin-panel/app/customers/[customer-id]/layout.tsx index 7da56b283..e75359275 100644 --- a/apps/admin-panel/app/customers/[customer-id]/layout.tsx +++ b/apps/admin-panel/app/customers/[customer-id]/layout.tsx @@ -1,6 +1,7 @@ "use client" import { gql } from "@apollo/client" + import { useEffect } from "react" import { CustomerDetailsCard } from "./details" @@ -17,10 +18,10 @@ import { DetailsPageSkeleton } from "@/components/details-page-skeleton" import { ScrollArea, ScrollBar } from "@/ui/scroll-area" const TABS = [ - { url: "/", tabLabel: "Overview" }, - { url: "/credit-facilities", tabLabel: "Credit Facilities" }, - { url: "/transactions", tabLabel: "Transactions" }, - { url: "/documents", tabLabel: "Documents" }, + { id: "1", url: "/", tabLabel: "Overview" }, + { id: "2", url: "/credit-facilities", tabLabel: "Credit Facilities" }, + { id: "3", url: "/transactions", tabLabel: "Transactions" }, + { id: "4", url: "/documents", tabLabel: "Documents" }, ] gql` @@ -50,6 +51,7 @@ export default function CustomerLayout({ }) { const { "customer-id": customerId } = params const { currentTab, handleTabChange } = useTabNavigation(TABS, customerId) + const { setCustomLinks, resetToDefault } = useBreadcrumb() const { setCustomer } = useCreateContext() @@ -87,12 +89,17 @@ export default function CustomerLayout({ return (
- +
{TABS.map((tab) => ( - + {tab.tabLabel} ))} @@ -101,7 +108,7 @@ export default function CustomerLayout({ {TABS.map((tab) => ( - + {children} ))} diff --git a/apps/admin-panel/app/customers/create.tsx b/apps/admin-panel/app/customers/create.tsx index 22b83e5af..5e5c38223 100644 --- a/apps/admin-panel/app/customers/create.tsx +++ b/apps/admin-panel/app/customers/create.tsx @@ -75,7 +75,6 @@ const DetailsForm = ({ value={formData.email} onChange={handleInputChange} disabled={isLoading} - autoFocus />
diff --git a/apps/admin-panel/app/layout.tsx b/apps/admin-panel/app/layout.tsx index ee87a7bba..7ec85b9c3 100644 --- a/apps/admin-panel/app/layout.tsx +++ b/apps/admin-panel/app/layout.tsx @@ -9,6 +9,8 @@ import { AuthSessionProvider } from "./session-provider" import { AppLayout } from "./app-layout" +import { CommandMenu } from "./command-menu" + import ApolloServerWrapper from "@/lib/apollo-client/server-wrapper" import { Toast } from "@/components/toast" import { SidebarProvider, SidebarInset } from "@/ui/sidebar" @@ -56,7 +58,10 @@ export default async function RootLayout({ - {children} + + + {children} + )} diff --git a/apps/admin-panel/components/app-sidebar/index.tsx b/apps/admin-panel/components/app-sidebar/index.tsx index 32e266540..a29051951 100644 --- a/apps/admin-panel/components/app-sidebar/index.tsx +++ b/apps/admin-panel/components/app-sidebar/index.tsx @@ -46,7 +46,7 @@ export function AppSidebar({ appVersion, ...props }: AppSidebarProps) { - +
diff --git a/apps/admin-panel/components/app-sidebar/nav-section.tsx b/apps/admin-panel/components/app-sidebar/nav-section.tsx index 2a562c157..fcfdb0ccf 100644 --- a/apps/admin-panel/components/app-sidebar/nav-section.tsx +++ b/apps/admin-panel/components/app-sidebar/nav-section.tsx @@ -45,7 +45,7 @@ export function NavSection({ items, label }: NavSectionProps) { return ( - + {item.title} diff --git a/apps/admin-panel/components/app-sidebar/user-block.tsx b/apps/admin-panel/components/app-sidebar/user-block.tsx index 8a32a7955..ff1379d8f 100644 --- a/apps/admin-panel/components/app-sidebar/user-block.tsx +++ b/apps/admin-panel/components/app-sidebar/user-block.tsx @@ -59,7 +59,7 @@ export function UserBlock() { - +
{initials}
diff --git a/apps/admin-panel/components/breadcrumb-wrapper.tsx b/apps/admin-panel/components/breadcrumb-wrapper.tsx index fe51fe787..3e3e5c692 100644 --- a/apps/admin-panel/components/breadcrumb-wrapper.tsx +++ b/apps/admin-panel/components/breadcrumb-wrapper.tsx @@ -38,7 +38,11 @@ const BreadCrumbWrapper = ({ links }: FlexibleBreadcrumbProps) => { {link.isCurrentPage ? ( - + @@ -46,6 +50,8 @@ const BreadCrumbWrapper = ({ links }: FlexibleBreadcrumbProps) => {