feat(sidebar): add calls section to navigation menu

This commit introduces a new "Calls" section to the main sidebar navigation to provide users with access to call logs.

- Adds a new collapsible "Calls" menu item to the sidebar configuration.
- Includes sub-links for "All Calls", "Incoming", "Outgoing", and "Missed" calls.
- Creates new custom icons (GlobeSearch, CallIncome, CallMade, CallMissed, CallReceived) to support the new menu items.
- Updates `apexcharts` and `react-apexcharts` dependencies to newer versions.
This commit is contained in:
AmirReza Jamali
2025-11-04 14:12:20 +03:30
parent 0084b131f8
commit 2d664795ad
22 changed files with 791 additions and 103 deletions
+2 -4
View File
@@ -21,9 +21,7 @@ import Link from "next/link";
import { Tooltip } from "react-tooltip";
import { useCompanyListPresenter } from "./useCompanyListPresenter";
interface IProps {}
const CompaniesTable = ({}: IProps) => {
const CompaniesTable = () => {
const {
isPending,
pathName,
@@ -47,7 +45,7 @@ const CompaniesTable = ({}: IProps) => {
{columns.map((columns) => (
<TableHead
colSpan={100}
className="text-start font-extrabold uppercase"
className="text-start uppercase"
key={columns}
>
{columns}
@@ -0,0 +1,37 @@
"use client";
import {
Table,
TableBody,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import TableSkeleton from "@/components/ui/TableSkeleton";
import { useContactUsListPresenter } from "./useContactUsListPresenter";
const ContactUsTable = () => {
const { columns, pathname, router, isPending } = useContactUsListPresenter();
return (
<div className="flex flex-col rounded-[10px] bg-white px-7.5 pb-4 pt-7.5 shadow-1 dark:bg-gray-dark dark:shadow-card">
<Table>
<TableHeader>
<TableRow>
{columns.map((columns) => (
<TableHead
colSpan={100}
className="text-start uppercase"
key={columns}
>
{columns}
</TableHead>
))}
</TableRow>
</TableHeader>
{isPending && <TableSkeleton column={columns.length} />}
<TableBody></TableBody>
</Table>
</div>
);
};
export default ContactUsTable;
@@ -0,0 +1,16 @@
"use client";
import { useReadAllContactUs } from "@/hooks/queries/useContactUsQuery";
import { usePathname, useRouter } from "next/navigation";
export const useContactUsListPresenter = () => {
const columns: string[] = [];
const pathname = usePathname();
const router = useRouter();
const { data, isPending } = useReadAllContactUs();
return {
columns,
pathname,
router,
isPending,
data,
};
};
@@ -0,0 +1,73 @@
import InputGroup from "@/components/FormElements/InputGroup";
import { Select } from "@/components/FormElements/select";
import {
FieldValues,
UseFormHandleSubmit,
UseFormRegister,
UseFormSetValue,
UseFormWatch,
} from "react-hook-form";
interface NotificationHistoryListFiltersProps {
filterRegister: UseFormRegister<FieldValues>;
setIsFilterModalOpen: (isOpen: boolean) => void;
isMutating: number;
handleFilterSubmit: UseFormHandleSubmit<FieldValues>;
search: (data: any) => void;
watch: UseFormWatch<FieldValues>;
setValue: UseFormSetValue<any>;
}
const NotificationHistoryListFilters = ({
filterRegister,
setIsFilterModalOpen,
isMutating,
handleFilterSubmit,
search,
}: NotificationHistoryListFiltersProps) => {
return (
<form
className="grid !min-w-full grid-cols-1 gap-4 xl:grid-cols-2"
onSubmit={handleFilterSubmit(search)}
>
<Select
{...filterRegister("seen")}
items={[
{ label: "Seen", value: "true" },
{ label: "Not seen", value: "false" },
]}
label="Seen"
name="seen"
/>
<InputGroup
{...filterRegister("search")}
name="search"
label="search"
placeholder="search"
type="search"
/>
<div className="flex gap-4">
<button
type="submit"
className="mt-6 flex w-fit justify-center rounded-lg bg-primary p-[13px] font-medium capitalize text-white hover:bg-opacity-90"
>
{isMutating ? (
<div className="h-5 w-5 animate-spin rounded-full border-b-2 border-gray-1"></div>
) : (
<span>Search</span>
)}
</button>
<button
type="button"
className="mt-6 flex w-fit justify-center rounded-lg bg-error p-[13px] font-medium capitalize text-white hover:bg-opacity-90"
onClick={() => setIsFilterModalOpen(false)}
>
Close
</button>
</div>
</form>
);
};
export default NotificationHistoryListFilters;
@@ -14,10 +14,12 @@ import {
import TableSkeleton from "@/components/ui/TableSkeleton";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import { truncateString, unixToDate } from "@/utils/shared";
import Link from "next/link";
import { Tooltip } from "react-tooltip";
import Boolean from "@/components/ui/Boolean";
import ListHeader from "@/components/ui/ListHeader";
import Modal from "@/components/ui/modal";
import NotificationHistoryListFilters from "./NotificationHistoryListFilters";
import useNotificationHistoryTablePresenter from "./useNotificationHistoryTablePresenter";
const NotificationHistoryTable = () => {
@@ -28,94 +30,122 @@ const NotificationHistoryTable = () => {
notificationHistory,
pathName,
router,
isFilterModalOpen,
setIsFilterModalOpen,
setParams,
handleSubmit,
register,
setValue,
watch,
search,
isMutating,
} = useNotificationHistoryTablePresenter();
return (
<ShowcaseSection className="flex flex-col">
<button className="mb-5 flex w-fit justify-center self-end rounded-lg bg-primary p-[13px] font-medium text-white hover:bg-opacity-90">
<Link href={`${pathName}/create`}>Create Notification </Link>
</button>
<Table>
<TableHeader>
<TableRow>
{columns.map((column) => (
<TableHead colSpan={100} key={column}>
{column}
</TableHead>
))}
</TableRow>
</TableHeader>
{isLoading && <TableSkeleton column={columns.length} />}
<TableBody>
{!notificationHistory?.length ? (
<>
<ShowcaseSection className="flex flex-col">
<ListHeader
createButtonText="Create notification"
onFilter={() => setIsFilterModalOpen(true)}
/>
<Table>
<TableHeader>
<TableRow>
<TableCell>
<h2>No notifications</h2>
</TableCell>
{columns.map((column) => (
<TableHead colSpan={100} key={column}>
{column}
</TableHead>
))}
</TableRow>
) : (
notificationHistory.map((item, index) => (
<TableRow key={item.id}>
<TableCell colSpan={100}>{index + 1}</TableCell>
<TableCell colSpan={100}>{item.title}</TableCell>
<TableCell colSpan={100}>{item.event_type}</TableCell>
<TableCell colSpan={100}>
{unixToDate({
hasTime: true,
unix: item.created_at,
})}
</TableCell>
<TableCell colSpan={100}>{item.recipient?.full_name}</TableCell>
<TableCell colSpan={100} className="truncate">
{truncateString(item.message, 20, 20)}
</TableCell>
<TableCell className="capitalize" colSpan={100}>
{item.priority}
</TableCell>
<TableCell className="capitalize" colSpan={100}>
{item.type}
</TableCell>
<TableCell className="capitalize" colSpan={100}>
<Boolean value={item.seen} />
</TableCell>
<TableCell colSpan={100}>
<Status status={item.status}>{item.status}</Status>
</TableCell>
<TableCell colSpan={100}>
<button
data-tooltip-id="details"
data-tooltip-content="Details"
data-tooltip-place="top"
onClick={() => {
router.push(`${pathName}/${item.id}`);
}}
>
<Tooltip
{..._TooltipDefaultParams({
id: "details",
})}
/>
<EyeIcon />
</button>
</TableHeader>
{isLoading && <TableSkeleton column={columns.length} />}
<TableBody>
{!notificationHistory?.length ? (
<TableRow>
<TableCell>
<h2>No notifications</h2>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
<Pagination
currentPage={metadata?.page ? metadata?.page - 1 : 0}
totalPages={metadata?.pages ?? 1}
onPageChange={(e) => {
setParams((currentParams) => ({
...currentParams,
offset: e.selected * (metadata?.limit ?? 10),
limit: metadata?.limit ?? 10,
}));
}}
/>
</ShowcaseSection>
) : (
notificationHistory.map((item, index) => (
<TableRow key={item.id}>
<TableCell colSpan={100}>{index + 1}</TableCell>
<TableCell colSpan={100}>{item.title}</TableCell>
<TableCell colSpan={100}>{item.event_type}</TableCell>
<TableCell colSpan={100}>
{unixToDate({
hasTime: true,
unix: item.created_at,
})}
</TableCell>
<TableCell colSpan={100}>
{item.recipient?.full_name}
</TableCell>
<TableCell colSpan={100} className="truncate">
{truncateString(item.message, 20, 20)}
</TableCell>
<TableCell className="capitalize" colSpan={100}>
{item.priority}
</TableCell>
<TableCell className="capitalize" colSpan={100}>
{item.type}
</TableCell>
<TableCell className="capitalize" colSpan={100}>
<Boolean value={item.seen} />
</TableCell>
<TableCell colSpan={100}>
<Status status={item.status}>{item.status}</Status>
</TableCell>
<TableCell colSpan={100}>
<button
data-tooltip-id="details"
data-tooltip-content="Details"
data-tooltip-place="top"
onClick={() => {
router.push(`${pathName}/${item.id}`);
}}
>
<Tooltip
{..._TooltipDefaultParams({
id: "details",
})}
/>
<EyeIcon />
</button>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
<Pagination
currentPage={metadata?.page ? metadata?.page - 1 : 0}
totalPages={metadata?.pages ?? 1}
onPageChange={(e) => {
setParams((currentParams) => ({
...currentParams,
offset: e.selected * (metadata?.limit ?? 10),
limit: metadata?.limit ?? 10,
}));
}}
/>
</ShowcaseSection>
<Modal
isOpen={isFilterModalOpen}
onClose={() => setIsFilterModalOpen(false)}
showButtons={false}
>
<NotificationHistoryListFilters
filterRegister={register}
handleFilterSubmit={handleSubmit}
isMutating={isMutating}
watch={watch}
setValue={setValue}
setIsFilterModalOpen={setIsFilterModalOpen}
search={search}
/>
</Modal>
</>
);
};
@@ -1,15 +1,20 @@
"use client";
import { apiDefaultParams } from "@/constants/Api_Params";
import { useGetNotificationHistoryQuery } from "@/hooks/queries/useNotificationQueries";
import { deleteEmptyKeys } from "@/utils/shared";
import { useIsMutating } from "@tanstack/react-query";
import { usePathname, useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
const useNotificationHistoryTablePresenter = () => {
const { register, handleSubmit, watch, setValue } = useForm();
const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
const [params, setParams] = useState<Record<string, any>>({
...apiDefaultParams,
});
const { data, isLoading } = useGetNotificationHistoryQuery(params);
const isMutating = useIsMutating();
const columns: string[] = [
"row",
"title",
@@ -25,6 +30,14 @@ const useNotificationHistoryTablePresenter = () => {
];
const pathName = usePathname();
const router = useRouter();
const search = (data: any) => {
const newParams = { ...params, ...data, offset: 0 };
const cleanedParams = deleteEmptyKeys(newParams);
setParams(cleanedParams);
const queryString = new URLSearchParams(cleanedParams).toString();
router.push(`${pathName}?${queryString}`);
setIsFilterModalOpen(false);
};
return {
notificationHistory: data?.data,
metadata: data?.meta,
@@ -33,6 +46,14 @@ const useNotificationHistoryTablePresenter = () => {
router,
pathName,
setParams,
register,
handleSubmit,
watch,
setValue,
isFilterModalOpen,
setIsFilterModalOpen,
isMutating,
search,
};
};