feat(companies): implement companies page with data table

This commit introduces a new "Companies" page to the dashboard, providing a comprehensive view of company data in a sortable and paginated table.

Key changes include:
- A new page at `/companies` to display the data table.
- A new API route `/api/companies` that serves mock company data.
- A `CompaniesTable` component built with `react-table` featuring sorting, pagination, and search functionality.
- Reusable `TablePagination` and `TableActions` components to support the table.
- The `moment` library has been added as a dependency to format dates within the table.
- A new `CrossIcon` has been added for UI controls.
- The `InputGroup` component is enhanced with an `autoComplete` prop.
- A link to the new "Companies" page has been added to the sidebar navigation.
This commit is contained in:
AmirReza Jamali
2025-09-16 16:01:59 +03:30
parent feac2ddcb9
commit 2fa774b6bd
21 changed files with 350 additions and 24 deletions
@@ -0,0 +1,67 @@
import {
Dispatch,
FC,
RefObject,
SetStateAction,
useEffect,
useState,
} from "react";
import { useForm } from "react-hook-form";
import { TAssignCustomerToCompanyCredentials } from "@/lib/api";
import { useCompanyFullList } from "@/hooks/queries";
import { ILabelValue } from "@/types/shared";
import { Select } from "@/components/FormElements/select";
import { Building } from "@/components/Layouts/sidebar/icons";
import { FormErrorMessages } from "@/constants/Texts";
interface IProps {
setCompanies: Dispatch<
SetStateAction<TAssignCustomerToCompanyCredentials | null>
>;
}
const AssignToCompanyModalContent: FC<IProps> = ({ setCompanies }) => {
const [options, setOptions] = useState<ILabelValue[]>([]);
const { data, status, isSuccess } = useCompanyFullList();
const { handleSubmit, register } =
useForm<TAssignCustomerToCompanyCredentials>();
useEffect(() => {
if (isSuccess) {
setOptions(
data.data.companies
? data.data.companies?.map((item) => ({
label: item.name,
value: item.id,
}))
: [],
);
}
}, [status]);
const assignCompany = (data: TAssignCustomerToCompanyCredentials) => {
setCompanies(data);
};
return (
<form className="mt-5" onSubmit={handleSubmit(assignCompany)}>
<Select
{...register("company_ids", {
required: { message: FormErrorMessages.required, value: true },
onChange: (e) => {
setCompanies({
company_ids: [e.target.value],
});
},
})}
name="company_ids"
label="Companies"
items={options}
prefixIcon={<Building />}
placeholder="Please Select company"
/>
</form>
);
};
export default AssignToCompanyModalContent;
+39 -2
View File
@@ -10,8 +10,13 @@ import {
TableHeader,
TableRow,
} from "@/components/ui/table";
import { PencilSquareIcon, TrashIcon } from "@/assets/icons";
import { PencilSquareIcon, TrashIcon, UserIcon } from "@/assets/icons";
import ConfirmationModal from "@/components/ui/ConfirmationModal";
import AssignToCompanyModalContent from "./AssignToCompanyModalContent";
import Modal from "@/components/ui/modal";
import { TAssignCustomerToCompanyCredentials } from "@/lib/api";
import { toast } from "react-toastify";
const CustomersTable = () => {
const {
isPending,
@@ -21,15 +26,21 @@ const CustomersTable = () => {
allCustomers,
modalConfig,
isModalOpen,
assignSelectedCompanies,
selectedCompanies,
currentCustomer,
setSelectedCompanies,
setIsModalOpen,
onConfirmDelete,
setCurrentCustomer,
isAssignCompanyModalOpen,
setIsAssignCompanyModalOpen,
} = useCustomerListPresenter();
if (isPending) return <CustomersSkeleton />;
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">
<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 Company</Link>
<Link href={`${pathName}/create`}>Create Customer </Link>
</button>
<Table>
<TableHeader>
@@ -83,12 +94,38 @@ const CustomersTable = () => {
<span className="sr-only">Edit Company </span>
<PencilSquareIcon />
</button>
<button
className="hover:text-green-dark"
onClick={() => {
setCurrentCustomer(customer);
setIsAssignCompanyModalOpen(true);
}}
>
<span className="sr-only">Assign To Company </span>
<UserIcon />
</button>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Modal
onConfirm={() => {
if (!selectedCompanies || !currentCustomer) {
toast.error("Invalid credentials");
return;
}
assignSelectedCompanies({
credentials: selectedCompanies,
id: currentCustomer?.id ?? "",
});
}}
isOpen={isAssignCompanyModalOpen}
onClose={() => setIsAssignCompanyModalOpen(false)}
>
<AssignToCompanyModalContent setCompanies={setSelectedCompanies} />
</Modal>
{isModalOpen && (
<ConfirmationModal
isOpen={isModalOpen}
@@ -15,7 +15,8 @@ import { z } from "zod";
const useCustomerListPresenter = () => {
const deleteModal = useRef<HTMLDialogElement | null>(null);
const assignToCompanyModal = useRef<HTMLDialogElement | null>(null);
const [isAssignCompanyModalOpen, setIsAssignCompanyModalOpen] =
useState(false);
const { inView } = useInView({ threshold: 0.5 });
const [selectedCompanies, setSelectedCompanies] =
useState<TAssignCustomerToCompanyCredentials | null>(null);
@@ -53,7 +54,7 @@ const useCustomerListPresenter = () => {
const { mutate: assignSelectedCompanies } = useAssignCompany(
currentCustomer?.id ?? "",
() => {
assignToCompanyModal.current?.close();
setIsAssignCompanyModalOpen(false);
},
);
useEffect(() => {
@@ -101,7 +102,8 @@ const useCustomerListPresenter = () => {
selectedCompanies,
setSelectedCompanies,
onConfirmDelete,
assignToCompanyModal,
setIsAssignCompanyModalOpen,
isAssignCompanyModalOpen,
assignSelectedCompanies,
pathName,
router,