Solved an issue where, for developers, because of the amount of permissions, the cookie was too big, so I separated the permissions logic into a hook
This commit is contained in:
@@ -4,364 +4,306 @@ import Select from "@/components/Low/Select";
|
||||
import useCodes from "@/hooks/useCodes";
|
||||
import useUser from "@/hooks/useUser";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import { Code, User } from "@/interfaces/user";
|
||||
import { USER_TYPE_LABELS } from "@/resources/user";
|
||||
import {
|
||||
createColumnHelper,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import {Code, User} from "@/interfaces/user";
|
||||
import {USER_TYPE_LABELS} from "@/resources/user";
|
||||
import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table";
|
||||
import axios from "axios";
|
||||
import moment from "moment";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { BsTrash } from "react-icons/bs";
|
||||
import { toast } from "react-toastify";
|
||||
import {useEffect, useState, useMemo} from "react";
|
||||
import {BsTrash} from "react-icons/bs";
|
||||
import {toast} from "react-toastify";
|
||||
import ReactDatePicker from "react-datepicker";
|
||||
import clsx from "clsx";
|
||||
import { checkAccess } from "@/utils/permissions";
|
||||
import {checkAccess} from "@/utils/permissions";
|
||||
import usePermissions from "@/hooks/usePermissions";
|
||||
|
||||
const columnHelper = createColumnHelper<Code>();
|
||||
|
||||
const CreatorCell = ({ id, users }: { id: string; users: User[] }) => {
|
||||
const [creatorUser, setCreatorUser] = useState<User>();
|
||||
const CreatorCell = ({id, users}: {id: string; users: User[]}) => {
|
||||
const [creatorUser, setCreatorUser] = useState<User>();
|
||||
|
||||
useEffect(() => {
|
||||
setCreatorUser(users.find((x) => x.id === id));
|
||||
}, [id, users]);
|
||||
useEffect(() => {
|
||||
setCreatorUser(users.find((x) => x.id === id));
|
||||
}, [id, users]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{(creatorUser?.type === "corporate"
|
||||
? creatorUser?.corporateInformation?.companyInformation?.name
|
||||
: creatorUser?.name || "N/A") || "N/A"}{" "}
|
||||
{creatorUser && `(${USER_TYPE_LABELS[creatorUser.type]})`}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{(creatorUser?.type === "corporate" ? creatorUser?.corporateInformation?.companyInformation?.name : creatorUser?.name || "N/A") || "N/A"}{" "}
|
||||
{creatorUser && `(${USER_TYPE_LABELS[creatorUser.type]})`}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default function CodeList({ user }: { user: User }) {
|
||||
const [selectedCodes, setSelectedCodes] = useState<string[]>([]);
|
||||
export default function CodeList({user}: {user: User}) {
|
||||
const [selectedCodes, setSelectedCodes] = useState<string[]>([]);
|
||||
|
||||
const [filteredCorporate, setFilteredCorporate] = useState<User | undefined>(
|
||||
user?.type === "corporate" ? user : undefined
|
||||
);
|
||||
const [filterAvailability, setFilterAvailability] = useState<
|
||||
"in-use" | "unused"
|
||||
>();
|
||||
const [filteredCorporate, setFilteredCorporate] = useState<User | undefined>(user?.type === "corporate" ? user : undefined);
|
||||
const [filterAvailability, setFilterAvailability] = useState<"in-use" | "unused">();
|
||||
|
||||
// const [filteredCodes, setFilteredCodes] = useState<Code[]>([]);
|
||||
const {permissions} = usePermissions(user?.id || "");
|
||||
|
||||
const { users } = useUsers();
|
||||
const { codes, reload } = useCodes(
|
||||
user?.type === "corporate" ? user?.id : undefined
|
||||
);
|
||||
// const [filteredCodes, setFilteredCodes] = useState<Code[]>([]);
|
||||
|
||||
const [startDate, setStartDate] = useState<Date | null>(moment("01/01/2023").toDate());
|
||||
const {users} = useUsers();
|
||||
const {codes, reload} = useCodes(user?.type === "corporate" ? user?.id : undefined);
|
||||
|
||||
const [startDate, setStartDate] = useState<Date | null>(moment("01/01/2023").toDate());
|
||||
const [endDate, setEndDate] = useState<Date | null>(moment().endOf("day").toDate());
|
||||
const filteredCodes = useMemo(() => {
|
||||
return codes.filter((x) => {
|
||||
// TODO: if the expiry date is missing, it does not make sense to filter by date
|
||||
// so we need to find a way to handle this edge case
|
||||
if(startDate && endDate && x.expiryDate) {
|
||||
const date = moment(x.expiryDate);
|
||||
if(date.isBefore(startDate) || date.isAfter(endDate)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (filteredCorporate && x.creator !== filteredCorporate.id) return false;
|
||||
if (filterAvailability) {
|
||||
if (filterAvailability === "in-use" && !x.userId) return false;
|
||||
if (filterAvailability === "unused" && x.userId) return false;
|
||||
}
|
||||
const filteredCodes = useMemo(() => {
|
||||
return codes.filter((x) => {
|
||||
// TODO: if the expiry date is missing, it does not make sense to filter by date
|
||||
// so we need to find a way to handle this edge case
|
||||
if (startDate && endDate && x.expiryDate) {
|
||||
const date = moment(x.expiryDate);
|
||||
if (date.isBefore(startDate) || date.isAfter(endDate)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (filteredCorporate && x.creator !== filteredCorporate.id) return false;
|
||||
if (filterAvailability) {
|
||||
if (filterAvailability === "in-use" && !x.userId) return false;
|
||||
if (filterAvailability === "unused" && x.userId) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}, [codes, startDate, endDate, filteredCorporate, filterAvailability]);
|
||||
return true;
|
||||
});
|
||||
}, [codes, startDate, endDate, filteredCorporate, filterAvailability]);
|
||||
|
||||
const toggleCode = (id: string) => {
|
||||
setSelectedCodes((prev) =>
|
||||
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
|
||||
);
|
||||
};
|
||||
const toggleCode = (id: string) => {
|
||||
setSelectedCodes((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]));
|
||||
};
|
||||
|
||||
const toggleAllCodes = (checked: boolean) => {
|
||||
if (checked)
|
||||
return setSelectedCodes(
|
||||
filteredCodes.filter((x) => !x.userId).map((x) => x.code)
|
||||
);
|
||||
const toggleAllCodes = (checked: boolean) => {
|
||||
if (checked) return setSelectedCodes(filteredCodes.filter((x) => !x.userId).map((x) => x.code));
|
||||
|
||||
return setSelectedCodes([]);
|
||||
};
|
||||
return setSelectedCodes([]);
|
||||
};
|
||||
|
||||
const deleteCodes = async (codes: string[]) => {
|
||||
if (
|
||||
!confirm(`Are you sure you want to delete these ${codes.length} code(s)?`)
|
||||
)
|
||||
return;
|
||||
const deleteCodes = async (codes: string[]) => {
|
||||
if (!confirm(`Are you sure you want to delete these ${codes.length} code(s)?`)) return;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
codes.forEach((code) => params.append("code", code));
|
||||
const params = new URLSearchParams();
|
||||
codes.forEach((code) => params.append("code", code));
|
||||
|
||||
axios
|
||||
.delete(`/api/code?${params.toString()}`)
|
||||
.then(() => {
|
||||
toast.success(`Deleted the codes!`);
|
||||
setSelectedCodes([]);
|
||||
})
|
||||
.catch((reason) => {
|
||||
if (reason.response.status === 404) {
|
||||
toast.error("Code not found!");
|
||||
return;
|
||||
}
|
||||
axios
|
||||
.delete(`/api/code?${params.toString()}`)
|
||||
.then(() => {
|
||||
toast.success(`Deleted the codes!`);
|
||||
setSelectedCodes([]);
|
||||
})
|
||||
.catch((reason) => {
|
||||
if (reason.response.status === 404) {
|
||||
toast.error("Code not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (reason.response.status === 403) {
|
||||
toast.error("You do not have permission to delete this code!");
|
||||
return;
|
||||
}
|
||||
if (reason.response.status === 403) {
|
||||
toast.error("You do not have permission to delete this code!");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.error("Something went wrong, please try again later.");
|
||||
})
|
||||
.finally(reload);
|
||||
};
|
||||
toast.error("Something went wrong, please try again later.");
|
||||
})
|
||||
.finally(reload);
|
||||
};
|
||||
|
||||
const deleteCode = async (code: Code) => {
|
||||
if (!confirm(`Are you sure you want to delete this "${code.code}" code?`))
|
||||
return;
|
||||
const deleteCode = async (code: Code) => {
|
||||
if (!confirm(`Are you sure you want to delete this "${code.code}" code?`)) return;
|
||||
|
||||
axios
|
||||
.delete(`/api/code/${code.code}`)
|
||||
.then(() => toast.success(`Deleted the "${code.code}" exam`))
|
||||
.catch((reason) => {
|
||||
if (reason.response.status === 404) {
|
||||
toast.error("Code not found!");
|
||||
return;
|
||||
}
|
||||
axios
|
||||
.delete(`/api/code/${code.code}`)
|
||||
.then(() => toast.success(`Deleted the "${code.code}" exam`))
|
||||
.catch((reason) => {
|
||||
if (reason.response.status === 404) {
|
||||
toast.error("Code not found!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (reason.response.status === 403) {
|
||||
toast.error("You do not have permission to delete this code!");
|
||||
return;
|
||||
}
|
||||
if (reason.response.status === 403) {
|
||||
toast.error("You do not have permission to delete this code!");
|
||||
return;
|
||||
}
|
||||
|
||||
toast.error("Something went wrong, please try again later.");
|
||||
})
|
||||
.finally(reload);
|
||||
};
|
||||
toast.error("Something went wrong, please try again later.");
|
||||
})
|
||||
.finally(reload);
|
||||
};
|
||||
|
||||
const allowedToDelete = checkAccess(
|
||||
user,
|
||||
["developer", "admin", "corporate", "mastercorporate"],
|
||||
"deleteCodes"
|
||||
);
|
||||
const allowedToDelete = checkAccess(user, ["developer", "admin", "corporate", "mastercorporate"], permissions, "deleteCodes");
|
||||
|
||||
const defaultColumns = [
|
||||
columnHelper.accessor("code", {
|
||||
id: "codeCheckbox",
|
||||
header: () => (
|
||||
<Checkbox
|
||||
disabled={filteredCodes.filter((x) => !x.userId).length === 0}
|
||||
isChecked={
|
||||
selectedCodes.length ===
|
||||
filteredCodes.filter((x) => !x.userId).length &&
|
||||
filteredCodes.filter((x) => !x.userId).length > 0
|
||||
}
|
||||
onChange={(checked) => toggleAllCodes(checked)}
|
||||
>
|
||||
{""}
|
||||
</Checkbox>
|
||||
),
|
||||
cell: (info) =>
|
||||
!info.row.original.userId ? (
|
||||
<Checkbox
|
||||
isChecked={selectedCodes.includes(info.getValue())}
|
||||
onChange={() => toggleCode(info.getValue())}
|
||||
>
|
||||
{""}
|
||||
</Checkbox>
|
||||
) : null,
|
||||
}),
|
||||
columnHelper.accessor("code", {
|
||||
header: "Code",
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("creationDate", {
|
||||
header: "Creation Date",
|
||||
cell: (info) =>
|
||||
info.getValue() ? moment(info.getValue()).format("DD/MM/YYYY") : "N/A",
|
||||
}),
|
||||
columnHelper.accessor("email", {
|
||||
header: "Invited E-mail",
|
||||
cell: (info) => info.getValue() || "N/A",
|
||||
}),
|
||||
columnHelper.accessor("creator", {
|
||||
header: "Creator",
|
||||
cell: (info) => <CreatorCell id={info.getValue()} users={users} />,
|
||||
}),
|
||||
columnHelper.accessor("userId", {
|
||||
header: "Availability",
|
||||
cell: (info) =>
|
||||
info.getValue() ? (
|
||||
<span className="flex gap-1 items-center text-mti-green">
|
||||
<div className="w-2 h-2 rounded-full bg-mti-green" /> In Use
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex gap-1 items-center text-mti-red">
|
||||
<div className="w-2 h-2 rounded-full bg-mti-red" /> Unused
|
||||
</span>
|
||||
),
|
||||
}),
|
||||
{
|
||||
header: "",
|
||||
id: "actions",
|
||||
cell: ({ row }: { row: { original: Code } }) => {
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
{allowedToDelete && !row.original.userId && (
|
||||
<div
|
||||
data-tip="Delete"
|
||||
className="cursor-pointer tooltip"
|
||||
onClick={() => deleteCode(row.original)}
|
||||
>
|
||||
<BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
const defaultColumns = [
|
||||
columnHelper.accessor("code", {
|
||||
id: "codeCheckbox",
|
||||
header: () => (
|
||||
<Checkbox
|
||||
disabled={filteredCodes.filter((x) => !x.userId).length === 0}
|
||||
isChecked={
|
||||
selectedCodes.length === filteredCodes.filter((x) => !x.userId).length && filteredCodes.filter((x) => !x.userId).length > 0
|
||||
}
|
||||
onChange={(checked) => toggleAllCodes(checked)}>
|
||||
{""}
|
||||
</Checkbox>
|
||||
),
|
||||
cell: (info) =>
|
||||
!info.row.original.userId ? (
|
||||
<Checkbox isChecked={selectedCodes.includes(info.getValue())} onChange={() => toggleCode(info.getValue())}>
|
||||
{""}
|
||||
</Checkbox>
|
||||
) : null,
|
||||
}),
|
||||
columnHelper.accessor("code", {
|
||||
header: "Code",
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("creationDate", {
|
||||
header: "Creation Date",
|
||||
cell: (info) => (info.getValue() ? moment(info.getValue()).format("DD/MM/YYYY") : "N/A"),
|
||||
}),
|
||||
columnHelper.accessor("email", {
|
||||
header: "Invited E-mail",
|
||||
cell: (info) => info.getValue() || "N/A",
|
||||
}),
|
||||
columnHelper.accessor("creator", {
|
||||
header: "Creator",
|
||||
cell: (info) => <CreatorCell id={info.getValue()} users={users} />,
|
||||
}),
|
||||
columnHelper.accessor("userId", {
|
||||
header: "Availability",
|
||||
cell: (info) =>
|
||||
info.getValue() ? (
|
||||
<span className="flex gap-1 items-center text-mti-green">
|
||||
<div className="w-2 h-2 rounded-full bg-mti-green" /> In Use
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex gap-1 items-center text-mti-red">
|
||||
<div className="w-2 h-2 rounded-full bg-mti-red" /> Unused
|
||||
</span>
|
||||
),
|
||||
}),
|
||||
{
|
||||
header: "",
|
||||
id: "actions",
|
||||
cell: ({row}: {row: {original: Code}}) => {
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
{allowedToDelete && !row.original.userId && (
|
||||
<div data-tip="Delete" className="cursor-pointer tooltip" onClick={() => deleteCode(row.original)}>
|
||||
<BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const table = useReactTable({
|
||||
data: filteredCodes,
|
||||
columns: defaultColumns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
const table = useReactTable({
|
||||
data: filteredCodes,
|
||||
columns: defaultColumns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between pb-4 pt-1">
|
||||
<div className="flex items-center gap-4">
|
||||
<Select
|
||||
className="!w-96 !py-1"
|
||||
disabled={user?.type === "corporate"}
|
||||
isClearable
|
||||
placeholder="Corporate"
|
||||
value={
|
||||
filteredCorporate
|
||||
? {
|
||||
label: `${
|
||||
filteredCorporate.type === "corporate"
|
||||
? filteredCorporate.corporateInformation
|
||||
?.companyInformation?.name || filteredCorporate.name
|
||||
: filteredCorporate.name
|
||||
} (${USER_TYPE_LABELS[filteredCorporate.type]})`,
|
||||
value: filteredCorporate.id,
|
||||
}
|
||||
: null
|
||||
}
|
||||
options={users
|
||||
.filter((x) =>
|
||||
["admin", "developer", "corporate"].includes(x.type)
|
||||
)
|
||||
.map((x) => ({
|
||||
label: `${
|
||||
x.type === "corporate"
|
||||
? x.corporateInformation?.companyInformation?.name || x.name
|
||||
: x.name
|
||||
} (${USER_TYPE_LABELS[x.type]})`,
|
||||
value: x.id,
|
||||
user: x,
|
||||
}))}
|
||||
onChange={(value) =>
|
||||
setFilteredCorporate(
|
||||
value ? users.find((x) => x.id === value?.value) : undefined
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Select
|
||||
className="!w-96 !py-1"
|
||||
placeholder="Availability"
|
||||
isClearable
|
||||
options={[
|
||||
{ label: "In Use", value: "in-use" },
|
||||
{ label: "Unused", value: "unused" },
|
||||
]}
|
||||
onChange={(value) =>
|
||||
setFilterAvailability(
|
||||
value ? (value.value as typeof filterAvailability) : undefined
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ReactDatePicker
|
||||
dateFormat="dd/MM/yyyy"
|
||||
className="px-4 py-6 w-full text-sm text-center font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none"
|
||||
selected={startDate}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
selectsRange
|
||||
showMonthDropdown
|
||||
filterDate={(date: Date) =>
|
||||
moment(date).isSameOrBefore(moment(new Date()))
|
||||
}
|
||||
onChange={([initialDate, finalDate]: [Date, Date]) => {
|
||||
setStartDate(initialDate ?? moment("01/01/2023").toDate());
|
||||
if (finalDate) {
|
||||
// basicly selecting a final day works as if I'm selecting the first
|
||||
// minute of that day. this way it covers the whole day
|
||||
setEndDate(moment(finalDate).endOf("day").toDate());
|
||||
return;
|
||||
}
|
||||
setEndDate(null);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{allowedToDelete && (
|
||||
<div className="flex gap-4 items-center">
|
||||
<span>{selectedCodes.length} code(s) selected</span>
|
||||
<Button
|
||||
disabled={selectedCodes.length === 0}
|
||||
variant="outline"
|
||||
color="red"
|
||||
className="!py-1 px-10"
|
||||
onClick={() => deleteCodes(selectedCodes)}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<table className="rounded-xl bg-mti-purple-ultralight/40 w-full">
|
||||
<thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th className="p-4 text-left" key={header.id}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody className="px-2">
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<tr
|
||||
className="odd:bg-white even:bg-mti-purple-ultralight/40 rounded-lg py-2"
|
||||
key={row.id}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td className="px-4 py-2" key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between pb-4 pt-1">
|
||||
<div className="flex items-center gap-4">
|
||||
<Select
|
||||
className="!w-96 !py-1"
|
||||
disabled={user?.type === "corporate"}
|
||||
isClearable
|
||||
placeholder="Corporate"
|
||||
value={
|
||||
filteredCorporate
|
||||
? {
|
||||
label: `${
|
||||
filteredCorporate.type === "corporate"
|
||||
? filteredCorporate.corporateInformation?.companyInformation?.name || filteredCorporate.name
|
||||
: filteredCorporate.name
|
||||
} (${USER_TYPE_LABELS[filteredCorporate.type]})`,
|
||||
value: filteredCorporate.id,
|
||||
}
|
||||
: null
|
||||
}
|
||||
options={users
|
||||
.filter((x) => ["admin", "developer", "corporate"].includes(x.type))
|
||||
.map((x) => ({
|
||||
label: `${x.type === "corporate" ? x.corporateInformation?.companyInformation?.name || x.name : x.name} (${
|
||||
USER_TYPE_LABELS[x.type]
|
||||
})`,
|
||||
value: x.id,
|
||||
user: x,
|
||||
}))}
|
||||
onChange={(value) => setFilteredCorporate(value ? users.find((x) => x.id === value?.value) : undefined)}
|
||||
/>
|
||||
<Select
|
||||
className="!w-96 !py-1"
|
||||
placeholder="Availability"
|
||||
isClearable
|
||||
options={[
|
||||
{label: "In Use", value: "in-use"},
|
||||
{label: "Unused", value: "unused"},
|
||||
]}
|
||||
onChange={(value) => setFilterAvailability(value ? (value.value as typeof filterAvailability) : undefined)}
|
||||
/>
|
||||
<ReactDatePicker
|
||||
dateFormat="dd/MM/yyyy"
|
||||
className="px-4 py-6 w-full text-sm text-center font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none"
|
||||
selected={startDate}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
selectsRange
|
||||
showMonthDropdown
|
||||
filterDate={(date: Date) => moment(date).isSameOrBefore(moment(new Date()))}
|
||||
onChange={([initialDate, finalDate]: [Date, Date]) => {
|
||||
setStartDate(initialDate ?? moment("01/01/2023").toDate());
|
||||
if (finalDate) {
|
||||
// basicly selecting a final day works as if I'm selecting the first
|
||||
// minute of that day. this way it covers the whole day
|
||||
setEndDate(moment(finalDate).endOf("day").toDate());
|
||||
return;
|
||||
}
|
||||
setEndDate(null);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{allowedToDelete && (
|
||||
<div className="flex gap-4 items-center">
|
||||
<span>{selectedCodes.length} code(s) selected</span>
|
||||
<Button
|
||||
disabled={selectedCodes.length === 0}
|
||||
variant="outline"
|
||||
color="red"
|
||||
className="!py-1 px-10"
|
||||
onClick={() => deleteCodes(selectedCodes)}>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<table className="rounded-xl bg-mti-purple-ultralight/40 w-full">
|
||||
<thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map((header) => (
|
||||
<th className="p-4 text-left" key={header.id}>
|
||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody className="px-2">
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<tr className="odd:bg-white even:bg-mti-purple-ultralight/40 rounded-lg py-2" key={row.id}>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<td className="px-4 py-2" key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user