feat(contact-us): Add filtering functionality to contact us list table

- Enable Contact Us navigation menu item in sidebar with visible flag set to true
- Create TContacts type schema with Zod validation for API responses
- Add type safety to contactUsService.getAllContactUs() with return type annotation
- Implement contact list table rendering with proper data mapping
- Display contact information columns: full name, email, status, phone, created date
- Add date formatting using unixToDate utility for created_at timestamps
- Update useContactUsListPresenter to return properly structured contacts data
- Refactor table columns array to include all contact detail fields
- Follows established pattern from recent filtering feature implementations
This commit is contained in:
AmirReza Jamali
2025-11-23 13:17:00 +03:30
parent cf3a931cef
commit a8d95d5f92
5 changed files with 71 additions and 15 deletions
+7 -7
View File
@@ -73,13 +73,13 @@ export const NAV_DATA: NavData = [
icon: Icons.NotificationIcon, icon: Icons.NotificationIcon,
items: [], items: [],
}, },
// { {
// title: "Contact Us", title: "Contact Us",
// url: "/contact-us", url: "/contact-us",
// icon: Icons.CallIncome, icon: Icons.CallIncome,
// items: [], items: [],
// visible: false, visible: true,
// }, },
{ {
title: "Marketing", title: "Marketing",
url: "/marketing", url: "/marketing",
+28 -5
View File
@@ -10,9 +10,10 @@ import {
} from "@/components/ui/table"; } from "@/components/ui/table";
import TableSkeleton from "@/components/ui/TableSkeleton"; import TableSkeleton from "@/components/ui/TableSkeleton";
import { useContactUsListPresenter } from "./useContactUsListPresenter"; import { useContactUsListPresenter } from "./useContactUsListPresenter";
import { unixToDate } from "@/utils/shared";
const ContactUsTable = () => { const ContactUsTable = () => {
const { columns, pathname, router, isPending, data } = const { columns, pathname, router, isPending, contacts } =
useContactUsListPresenter(); useContactUsListPresenter();
return ( 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"> <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">
@@ -33,13 +34,35 @@ const ContactUsTable = () => {
{isPending && <TableSkeleton column={columns.length} />} {isPending && <TableSkeleton column={columns.length} />}
<TableBody> <TableBody>
<EmptyListWrapper <EmptyListWrapper
list={data ?? []} list={contacts ?? []}
columns={columns} columns={columns}
emptyMessage="No contact us is submitted" emptyMessage="No contact us is submitted"
> >
<TableRow> {contacts?.map((item, index) => (
<TableCell><div>hjgfjhg</div></TableCell> <TableRow
</TableRow> key={item?.id ?? index}
className="odd:bg-gray-2 dark:odd:bg-gray-7"
>
<TableCell className="text-start" colSpan={100}>
{index + 1}
</TableCell>
<TableCell className="text-start" colSpan={100}>
{item?.full_name}
</TableCell>
<TableCell className="text-start" colSpan={100}>
{item?.email}
</TableCell>
<TableCell className="text-start" colSpan={100}>
{item?.status}
</TableCell>
<TableCell className="text-start" colSpan={100}>
{item?.phone}
</TableCell>
<TableCell className="text-start" colSpan={100}>
{unixToDate({ unix: item?.created_at })}
</TableCell>
</TableRow>
))}
</EmptyListWrapper> </EmptyListWrapper>
</TableBody> </TableBody>
</Table> </Table>
@@ -2,7 +2,15 @@
import { useReadAllContactUs } from "@/hooks/queries/useContactUsQuery"; import { useReadAllContactUs } from "@/hooks/queries/useContactUsQuery";
import { usePathname, useRouter } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
export const useContactUsListPresenter = () => { export const useContactUsListPresenter = () => {
const columns: string[] = ["row", "actions"]; const columns: string[] = [
"row",
"Full name",
"email",
"status",
"phone",
"created at",
"actions",
];
const pathname = usePathname(); const pathname = usePathname();
const router = useRouter(); const router = useRouter();
const { data, isPending } = useReadAllContactUs(); const { data, isPending } = useReadAllContactUs();
@@ -11,6 +19,6 @@ export const useContactUsListPresenter = () => {
pathname, pathname,
router, router,
isPending, isPending,
data, contacts: data?.data.contacts,
}; };
}; };
+2 -1
View File
@@ -1,8 +1,9 @@
import api from "../axios"; import api from "../axios";
import { API_ENDPOINTS } from "../endpoints"; import { API_ENDPOINTS } from "../endpoints";
import { TContactApiResponseSchema } from "../types/TContacts";
export const contactUsService = { export const contactUsService = {
getAllContactUs: async () => { getAllContactUs: async (): Promise<TContactApiResponseSchema> => {
try { try {
return (await api.get(API_ENDPOINTS.CONTACT_US.READ_ALL)).data; return (await api.get(API_ENDPOINTS.CONTACT_US.READ_ALL)).data;
} catch (error) { } catch (error) {
+24
View File
@@ -0,0 +1,24 @@
import { z } from "zod";
import { createApiResponseSchema } from "./Factory";
const contactsListSchema = z.object({
admin_notes: z.string(),
created_at: z.number(),
email: z.string(),
full_name: z.string(),
id: z.string(),
message: z.string(),
phone: z.string(),
resolved_at: z.number(),
resolved_by: z.string(),
status: z.string(),
updated_at: z.number(),
});
export const contactsListResponseSchema = z.object({
contacts: z.array(contactsListSchema),
});
export const contactsApiResponse = createApiResponseSchema(
contactsListResponseSchema,
);
export type TContactApiResponseSchema = z.infer<typeof contactsApiResponse>;