New Tasks

This commit is contained in:
Possible
2024-06-25 23:51:51 +01:00
parent ca57235999
commit 967aec7bd1
117 changed files with 9108 additions and 46 deletions
+60
View File
@@ -0,0 +1,60 @@
import React, { useId, useState } from "react";
import classes from "./AddButton.module.css";
import MoonLoader from "react-spinners/MoonLoader";
const AddButton = ({
onClick,
children = "Add New",
showPlus = true,
className,
showChildren = true,
loaderclasses,
color = "#ffffff",
loading = false,
disabled = false,
icon = null,
title,
}) => {
const override = {
borderColor: "#ffffff",
};
const id = useId();
const [animate, setAnimate] = useState(false);
const onClickHandle = () => {
if (onClick) {
onClick();
}
setAnimate(true);
};
// const classes = ` after:bg-[#90EE90] after:block after:absolute after:pt-[300%] after:pl-[350%] after:!ml-[-1.25rem] after:mt-[-120%] after:opacity-0 after:transition-all active:after:p-0 active:after:m-0 active:after:opacity-100 `;
return (
<button
title={title}
disabled={disabled}
type="button"
onAnimationEnd={() => setAnimate(false)}
onClick={onClickHandle}
className={`${animate && "animate-wiggle"} ${
classes.button
} relative flex h-[3rem] cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-[.625rem] border border-primary bg-primary px-[.625rem] py-2 font-inter text-sm font-medium leading-loose tracking-wide text-white ${className}`}
>
<>
<MoonLoader
color={color}
loading={loading}
cssOverride={override}
size={14}
className={loaderclasses}
// aria-label="Loading Spinner"
data-testid={id}
/>
{!loading && icon ? icon : null}
{/* {showPlus ? "+" : null} */}
{showChildren ? children : null}
</>
</button>
);
};
export default AddButton;
@@ -0,0 +1,32 @@
.button {
position: relative;
/* border: none; */
color: #ffffff;
text-align: center;
-webkit-transition-duration: 0.4s; /* Safari */
transition-duration: 0.4s;
text-decoration: none;
overflow: hidden;
cursor: pointer;
/* background-color: #7B1113; */
}
.button:after {
content: "";
background-color: #7b1113;
/* background: #4f46e5; */
display: block;
position: absolute;
padding-top: 300%;
padding-left: 350%;
margin-left: -20px !important;
margin-top: -120%;
opacity: 0;
transition: all 0.8s;
}
.button:active:after {
padding: 0;
margin: 0;
opacity: 1;
transition: 0s;
}
+1
View File
@@ -0,0 +1 @@
export { default as AddButton } from "./AddButton";
@@ -0,0 +1,24 @@
import React from "react";
const DropdownOption = ({
icon,
onClick = () => {},
name,
style = {},
className = "",
}) => {
return (
<div
style={{ ...style }}
className={`flex h-[2.25rem] max-h-[2.25rem] min-h-[2.25rem] w-full cursor-pointer items-center gap-3 px-2 capitalize text-[#262626] hover:bg-[#F4F4F4] hover:text-[#262626] ${className}`}
onClick={(e) => {
onClick(e);
}}
>
{icon && <span className=""> {icon}</span>}
{name && <span className="grow"> {name}</span>}
</div>
);
};
export default DropdownOption;
@@ -0,0 +1,3 @@
import { lazy } from "react";
export const DropdownOption = lazy(() => import("./DropdownOption"));
@@ -0,0 +1,29 @@
import React from "react";
import { KebabIcon } from "Assets/svgs";
const DropdownOptions = ({
icon,
children,
childrenWrapperClass = "",
iconWrapperClass = "",
className = "",
style = {},
}) => {
return (
<div
style={{ ...style }}
className={`relative flex items-center justify-center ${className}`}
>
<button className={`peer relative ${iconWrapperClass}`}>
{icon ? icon : <KebabIcon />}
</button>
<div
className={`absolute right-0 top-[85%] z-[9999999999] m-auto hidden whitespace-nowrap rounded-lg border border-[#a8a8a8] bg-white text-sm text-[#525252] shadow-md hover:block focus:block peer-focus:block peer-focus-visible:block ${childrenWrapperClass} `}
>
{children}
</div>
</div>
);
};
export default DropdownOptions;
+4
View File
@@ -0,0 +1,4 @@
import { lazy } from "react";
export const DropdownOptions = lazy(() => import("./DropdownOptions"));
export { DropdownOption } from "./DropdownOption";
+145
View File
@@ -0,0 +1,145 @@
.toggleClass.react-toggle--checked .react-toggle-track {
background-color: #7b1113;
}
.react-toggle {
touch-action: pan-x;
display: inline-block;
position: relative;
cursor: pointer;
background-color: transparent;
border: 0;
padding: 0;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: #ffffff;
-webkit-tap-highlight-color: transparent;
}
.react-toggle-screenreader-only {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.react-toggle--disabled {
cursor: not-allowed;
opacity: 0.5;
-webkit-transition: opacity 0.25s;
transition: opacity 0.25s;
}
.react-toggle-track {
width: 50px;
height: 24px;
padding: 0;
border-radius: 30px;
background-color: #d4d4d8;
-webkit-transition: all 0.2s ease;
-moz-transition: all 0.2s ease;
transition: all 0.2s ease;
}
.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
/* background-color: #000000; */
}
.react-toggle--checked .react-toggle-track {
background-color: #7b1113;
}
.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
background-color: #7b1113;
}
.react-toggle-track-check {
position: absolute;
width: 14px;
height: 10px;
top: 0px;
bottom: 0px;
margin-top: auto;
margin-bottom: auto;
line-height: 0;
left: 8px;
opacity: 0;
-webkit-transition: opacity 0.25s ease;
-moz-transition: opacity 0.25s ease;
transition: opacity 0.25s ease;
}
.react-toggle--checked .react-toggle-track-check {
opacity: 1;
-webkit-transition: opacity 0.25s ease;
-moz-transition: opacity 0.25s ease;
transition: opacity 0.25s ease;
}
.react-toggle-track-x {
position: absolute;
width: 10px;
height: 10px;
top: 0px;
bottom: 0px;
margin-top: auto;
margin-bottom: auto;
line-height: 0;
right: 10px;
opacity: 1;
-webkit-transition: opacity 0.25s ease;
-moz-transition: opacity 0.25s ease;
transition: opacity 0.25s ease;
}
.react-toggle--checked .react-toggle-track-x {
opacity: 0;
}
.react-toggle-thumb {
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
position: absolute;
top: 1px;
left: 1px;
width: 22px;
height: 22px;
border: 1px solid #4d4d4d;
border-radius: 50%;
background-color: #fafafa;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-transition: all 0.25s ease;
-moz-transition: all 0.25s ease;
transition: all 0.25s ease;
}
.react-toggle--checked .react-toggle-thumb {
left: 27px;
border-color: #7b1113;
}
.react-toggle--focus .react-toggle-thumb {
-webkit-box-shadow: 0px 0px 3px 2px #7b1113;
-moz-box-shadow: 0px 0px 3px 2px #7b1113;
box-shadow: 0px 0px 2px 3px #7b1113;
}
.react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb {
-webkit-box-shadow: 0px 0px 5px 5px #7b1113;
-moz-box-shadow: 0px 0px 5px 5px #7b1113;
box-shadow: 0px 0px 5px 5px #7b1113;
}
+209
View File
@@ -0,0 +1,209 @@
import React, { useId } from "react";
import { StringCaser } from "Utils/utils";
import Toggle from "react-toggle";
import "./MkdInput.css";
import { SkeletonLoader } from "Components/Skeleton";
const MkdInput = ({
type = "text",
page,
cols = "30",
rows = "50",
name,
label,
errors = null,
register = () => ({}),
className,
placeholder,
options = [],
mapping = null,
disabled = false,
value = null,
onChange,
loading = false,
required = false,
}) => {
const uniqueId = useId();
return (
<>
<div
className={`relative grow ${
page === "list" ? "w-full pl-2 pr-2 md:w-1/2" : ""
}`}
>
{["radio", "checkbox", "color", "toggle"].includes(type) ? null : (
<>
{label && (
<label
className="mb-2 block cursor-pointer text-sm "
htmlFor={uniqueId}
>
{label}
{required && (
<sup className="text-[.825rem] text-red-600">*</sup>
)}
{/* {StringCaser(label, { casetype: "capitalize", separator: "space" })} */}
</label>
)}
</>
)}
{loading ? (
<SkeletonLoader
count={1}
counts={[2]}
className={`!h-[3rem] !gap-0 rounded-[.625rem] !bg-[#ebebeb] !p-0 `}
/>
) : type === "textarea" ? (
<>
<textarea
className={`focus:shadow-outline w-full appearance-none rounded border border-soft-200 px-3 py-2 font-inter leading-tight text-black shadow focus:outline-none ${className} ${
errors && errors[name] && errors[name]?.message
? "border-red-500"
: ""
}`}
disabled={disabled}
id={uniqueId}
cols={cols}
name={name}
placeholder={placeholder}
rows={rows}
{...register(name)}
></textarea>
</>
) : ["radio", "checkbox", "color", "toggle"].includes(type) ? (
<div className="flex h-[1.875rem] items-center gap-2 pb-1 pt-3">
{["toggle"].includes(type) ? (
<Toggle
className={`toggle_class ${className}`}
// defaultChecked={}
// checked={selectRoles}
icons={false}
{...(onChange ? { onChange: onChange } : null)}
{...(value ? { checked: value } : null)}
// placeholder={placeholder}
// {...register(name)}
/>
) : (
// <input
// disabled={disabled}
// type={type}
// id={name}
// name={name}
// {...(value ? { value: value } : null)}
// placeholder={placeholder}
// {...register(name)}
// className={`focus:shadow-outline !h-4 !w-4 cursor-pointer appearance-none rounded border border-soft-200 font-inter leading-tight text-primary shadow focus:outline-none focus:ring-0 ${className} ${
// errors && errors[name] && errors[name]?.message
// ? "border-red-500"
// : ""
// } ${
// type === "color" ? "min-h-[3.125rem] min-w-[6.25rem]" : ""
// }`}
// />
<input
disabled={disabled}
type={type}
id={uniqueId}
name={name}
{...(value ? { value: value } : null)}
// {...(value ? { checked: value } : null)}
placeholder={placeholder}
{...register(name)}
className={`focus:shadow-outline !h-4 !w-4 cursor-pointer appearance-none rounded border border-soft-200 font-inter leading-tight text-primary shadow focus:outline-none focus:ring-0 ${className} ${
errors && errors[name] && errors[name]?.message
? "border-red-500"
: ""
} ${
type === "color" ? "min-h-[3.125rem] min-w-[6.25rem]" : ""
}`}
/>
)}
<label
className="mb-2 block h-full cursor-pointer font-inter text-sm font-[500] capitalize text-black"
htmlFor={uniqueId}
>
{label}
{/* {StringCaser(label, {
casetype: "capitalize",
separator: "space",
})} */}
</label>
</div>
) : type === "dropdown" || type === "select" ? (
<select
type={type}
id={uniqueId}
disabled={disabled}
placeholder={placeholder}
{...register(name)}
className={`focus:shadow-outline h-[3rem] w-full appearance-none rounded-[.625rem] border border-soft-200 p-[.625rem] px-3 py-2 font-inter leading-tight text-black shadow focus:outline-none focus:ring-0 ${className} ${
errors && errors[name] && errors[name]?.message
? "border-red-500"
: ""
}`}
>
<option></option>
{options.map((option, key) => (
<option value={option} key={key + 1}>
{option}
</option>
))}
</select>
) : type === "mapping" ? (
<>
{mapping ? (
<select
type={"number"}
id={uniqueId}
disabled={disabled}
{...(value ? { value: value } : null)}
placeholder={placeholder}
{...register(name)}
className={`focus:shadow-outline h-[3rem] w-full appearance-none rounded-[.625rem] border border-soft-200 p-[.625rem] px-3 py-2 font-inter leading-tight text-black shadow focus:outline-none focus:ring-0 ${className} ${
errors && errors[name] && errors[name]?.message
? "border-red-500"
: ""
}`}
>
<option></option>
{options.map((option, key) => (
<option value={option} key={key + 1}>
{mapping[option]}
</option>
))}
</select>
) : (
`Please Pass the mapping e.g {key:value}`
)}
</>
) : (
<input
type={type}
id={uniqueId}
disabled={disabled}
placeholder={placeholder}
{...register(name)}
{...(type === "number" ? { step: "0.01" } : null)}
min={type === "number" ? "0.00" : undefined} //
className={`focus:shadow-outline h-[3rem] w-full appearance-none rounded-[.625rem] border border-soft-200 p-[.625rem] px-3 py-2 font-inter leading-tight text-black shadow focus:outline-none focus:ring-0 ${className} ${
errors && errors[name] && errors[name]?.message
? "border-red-500"
: ""
}`}
/>
)}
{errors && errors[name] && (
<p className="text-field-error absolute inset-x-0 top-full m-auto mt-2 text-[.8rem] italic text-red-500">
{StringCaser(errors[name]?.message, {
casetype: "capitalize",
separator: " ",
})}
</p>
)}
</div>
</>
);
};
export default MkdInput;
+145
View File
@@ -0,0 +1,145 @@
.toggleClass.react-toggle--checked .react-toggle-track {
background-color: #7b1113;
}
.react-toggle {
touch-action: pan-x;
display: inline-block;
position: relative;
cursor: pointer;
background-color: transparent;
border: 0;
padding: 0;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-tap-highlight-color: transparent;
}
.react-toggle-screenreader-only {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.react-toggle--disabled {
cursor: not-allowed;
opacity: 0.5;
-webkit-transition: opacity 0.25s;
transition: opacity 0.25s;
}
.react-toggle-track {
width: 50px;
height: 24px;
padding: 0;
border-radius: 30px;
background-color: #4d4d4d;
-webkit-transition: all 0.2s ease;
-moz-transition: all 0.2s ease;
transition: all 0.2s ease;
}
.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
background-color: #000000;
}
.react-toggle--checked .react-toggle-track {
background-color: #19ab27;
}
.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
background-color: #128d15;
}
.react-toggle-track-check {
position: absolute;
width: 14px;
height: 10px;
top: 0px;
bottom: 0px;
margin-top: auto;
margin-bottom: auto;
line-height: 0;
left: 8px;
opacity: 0;
-webkit-transition: opacity 0.25s ease;
-moz-transition: opacity 0.25s ease;
transition: opacity 0.25s ease;
}
.react-toggle--checked .react-toggle-track-check {
opacity: 1;
-webkit-transition: opacity 0.25s ease;
-moz-transition: opacity 0.25s ease;
transition: opacity 0.25s ease;
}
.react-toggle-track-x {
position: absolute;
width: 10px;
height: 10px;
top: 0px;
bottom: 0px;
margin-top: auto;
margin-bottom: auto;
line-height: 0;
right: 10px;
opacity: 1;
-webkit-transition: opacity 0.25s ease;
-moz-transition: opacity 0.25s ease;
transition: opacity 0.25s ease;
}
.react-toggle--checked .react-toggle-track-x {
opacity: 0;
}
.react-toggle-thumb {
transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
position: absolute;
top: 1px;
left: 1px;
width: 22px;
height: 22px;
border: 1px solid #4d4d4d;
border-radius: 50%;
background-color: #fafafa;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-transition: all 0.25s ease;
-moz-transition: all 0.25s ease;
transition: all 0.25s ease;
}
.react-toggle--checked .react-toggle-thumb {
left: 27px;
border-color: #19ab27;
}
.react-toggle--focus .react-toggle-thumb {
-webkit-box-shadow: 0px 0px 3px 2px #0099e0;
-moz-box-shadow: 0px 0px 3px 2px #0099e0;
box-shadow: 0px 0px 2px 3px #0099e0;
}
.react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb {
-webkit-box-shadow: 0px 0px 5px 5px #0099e0;
-moz-box-shadow: 0px 0px 5px 5px #0099e0;
box-shadow: 0px 0px 5px 5px #0099e0;
}
+3
View File
@@ -0,0 +1,3 @@
export { default as MkdInput } from "./MkdInput";
@@ -0,0 +1,42 @@
table thead {
border-top-left-radius: 0.625rem !important;
border-top-right-radius: 0.625rem !important;
}
table thead tr {
border-top-left-radius: 0.625rem !important;
border-top-right-radius: 0.625rem !important;
background-color: #f4f4f5;
}
tbody tr:last-child {
border-bottom: none;
}
table thead tr:first-child th:first-child {
border-left: 0.0313rem solid #e5e7eb;
border-top: 0.0313rem solid #e5e7eb;
border-top-left-radius: 0.625rem !important;
}
table thead tr:first-child th:last-child {
border-right: 0.0313rem solid #e5e7eb;
border-top: 0.0313rem solid #e5e7eb;
border-top-right-radius: 0.625rem !important;
}
table thead tr:first-child th {
border-top: 0.0313rem solid #e5e7eb;
}
table tbody tr:last-child td:first-child {
border-bottom-left-radius: 0.625rem !important;
}
table tbody tr:last-child td:last-child {
border-bottom-right-radius: 0.625rem !important;
}
table tbody tr td:first-child {
border-left: 0.0313rem solid #e5e7eb;
}
table tbody tr td {
border-right: 0.0313rem solid #e5e7eb;
}
.no-data-found {
filter: grayscale(10);
}
@@ -0,0 +1,339 @@
import React, { Fragment } from "react";
import { useNavigate } from "react-router-dom";
import { DotIcon, KebabIcon, NarrowUpArrowIcon, Spinner } from "Assets/svgs";
import { SkeletonLoader } from "Components/Skeleton";
import { NoDataFoundImg } from "Assets/images";
import MkdListTableSelectRow from "./MkdListTableSelectRow";
import MkdListTableRightActionPanel from "./MkdListTableRightActionPanel";
import MkdListTableRow from "./MkdListTableRow";
const MkdListTable = ({
table,
tableTitle,
onSort,
loading,
columns = [],
actions,
actionPostion = [],
tableRole,
deleteItem,
deleteLoading,
actionId = "id",
showDeleteModal,
currentTableData = [],
setShowDeleteModal,
handleTableCellChange,
setSelectedItems,
allowEditing,
useImage = true,
columnData = null,
setColumns = null,
setColumnData = null,
allowSortColumns = true,
onPopoverStateChange = null,
popoverShown = false,
}) => {
const [deleteId, setIdToDelete] = React.useState(null);
const [isOneOrMoreRowSelected, setIsOneOrMoreRowSelected] =
React.useState(false);
const [areAllRowsSelected, setAreAllRowsSelected] = React.useState(false);
const [selectedIds, setSelectedIds] = React.useState([]);
const [dragging, setDragging] = React.useState(false);
const [fromKey, setFromKey] = React.useState(null);
const [toKey, setToKey] = React.useState(null);
function handleSelectRow(id) {
setIsOneOrMoreRowSelected(true);
const tempIds = selectedIds;
if (actions?.select?.multiple) {
if (tempIds.includes(id)) {
const newIds = tempIds.filter((selectedId) => selectedId !== id);
setSelectedIds(() => [...newIds]);
setSelectedItems(newIds);
} else {
const newIds = [...tempIds, id];
setSelectedIds(() => [...newIds]);
setSelectedItems(newIds);
}
} else {
if (tempIds.includes(id)) {
const newIds = tempIds.filter((selectedId) => selectedId !== id);
setSelectedIds(() => [...newIds]);
setSelectedItems(newIds);
} else {
const newIds = [id];
setSelectedIds(() => [...newIds]);
setSelectedItems(newIds);
}
}
console.log(id);
}
const handleSelectAll = () => {
setAreAllRowsSelected((prevSelectAll) => !prevSelectAll);
if (!areAllRowsSelected) {
const allIds = currentTableData.map((item) => item[actionId]);
setSelectedIds(allIds);
setSelectedItems(allIds);
} else {
setSelectedIds([]);
setSelectedItems([]);
}
};
const handleDeleteAll = async (id) => {
setShowDeleteModal(true);
setIdToDelete(id);
};
const navigate = useNavigate();
const setDeleteId = async (id) => {
console.log("id >>", id);
setShowDeleteModal(true);
setIdToDelete(id);
};
const onDragStart = (e, key) => {
if (!allowSortColumns) return;
// e.preventDefault();
// console.log("onDragStart");
setFromKey(key);
setDragging(true);
};
const onDrop = (e) => {
if (!allowSortColumns) return;
e.preventDefault();
if (fromKey && toKey && fromKey != toKey) {
const tempColumns = [...columns];
const fromColumn = tempColumns[fromKey];
const toColumn = tempColumns[toKey];
tempColumns.splice(fromKey, 1);
tempColumns.splice(toKey, 0, fromColumn);
if (setColumns) {
setColumns(() => tempColumns);
}
}
setToKey(null);
setFromKey(null);
setDragging(false);
};
const onDragOver = (e, key) => {
if (!allowSortColumns) return;
e.preventDefault();
setToKey(key);
// if (fromKey != key) {
// }
};
const onDragEnter = (e) => {
if (!allowSortColumns) return;
e.preventDefault();
};
const onDragEnd = (e) => {
if (!allowSortColumns) return;
e.preventDefault();
setToKey(null);
setFromKey(null);
// console.log("onDragEnd");
setDragging(false);
};
const onDragLeave = (e) => {
if (!allowSortColumns) return;
e.preventDefault();
setToKey(null);
// setFromKey(null);
};
React.useEffect(() => {
if (selectedIds.length <= 0) {
setIsOneOrMoreRowSelected(false);
setAreAllRowsSelected(false);
}
if (selectedIds.length === currentTableData?.length) {
setAreAllRowsSelected(true);
setIsOneOrMoreRowSelected(true);
}
if (
selectedIds.length < currentTableData?.length &&
selectedIds.length > 0
) {
setAreAllRowsSelected(false);
}
}, [selectedIds, currentTableData]);
// console.log("currentTableData");
// console.log(currentTableData);
// console.log(columns)
return (
<>
<div
className={`${
loading || !currentTableData?.length
? "grid-cols-1"
: "grid-cols-[auto_1fr_auto]"
} relative grid !max-h-fit !min-h-fit !w-full justify-center !rounded-[.625rem] border border-soft-200 bg-white`}
>
<MkdListTableSelectRow
actions={actions}
loading={loading}
columns={columns}
currentTableData={currentTableData}
handleSelectAll={handleSelectAll}
areAllRowsSelected={areAllRowsSelected}
handleSelectRow={handleSelectRow}
selectedIds={selectedIds}
actionId={actionId}
/>
{/* BR */}
<div
className={`${
loading ? "" : ""
} relative flex max-h-fit !min-h-fit w-full justify-start overflow-y-clip bg-white ${
!popoverShown ? "overflow-x-auto" : ""
} `}
>
{(loading && useImage) ||
(loading && !columns?.length) ||
(loading && !currentTableData?.length) ? (
<div
className={`max-h-fit min-h-fit w-full min-w-full max-w-full items-center justify-center`}
>
<SkeletonLoader />
</div>
) : columns?.length && currentTableData?.length ? (
<>
{columns?.map((cell, cellIndex) => {
if (
!["row", ""].includes(cell?.accessor) &&
cell?.selected_column
) {
return (
<div key={cellIndex} className="h-full grow">
<div
draggable={allowSortColumns}
onDragStart={(e) => onDragStart(e, cellIndex)}
onDragEnd={onDragEnd}
onDragOver={(e) => onDragOver(e, cellIndex)}
onDragLeave={(e) => onDragLeave(e)}
onDrop={(e) => onDrop(e)}
className={`flex !h-[2.65rem] !max-h-[2.65rem] !min-h-[2.65rem] w-full !min-w-full max-w-fit items-center justify-start gap-2 px-[.75rem] py-[.5rem] ${
allowSortColumns && dragging
? "cursor-grabbing"
: cell?.isSorted
? "cursor-pointer"
: ""
} ${
toKey == cellIndex
? "bg-primary-light"
: "bg-weak-100"
} `}
>
<div
className="flex grow items-center justify-between gap-5"
onClick={
cell?.isSorted ? () => onSort(cellIndex) : undefined
}
>
<div className="w-auto grow whitespace-nowrap capitalize">
{cell.header}
</div>
<span className="w-fit">
{cell.isSorted ? (
<NarrowUpArrowIcon
className={`h-2 w-2 ${
cell.isSortedDesc ? "rotate-180" : ""
}`}
/>
) : (
""
)}
</span>
</div>
{allowSortColumns ? (
<DotIcon className="h-2 w-2 cursor-grab" />
) : null}
</div>
{currentTableData?.map((row, rowIndex) => {
return (
<MkdListTableRow
key={rowIndex}
columnIndex={cellIndex}
row={row}
columns={columns}
column={cell}
currentTableData={currentTableData}
actions={actions}
allowEditing={allowEditing}
handleSelectRow={handleSelectRow}
handleTableCellChange={handleTableCellChange}
actionPostion={actionPostion}
onPopoverStateChange={onPopoverStateChange}
selectedIds={selectedIds}
actionId={actionId}
tableRole={tableRole}
className={`!h-[3rem] !max-h-[3rem] !min-h-[3rem] w-full min-w-full max-w-fit whitespace-nowrap !border-b border-soft-200 bg-white px-[.75rem] py-[.5rem] ${
cellIndex === 0 ? "border-l border-r" : "border-r"
}`}
/>
);
})}
</div>
);
}
})}
</>
) : !loading && !currentTableData?.length ? (
<div className="relative w-full">
<div
className={`relative max-h-fit min-h-fit w-full min-w-fit max-w-full items-center justify-center `}
>
<div
className={`relative ${
useImage
? "h-[31.25rem]"
: "flex h-[6.25rem] items-center justify-center"
} w-full`}
>
{useImage ? (
<img
src={NoDataFoundImg}
className={`no-data-found absolute inset-x-0 m-auto h-full w-[50%] grayscale-[10%]`}
/>
) : (
<>No Data</>
)}
</div>
</div>
</div>
) : null}
</div>
<MkdListTableRightActionPanel
table={table}
tableTitle={tableTitle}
onSort={onSort}
loading={loading}
columns={columns}
actions={actions}
actionPostion={actionPostion}
tableRole={tableRole}
deleteItem={deleteItem}
deleteLoading={deleteLoading}
actionId={actionId}
showDeleteModal={showDeleteModal}
currentTableData={currentTableData}
setShowDeleteModal={setShowDeleteModal}
handleTableCellChange={handleTableCellChange}
setSelectedItems={setSelectedItems}
/>
</div>
</>
);
};
export default MkdListTable;
@@ -0,0 +1,104 @@
export const operations = {
EQUAL: "eq",
NOT_EQUAL: "neq",
IS_NULL: "isn",
IS_NOT_NULL: "isnn",
CONTAINS: "cs",
START_WITH: "sw",
END_WITH: "ew",
LESS_THAN: "lt",
GREATER_THAN: "gt",
};
export const runOperation = (row, column, operator, value) => {
switch (operator) {
case operations.EQUAL:
// TO DO
// return runEqualOperation(row, column, value);
case operations.NOT_EQUAL:
// TO DO
// return runNotEqualOperation(row, column, value);
case operations.IS_NULL:
// TO DO
// return runIsNullOperation(row, column, value);
case operations.IS_NOT_NULL:
// TO DO
// return runIsNotNullOperation(row, column, value);
case operations.CONTAINS:
// TO DO
// return runContainsOperation(row, column, value);
case operations.START_WITH:
// TO DO
// return runStartWithOperation(row, column, value);
case operations.END_WITH:
// TO DO
// return runEndWithOperation(row, column, value);
case operations.GREATER_THAN:
// TO DO
// return runGreaterThanOperation(row, column, value);
case operations.LESS_THAN:
// TO DO
// return runLessThanOperation(row, column, value);
}
};
export const logicalOR = (action, row) => {
if (
!Array.isArray(action?.bind?.column) ||
!Array.isArray(action?.bind?.ifValue)
) {
return false;
}
if (action?.bind?.column?.length !== action?.bind?.ifValue?.length) {
return false;
}
const result = action?.bind?.ifValue.map((value, index) =>
runOperation(
row,
action?.bind?.column[index],
action?.bind?.operator,
value
)
);
return result.some((res) => res === true);
};
export const logicalAND = (action, row) => {
if (
!Array.isArray(action?.bind?.column) ||
!Array.isArray(action?.bind?.ifValue)
) {
return false;
}
if (action?.bind?.column?.length !== action?.bind?.ifValue?.length) {
return false;
}
const result = action?.bind?.ifValue.map((value, index) =>
runOperation(
row,
action?.bind?.column[index],
action?.bind?.operator,
value
)
);
return result.every((res) => res === true);
};
export const processBind = (action, row) => {
if (action?.bind?.logic) {
switch (action?.bind?.logic) {
case "or":
return logicalOR(action, row);
case "and":
return logicalAND(action, row);
}
} else {
return runOperation(
row,
action?.bind?.column,
action?.bind?.operator,
action?.bind?.ifValue
);
}
};
@@ -0,0 +1,52 @@
import React from "react";
import { NarrowUpArrowIcon } from "Assets/svgs";
import { SkeletonLoader } from "Components/Skeleton";
const MkdListTableHead = ({ onSort, columns }) => {
return (
<>
<tr className="flex !h-[2.25rem] !max-h-[2.25rem] !min-h-[2.25rem] overflow-hidden">
{!columns?.length ? (
<th scope="col" className={`!w-full !min-w-full !max-w-full `}>
<SkeletonLoader
count={1}
counts={[1]}
className="!m-0 !h-full !p-0"
/>
</th>
) : null}
{columns.map((column, i) => {
if (column?.accessor !== "" && column?.selected_column) {
return (
<th
key={i}
scope="col"
className={`flex !w-[6.25rem] !min-w-[6.25rem] max-w-[auto] shrink-0 grow cursor-pointer justify-start px-[.75rem] py-[.5rem] text-left text-sm font-[400] capitalize leading-[1.5rem] tracking-wider text-sub-500 ${
column.isSorted ? "cursor-pointer" : ""
} `}
onClick={column.isSorted ? () => onSort(i) : undefined}
>
<div className="flex !w-auto !min-w-fit max-w-[auto] shrink-0 items-center justify-start gap-2">
{column.header}
<span className="shrink-0">
{column.isSorted ? (
<NarrowUpArrowIcon
className={`h-2 w-2 ${
column.isSortedDesc ? "rotate-180" : ""
}`}
/>
) : (
""
)}
</span>
</div>
</th>
);
}
})}
</tr>
</>
);
};
export default MkdListTableHead;
@@ -0,0 +1,96 @@
import React, { Fragment } from "react";
import MkdListTableRowDropdown from "./MkdListTableRowDropdown";
import MkdListTableRowButtons from "./MkdListTableRowButtons";
const MkdListTableRightActionPanel = ({
table,
loading,
columns = [],
actions,
actionPostion = [],
tableRole,
deleteItem,
deleteLoading,
actionId = "id",
showDeleteModal,
currentTableData = [],
setShowDeleteModal,
}) => {
const [deleteId, setIdToDelete] = React.useState(null);
const setDeleteId = async (id) => {
console.log("id >>", id);
setShowDeleteModal(true);
setIdToDelete(id);
};
return (
<Fragment>
{!loading && currentTableData.length && columns.length ? (
<>
{(columns.find((item) => item.accessor === "") &&
actions?.delete?.show) ||
Object.keys(actions).filter(
(key) =>
actions[key]?.show &&
actions[key]?.locations &&
actions[key]?.locations?.length &&
(actions[key]?.locations?.includes("dropdown") ||
actions[key]?.locations?.includes("buttons"))
)?.length ? (
<div className="h-full ">
<div className="grid grid-rows-[auto_1fr]">
<div className="flex !h-[2.65rem] !max-h-[2.65rem] !min-h-[2.65rem] border-b !bg-weak-100 "></div>
<div className="flex flex-col px-1">
{currentTableData?.map((rowData, rowDataIndex) => {
return (
<div
className="flex !h-[3rem] !max-h-[3rem] !min-h-[3rem] w-full items-center border-b"
key={rowDataIndex}
>
{/* // key={rowDataIndex}
// className="!h-[3rem] !max-h-[3rem] !min-h-[3rem] w-fit border-b" */}
{actionPostion?.includes("dropdown") ? (
<>
{/* <KebabIcon className="rotate-90" /> */}
<MkdListTableRowDropdown
actions={actions}
columns={columns}
row={rowData}
setDeleteId={setDeleteId}
// onPopoverStateChange={onPopoverStateChange}
actionId={actionId}
/>
</>
) : null}
{actionPostion?.includes("buttons") ? (
<>
<MkdListTableRowButtons
row={rowData}
actions={actions}
columns={columns}
actionId={actionId}
setDeleteId={setDeleteId}
/>
</>
) : null}
</div>
);
})}
</div>
</div>
</div>
) : null}
</>
) : null}
</Fragment>
);
};
export default MkdListTableRightActionPanel;
// [
// actions?.view?.show,
// actions?.edit?.show,
// actions?.delete?.show,
// actions?.status?.show,
// ].includes(true) ||
@@ -0,0 +1,296 @@
import React from "react";
import { MdPhoto } from "react-icons/md";
import { MkdPopover } from "Components/MkdPopover";
import { NotesIcon } from "Assets/svgs";
import { truncate, processList, mappingValues } from "Utils/utils";
import { GlobalContext } from "Context/Global";
import { AuthContext } from "Context/Auth";
import { SkeletonLoader } from "Components/Skeleton";
const statusNames = {
status: "status",
verify: "verify",
receipt_status: "receipt_status",
};
const MkdListTableRow = ({
column = null,
actions,
tableRole = "",
actionPostion = [],
actionId = "id",
handleTableCellChange,
selectedIds = [],
handleSelectRow,
columnIndex,
allowEditing,
onPopoverStateChange = null,
columns = [],
row,
currentTableData = [],
expandRow = false,
}) => {
const { dispatch } = React.useContext(AuthContext);
const {
dispatch: globalDispatch,
state: {},
} = React.useContext(GlobalContext);
const [listResult, setResult] = React.useState(null);
const showWithLimit = (column) => {
if (expandRow) {
if (["string", "number"].includes(typeof listResult)) {
return (
<span
className={`flex w-fit items-center justify-normal gap-[.25rem] rounded-[.375rem] border border-soft-200 p-[.25rem_.5rem_.25rem_.25rem] capitalize`}
>
{listResult}
</span>
);
}
if (typeof listResult === "object" && Array.isArray(listResult)) {
return (
<>
{listResult.map((item, itemKey) => {
return (
<span
className={`flex w-fit items-center justify-normal gap-[.25rem] rounded-[.375rem] border border-soft-200 p-[.25rem_.5rem_.25rem_.25rem] capitalize`}
key={itemKey}
>
{item}
</span>
);
})}
</>
);
}
} else {
if (["string", "number"].includes(typeof listResult)) {
return (
<span
className={`flex w-fit items-center justify-normal gap-[.25rem] rounded-[.375rem] border border-soft-200 p-[.25rem_.5rem_.25rem_.25rem] capitalize`}
>
{listResult}
</span>
);
}
if (typeof listResult === "object" && Array.isArray(listResult)) {
const lengthToHide =
listResult.length > column.limit
? `+${listResult.length - column?.limit}`
: null;
const itemsToShow = listResult
.map((item, index) => {
if (index + 1 <= column.limit) {
return item;
}
})
.filter(Boolean);
if (lengthToHide) itemsToShow.push(lengthToHide);
return (
<>
{itemsToShow.map((item, itemKey) => {
return (
<span
className={`flex w-fit items-center justify-normal gap-[.25rem] rounded-[.375rem] border border-soft-200 p-[.25rem_.5rem_.25rem_.25rem] capitalize`}
key={itemKey}
>
{item}
</span>
);
})}
</>
);
}
}
};
const getColumnListData = async (value, column) => {
// TO DO the processList function
const result = await processList(value, column, globalDispatch, dispatch);
if (["string", "number"].includes(typeof result)) {
setResult(result);
}
if (typeof result === "object" && Array.isArray(result)) {
setResult(() => [...result]);
}
};
React.useEffect(() => {
if (column?.list && !listResult) {
getColumnListData(row[column?.accessor], column);
}
}, [column?.accessor]);
return (
<div
className={`!h-[3rem] !max-h-[3rem] !min-h-[3rem] w-full min-w-full max-w-fit whitespace-nowrap !border-b border-soft-200 bg-white px-[.75rem] py-[.5rem] ${
columnIndex === 0 ? "border-l border-r" : "border-r"
}`}
>
{column?.accessor?.indexOf("image") > -1 ||
(column?.accessor?.indexOf("photo") > -1 && column?.selected_column) ? (
<LazyLoad>
<MkdPopover
display={<MdPhoto className="peer " />}
openOnClick={false}
zIndex={999999999999999}
onPopoverStateChange={onPopoverStateChange}
place="left-end"
tooltipClasses={`whitespace-nowrap h-fit min-h-[1rem] max-h-fit w-[18.75rem] !rounded-lg border border-[#a8a8a8] !bg-white p-2 text-sm text-[#525252] shadow-md`}
>
<LazyLoad
className={`h-[18.75rem] w-[18.75rem] whitespace-nowrap !rounded-lg border border-[#a8a8a8] !bg-white p-2 text-sm text-[#525252] shadow-md`}
>
<img
src={row[column?.accessor]}
className="w-[18.75rem]"
alt=""
/>
</LazyLoad>
</MkdPopover>
</LazyLoad>
) : (column?.accessor?.indexOf("pdf") > -1 ||
column?.accessor?.indexOf("doc") > -1 ||
column?.accessor?.indexOf("file") > -1 ||
column?.accessor?.indexOf("video") > -1) &&
column?.selected_column ? (
<a
className="text-blue-500"
target="_blank"
href={row[column?.accessor]}
rel="noreferrer"
>
{" "}
View
</a>
) : column?.join && column?.selected_column ? (
<>
{row[column?.join] && row[column?.join][column?.accessor]
? row[column?.join][column?.accessor]
: null}
</>
) : column?.mappingExist &&
["status", "role", "verify", "receipt_status"].includes(
column?.accessor
) &&
!["admin"].includes(tableRole) &&
column?.selected_column ? (
<span
style={{
backgroudColor: column?.mappings[row[column?.accessor]]?.bg,
color: column?.mappings[row[column?.accessor]]?.color,
}}
className={`flex w-fit items-center justify-normal gap-[.25rem] rounded-[.375rem] border border-soft-200 p-[.25rem_.5rem_.25rem_.25rem] capitalize`}
>
{mappingValues[
column?.mappings[row[column?.accessor]]?.toLowerCase()
] ?? column?.mappings[row[column?.accessor]]}
</span>
) : column?.mappingExist &&
allowEditing &&
["admin"].includes(tableRole) &&
column?.selected_column ? (
<select
// onChange={(e) =>
// handleTableCellChange(
// row[actionId],
// e.target.value,
// i,
// column?.accessor
// )
// }
value={row[column?.accessor]}
>
{Object.keys(column?.mappings).map((columnDataKey, index) => (
<option
key={index}
value={columnDataKey}
selected={columnDataKey === row[column?.accessor]}
>
{column?.mappings[columnDataKey]}
</option>
))}
</select>
) : column?.mappingExist &&
!allowEditing &&
["admin"].includes(tableRole) &&
column?.selected_column ? (
<span
style={{
backgroundColor: column?.mappings[row[column?.accessor]]?.bg,
color: column?.mappings[row[column?.accessor]]?.color,
}}
className={`flex w-fit items-center justify-normal gap-[.25rem] rounded-[.375rem] border border-soft-200 p-[.25rem_.5rem_.25rem_.25rem] capitalize`}
>
{mappingValues[
column?.mappings[row[column?.accessor]]?.toLowerCase()
] ?? column?.mappings[row[column?.accessor]]}
</span>
) : !column?.mappingExist &&
column?.accessor !== "id" &&
column?.accessor !== "create_at" &&
column?.accessor !== "update_at" &&
column?.accessor !== "user_id" &&
column?.accessor !== "" &&
allowEditing &&
column?.selected_column ? (
<input
className="text-ellipsis border-0"
type="text"
value={row[column?.accessor]}
// onChange={(e) =>
// handleTableCellChange(
// row[actionId],
// e.target.value,
// i,
// column?.accessor
// )
// }
/>
) : column?.truncate && column?.selected_column ? (
<>{truncate(row[column?.accessor], 50)}</>
) : column?.replace && column?.selected_column ? (
<>{truncate(row[column?.accessor], 30)}</>
) : column?.list && column?.selected_column ? (
<>
{listResult ? (
<div className="flex items-center gap-2">
{showWithLimit(column)}
</div>
) : (
<SkeletonLoader
count={1}
counts={[2]}
className={`!h-[2rem] !gap-0 rounded-[.625rem] !bg-[#ebebeb] !p-0`}
/>
)}
</>
) : column?.isCurrency && column?.selected_column ? (
<>
{column?.currency}
{row[column?.accessor]}
</>
) : ["notes"].includes(column?.accessor) && column?.selected_column ? (
<button type="button" className="flex items-center gap-2 ">
<NotesIcon className="h-[1.0313rem] w-[1.0313rem]" />
View
</button>
) : column?.accessor !== "" && column?.selected_column ? (
<>
{typeof row[column?.accessor] === "object"
? null
: row[column?.accessor]}
</>
) : null}
</div>
);
};
export default MkdListTableRow;
@@ -0,0 +1,80 @@
import React from "react";
import { AddButton } from "Components/AddButton";
import { processBind } from "./MkdListTableBindOperations";
const MkdListTableRowDropdown = ({
row,
columns,
actions,
actionId = "id",
setDeleteId = null,
}) => {
return (
<>
<div className="z-3 relative flex h-fit w-fit items-center gap-2">
{Object.keys(actions)
.filter(
(key) =>
actions[key]?.locations &&
actions[key]?.locations?.includes("buttons")
)
.map((key, keyIndex) => {
if (actions[key]?.bind) {
switch (actions[key]?.bind?.action) {
case "hide":
if (!processBind(actions[key], row)) {
return (
<AddButton
key={keyIndex}
title={actions[key]?.children ?? key}
onClick={() => {
if (["delete"].includes(key)) {
if (setDeleteId) {
setDeleteId(row[actionId]);
}
} else if (actions[key]?.action) {
actions[key]?.action([row[actionId]]);
}
}}
className={`!h-[2rem] !w-[2.0713rem] !border-soft-200 !bg-white`}
>
{actions[key]?.icon
? actions[key]?.icon
: actions[key]?.children ?? key}
</AddButton>
);
}
}
}
if (!actions[key]?.bind) {
return (
<AddButton
key={keyIndex}
title={actions[key]?.children ?? key}
onClick={() => {
if (["delete"].includes(key) && !actions[key]?.action) {
if (setDeleteId) {
setDeleteId(row[actionId]);
}
} else if (actions[key]?.action) {
actions[key]?.action([row[actionId]]);
}
// if (actions[key]?.action) {
// actions[key]?.action([row[actionId]]);
// }
}}
className={`!h-[2rem] !w-[2.0713rem] !border-soft-200 !bg-white`}
>
{actions[key]?.icon
? actions[key]?.icon
: actions[key]?.children ?? key}
</AddButton>
);
}
})}
</div>
</>
);
};
export default MkdListTableRowDropdown;
@@ -0,0 +1,119 @@
import React from "react";
import { AiFillEye } from "react-icons/ai";
import { MkdPopover } from "Components/MkdPopover";
import { DropdownOption } from "Components/DropdownOptions";
import { KebabIcon, TrashIcon, EditIcon2, UpdateIcon } from "Assets/svgs";
import { optionTypes } from "Utils/utils";
import RenderDropdownActions from "./RenderDropdownActions";
import RenderActions from "./RenderActions";
const MkdListTableRowDropdown = ({
row,
columns,
actions,
actionId = "id",
setDeleteId,
onPopoverStateChange = null,
}) => {
return (
<>
<div className="items center z-3 relative flex h-fit w-fit">
<MkdPopover
display={<KebabIcon className="rotate-90" />}
tooltipClasses="!rounded-[.5rem] !min-w-[11rem] !px-0 !right-[3.25rem] !border bg-white"
place={"left-end"}
onPopoverStateChange={onPopoverStateChange}
>
{actions?.edit?.show && (
<DropdownOption
className="bg-white"
icon={<EditIcon2 />}
name={"Edit"}
onClick={() => {
if (actions?.edit?.action) {
actions?.edit?.action([row[actionId]]);
}
}}
/>
)}
{actions?.view?.show && (
<DropdownOption
icon={<AiFillEye className="text-gray-400" />}
name={"View"}
onClick={() => {
if (actions?.view?.action) {
actions?.view?.action([row[actionId]]);
}
}}
/>
)}
{actions?.status?.show && (
<DropdownOption
icon={<UpdateIcon />}
name={getStatusMap(statusRow, statusCol, row)}
onClick={() => {
if (actions?.status?.action) {
actions?.status?.action([row[actionId]]);
}
}}
/>
)}
{actions?.delete?.show && (
<DropdownOption
icon={<TrashIcon />}
name={"Delete"}
onClick={() => {
if (!actions[key]?.action) {
if (setDeleteId) {
setDeleteId(row[actionId]);
}
} else if (actions[key]?.action) {
actions[key]?.action([row[actionId]]);
}
// setDeleteId(row[actionId]);
}}
/>
)}
{Object.keys(actions)
.filter(
(key) =>
actions[key]?.show &&
actions[key]?.locations &&
actions[key]?.locations?.includes("dropdown")
)
.map((key, keyIndex) => {
if (
actions[key]?.type &&
[optionTypes.DROPDOWN].includes(actions[key]?.type)
) {
return (
<RenderDropdownActions
row={row}
key={keyIndex}
actionKey={key}
actionId={actionId}
action={actions[key]}
/>
);
} else if (!actions[key]?.type) {
return (
<RenderActions
row={row}
key={keyIndex}
actionId={actionId}
action={actions[key]}
/>
);
}
})}
</MkdPopover>
</div>
</>
);
};
export default MkdListTableRowDropdown;
@@ -0,0 +1,85 @@
import React, { Fragment } from "react";
const MkdListTableSelectRow = ({
loading,
columns = [],
actions,
currentTableData = [],
areAllRowsSelected,
handleSelectAll,
handleSelectRow,
actionId = "id",
selectedIds,
}) => {
return (
<Fragment>
{!loading && currentTableData?.length && columns.length ? (
<>
{[actions?.select?.show].includes(true) ||
columns.find((item) => item?.accessor === "row") ? (
<div className="">
<div className="flex !h-[2.65rem] !max-h-[2.65rem] !min-h-[2.65rem] w-fit !bg-weak-100 ">
{[actions?.select?.show].includes(true) ? (
<div
className={`!w-[2.65rem] !min-w-[2.65rem] !max-w-[2.65rem] cursor-pointer !bg-weak-100 px-[.75rem] py-[.5rem] text-sm font-[400] capitalize leading-[1.5rem] tracking-wider text-sub-500`}
>
{actions?.select?.multiple ? (
<input
type="checkbox"
disabled={!actions?.select?.multiple}
id="select_all_rows"
className={`focus:shadow-outline mr-1 !h-4 !w-4 cursor-pointer appearance-none rounded border leading-tight text-primary shadow focus:outline-none focus:ring-0`}
checked={areAllRowsSelected}
onChange={handleSelectAll}
/>
) : null}
</div>
) : null}
{columns.find((item) => item.accessor === "row") ? (
<div
className={`flex !w-[2.65rem] !min-w-[2.65rem] !max-w-[2.65rem] cursor-pointer justify-center !bg-weak-100 py-[.5rem] text-sm font-[400] capitalize leading-[1.5rem] tracking-wider text-sub-500 `}
>
Row
</div>
) : null}
</div>
{currentTableData?.map((rowData, rowDataIndex) => {
return (
<div
className=" flex !h-[3rem] !max-h-[3rem] !min-h-[3rem] border-b"
key={rowDataIndex}
>
{[actions?.select?.show].includes(true) && (
<div
className={` !h-full !max-h-full !min-h-full !w-[2.65rem] !min-w-[2.65rem] !max-w-[2.65rem] cursor-pointer px-[.75rem] py-[.5rem] text-sm font-[400] capitalize leading-[1.5rem] tracking-wider text-sub-500`}
>
<input
type="checkbox"
// disabled={!actions?.select?.multiple}
className={`focus:shadow-outline mr-1 !h-4 !w-4 cursor-pointer appearance-none rounded border leading-tight text-primary shadow focus:outline-none focus:ring-0`}
name="select_item"
checked={selectedIds.includes(rowData[actionId])}
onChange={() => handleSelectRow(rowData[actionId])}
/>
</div>
)}
{columns.find((item) => item.accessor === "row") ? (
<div
className={`flex h-full !w-[2.65rem] !min-w-[2.65rem] !max-w-[2.65rem] items-center whitespace-nowrap px-[.75rem] py-[.5rem] text-sm`}
>
{rowDataIndex + 1}
</div>
) : null}
</div>
);
})}
</div>
) : null}
</>
) : null}
</Fragment>
);
};
export default MkdListTableSelectRow;
@@ -0,0 +1,809 @@
import React, { memo, useMemo } from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import MkdSDK from "Utils/MkdSDK";
import { StringCaser } from "Utils/utils";
import { AuthContext, tokenExpireError } from "Context/Auth";
import {
GlobalContext,
customRequest,
getMany,
setLoading as setGlobalLoading,
} from "Context/Global";
import {
MkdListTable,
OverlayTableActions,
TableActions,
} from "Components/MkdListTable";
import { AddButton } from "Components/AddButton";
import TreeSDK from "Utils/TreeSDK";
import "./MkdListTable.css";
import { ExCircleIcon } from "Assets/svgs";
import { MkdInput } from "Components/MkdInput";
let sdk = new MkdSDK();
// const getSchemaStructure = (schema) => {
// return;
// };
const getType = (type) => {
switch (type) {
case "varchar":
return "text";
case "text":
return "textarea";
case "mediumtext":
return "textarea";
case "longtext":
return "textarea";
case "tinyint":
return "number";
case "int":
return "number";
case "bigint":
return "number";
case "float":
return "number";
case "double":
return "number";
case "image":
return "image";
case "file":
return "file";
case "date":
return "date";
case "datetime":
return "datetime";
default:
return "text";
}
};
/**
* @params {"dropwdown" | "ontop" | "overlay"} actionPostion
*
*/
const MkdListTableV2 = ({
externalData = [],
useExternalData = false,
defaultColumns = [],
columnModel = null,
// setColumns,
actions = {
view: { show: true, multiple: true, action: null },
edit: { show: true, multiple: true, action: null },
delete: { show: true, multiple: true, action: null },
select: { show: true, multiple: true, action: null },
action: {
show: false,
multiple: false,
action: null,
showChildren: true,
children: "+",
type: "",
className: "",
locations: [],
icon: null,
},
add: {
show: true,
multiple: true,
action: null,
showChildren: true,
children: "+",
type: "",
className: "",
},
export: {
show: true,
multiple: true,
action: null,
showText: false,
className: "",
},
},
actionPostion = ["dropdown"], // "dropwdown" | "ontop" | "overlay" | "button"
actionId = "id",
tableRole = "admin",
table = "user",
tableTitle = "",
tableSchema = [],
hasFilter = true,
schemaFields = [],
showPagination = true,
defaultFilter = [],
refreshRef = null,
allowEditing = false,
allowSortColumns = true,
topClasses = "",
join = [],
filterDisplays = [],
}) => {
const tdk = new TreeSDK();
const { dispatch } = React.useContext(AuthContext);
const {
dispatch: globalDispatch,
state: { columModel },
} = React.useContext(GlobalContext);
const [query, setQuery] = React.useState("");
const [currentTableData, setCurrentTableData] = React.useState([]);
const [pageSize, setPageSize] = React.useState(1000);
const [pageCount, setPageCount] = React.useState(0);
const [dataTotal, setDataTotal] = React.useState(0);
const [currentPage, setPage] = React.useState(1);
const [canPreviousPage, setCanPreviousPage] = React.useState(false);
const [canNextPage, setCanNextPage] = React.useState(false);
const [showDeleteModal, setShowDeleteModal] = React.useState(false);
const [deleteLoading, setDeleteLoading] = React.useState(false);
const [selectedOptions, setSelectedOptions] = React.useState([]);
const [filterConditions, setFilterConditions] = React.useState([]);
const [selectedItems, setSelectedItems] = React.useState([]);
const [searchValue, setSearchValue] = React.useState("");
const [isSearchDirty, setIsSearchDirty] = React.useState(false);
// const [optionValue, setOptionValue] = React.useState("eq");
const [isLoading, setIsLoading] = React.useState(false);
const [loading, setLoading] = React.useState(false);
const [runFilter, setRunFilter] = React.useState(false);
const [searchField, setSearchField] = React.useState("name");
const [columnData, setColumnData] = React.useState(null);
const [columns, setColumns] = React.useState([]);
const [columnId, setColumnId] = React.useState(0);
const [popoverShown, setPopoverShow] = React.useState(false);
const selectedOptionsMemo = useMemo(() => selectedOptions, [selectedOptions]);
const schema = yup.object({});
const {
register,
handleSubmit,
setError,
reset,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
function onSort(columnIndex) {
console.log(columns[columnIndex]);
if (columns[columnIndex].isSorted) {
columns[columnIndex].isSortedDesc = !columns[columnIndex].isSortedDesc;
} else {
columns.map((i) => (i.isSorted = false));
columns.map((i) => (i.isSortedDesc = false));
columns[columnIndex].isSorted = true;
}
(async function () {
await getData(currentPage, pageSize, {
filterConditions,
order: columns[columnIndex].accessor,
direction: columns[columnIndex].isSortedDesc ? "desc" : "asc",
});
})();
}
function updatePageSize(limit) {
(async function () {
setPageSize(limit);
if (isSearchDirty && !searchValue) {
await getData(currentPage, limit);
setIsSearchDirty(false);
} else if (isSearchDirty && searchValue) {
getSearchData({ limit, page: currentPage });
}
})();
}
function setOptionValue(field, value, uid) {
const tempSelectedOptions = [...selectedOptions];
const data = tempSelectedOptions.map((item) => {
if (item?.uid === uid) {
return {
...item,
[field]: value,
};
}
return item;
});
setSelectedOptions((prev) => [...data]);
// console.log("field", field);
if (field === "value") {
// console.log("field", field);
setRunFilter(true);
}
}
function previousPage() {
(async function () {
if (isSearchDirty && !searchValue) {
// await getData(currentPage, pageSize, { filterConditions: [] });
await getData(
currentPage - 1 > 0 ? currentPage - 1 : currentPage,
pageSize
);
setIsSearchDirty(false);
} else if (isSearchDirty && searchValue) {
getSearchData({
limit: pageSize,
page: currentPage - 1 > 0 ? currentPage - 1 : currentPage,
});
}
})();
}
function updateCurrentPage(page) {
(async function () {
setPage(page);
if (isSearchDirty && !searchValue) {
await getData(page, pageSize);
setIsSearchDirty(false);
} else if (isSearchDirty && searchValue) {
getSearchData({ limit: pageSize, page });
}
})();
}
function nextPage() {
(async function () {
if (isSearchDirty && !searchValue) {
await getData(
currentPage + 1 <= pageCount ? currentPage + 1 : currentPage,
pageSize
);
setIsSearchDirty(false);
} else if (isSearchDirty && searchValue) {
getSearchData({
limit: pageSize,
page: currentPage + 1 <= pageCount ? currentPage + 1 : currentPage,
});
}
})();
}
const addFilterCondition = (option, selectedValue, inputValue) => {
const input =
selectedValue === "eq" && isNaN(inputValue)
? `${inputValue}`
: inputValue;
const condition = `${option},${selectedValue},${input}`.toLowerCase();
setFilterConditions((prevConditions) => {
const newConditions = prevConditions.filter(
(condition) => !condition.includes(option)
);
return [...newConditions, condition];
});
setSearchValue(inputValue);
};
// options.size = payload.limit;
// options.order = payload.sortId;
// options.direction = payload.direction;
// options.page = payload.page;
// options.join = payload.join;
async function getData(pageNum, limitNum, currentTableData) {
// console.log("currentTableData >>", currentTableData);
try {
setLoading(true);
const result = await tdk.getPaginate(table, {
size: limitNum,
page: pageNum,
...{
...(join && join.length
? {
join,
}
: null),
},
...{
...(currentTableData?.order
? {
order: currentTableData?.order,
direction: currentTableData?.direction,
}
: null),
},
...{
...(currentTableData?.filterConditions &&
currentTableData?.filterConditions.length
? {
filter: [
...currentTableData?.filterConditions,
...(defaultFilter.length ? defaultFilter : []),
],
}
: defaultFilter.length
? { filter: [...defaultFilter] }
: null),
},
});
// sdk.setTable(table);
// const result = await sdk.callRestAPI(
// {
// payload: {
// ...currentTableData,
// ...{
// ...(filterConditions.length
// ? {
// filter: [
// ...(defaultFilter.length && defaultFilter),
// ...filterConditions,
// ],
// }
// : defaultFilter.length
// ? { filter: [...defaultFilter] }
// : null),
// },
// },
// page: pageNum,
// limit: limitNum,
// },
// "PAGINATE"
// );
// if (!result) {
// setLoading(false);
// }
setSelectedItems(() => []);
const { list, total, limit, num_pages, page } = result;
setCurrentTableData(list);
console.log("v2 component fetch result");
console.log(result);
console.log("list");
console.log(list);
setPageSize(limit);
setPageCount(num_pages);
setPage(page);
setDataTotal(total);
setCanPreviousPage(page > 1);
setCanNextPage(page + 1 <= num_pages);
setLoading(false);
if (selectedItems?.length) {
setSelectedItems(() => []);
}
} catch (error) {
setLoading(false);
console.log("ERROR", error);
tokenExpireError(dispatch, error.message);
}
}
const deleteItem = async (id) => {
async function deleteId(id) {
try {
setDeleteLoading(true);
sdk.setTable(table);
const result = await sdk.callRestAPI({ id }, "DELETE");
if (!result?.error) {
setCurrentTableData((list) =>
list.filter((x) => Number(x.id) !== Number(id))
);
setDeleteLoading(false);
setShowDeleteModal(false);
}
} catch (err) {
setDeleteLoading(false);
setShowDeleteModal(false);
tokenExpireError(dispatch, err?.message);
throw new Error(err);
}
}
if (typeof id === "object") {
id.forEach(async (idToDelete) => {
await deleteId(idToDelete);
});
} else if (typeof id === "number") {
await deleteId(id);
}
};
const exportTable = async (id) => {
try {
sdk.setTable(table);
const result = await sdk.exportCSV();
} catch (err) {
throw new Error(err);
}
};
const handleAlphaSearchInput = async (e) => {
if ([e?.code?.toLowerCase(), e?.key?.toLowerCase()].includes("enter")) {
console.log("searchValue >>", searchValue);
if (isSearchDirty && !searchValue) {
await getData(currentPage, pageSize, { filterConditions: [] });
setIsSearchDirty(false);
} else if (isSearchDirty && searchValue) {
getSearchData();
}
} else {
setSearchValue(e?.target?.value);
if (!isSearchDirty) {
setIsSearchDirty(true);
}
}
};
const getSearchData = async (query = { limit: pageSize, page: 1 }) => {
try {
const apiEndpoint = `/v3/api/custom/qualitysign/generic/search/${table}?limit=${query?.limit}&page=${query?.page}`;
setLoading(true);
const result = await customRequest(globalDispatch, dispatch, {
endpoint: apiEndpoint,
method: "POST",
payload: {
search: searchValue,
columns: columns,
},
});
if (!result?.error) {
setSelectedItems(() => []);
const { data, total, limit, num_pages, page } = result;
setCurrentTableData(() => data);
console.log("v2 component fetch search result");
console.log(result);
console.log("list");
console.log(data);
setPageSize(Number(limit));
setPageCount(num_pages ?? pageCount);
setPage(Number(page));
setDataTotal(Number(total));
setCanPreviousPage(Number(page) > 1);
setCanNextPage(
Number(page) + 1 <= num_pages ? Number(num_pages) : pageCount
);
setLoading(false);
if (selectedItems?.length) {
setSelectedItems(() => []);
}
}
setLoading(false);
} catch (error) {
setLoading(false);
}
};
const resetForm = async () => {
reset();
await getData(currentPage, pageSize);
};
const onSubmit = (e) => {
let filters = [];
// find duplicate fields
const uniqueSet = new Set(
selectedOptionsMemo.map((item) => item?.accessor)
);
if (uniqueSet?.size) {
uniqueSet.forEach((uniqueSetItem) => {
const filterSet = selectedOptionsMemo.filter(
(item) => item.accessor === uniqueSetItem
);
console.log("filterSet >>", filterSet);
if (filterSet?.length > 0 && filterSet?.length > 1) {
const valueSet = filterSet
.map((item) => {
if (item?.value) {
return item;
}
})
.filter(Boolean);
if (valueSet.length > 1) {
// const value = `${uniqueSetItem},in,${valueSet
// .map((item) => item.value)
// .join(",")}`;
// filters.push(value);
valueSet.forEach((valueSetItem) => {
switch (valueSetItem?.operator) {
case "cs":
case "eq":
filters.push(
`qualitysign_${table}.${valueSetItem?.accessor},o${valueSetItem?.operator},${valueSetItem?.value}`
);
break;
default:
filters.push(
`qualitysign_${table}.${valueSetItem?.accessor},${valueSetItem?.operator},${valueSetItem?.value}`
);
}
});
} else if (valueSet.length === 1) {
filters.push(
`qualitysign_${table}.${valueSet[0]?.accessor},${valueSet[0]?.operator},${valueSet[0]?.value}`
);
}
} else if (filterSet?.length === 1) {
if (filterSet[0]?.value) {
filters.push(
`qualitysign_${table}.${filterSet[0]?.accessor},${filterSet[0]?.operator},${filterSet[0]?.value}`
);
}
}
});
}
// return console.log("filters >>", filters);
if (filters?.length) {
getData(currentPage, pageSize, { filterConditions: filters });
} else {
getData(currentPage, pageSize, { filterConditions: [] });
}
// getData(currentPage, pageSize, filter);
};
async function updateTableData(id, key, updatedData) {
try {
// setLoading(true);
sdk.setTable(table);
const result = await sdk.callRestAPI(
{
id,
[key]: updatedData,
},
"PUT"
);
// if (result) {
// setLoading(false);
// }
console.log("update user data");
console.log(result);
} catch (error) {
// setLoading(false);
console.log("ERROR", error);
tokenExpireError(dispatch, error.message);
}
}
async function handleTableCellChange(id, newValue, index, newValueKey) {
console.log(newValue);
console.log(index);
console.log(newValueKey);
let runApiCall;
newValue = isNaN(Number.parseInt(newValue))
? newValue
: Number.parseInt(newValue);
try {
clearTimeout(runApiCall);
runApiCall = setTimeout(async () => {
await updateTableData(id, newValueKey, newValue);
}, 200);
setCurrentTableData((prevData) => {
const updatedData = prevData.map((item, i) => {
if (i === index) {
return {
...item,
[newValueKey]: newValue,
};
}
return item;
});
return updatedData;
});
} catch (error) {
console.error(error);
}
}
const populateColums = (result) => {
setColumnId(result?.data[0]?.id);
setColumnData(result?.data[0]);
const data =
result?.data[0].columns && JSON.parse(result?.data[0].columns)
? JSON.parse(result?.data[0].columns)
: [];
console.log("data >>", data);
if (data.length) {
setColumns(() => [...data]);
} else {
setColumns(() => [...defaultColumns]);
}
};
const getColumns = async () => {
setGlobalLoading(globalDispatch, true, "columModel");
const result = await getMany(globalDispatch, dispatch, "column", [
...(columnModel
? [`model,eq,'${columnModel}'`]
: [`model,eq,'${table}'`]),
`user_id,eq,0`,
]);
// console.log("result >>", result);
if (!result?.error && result?.data?.length) {
populateColums(result);
} else {
const result = await getMany(globalDispatch, dispatch, "column", [
...(columnModel
? [`model,eq,'${columnModel}'`]
: [`model,eq,'${table}'`]),
`user_id,eq,0`,
]);
if (!result?.error && result?.data?.length) {
populateColums(result);
}
}
setGlobalLoading(globalDispatch, false, "columModel");
};
React.useEffect(() => {
if (useExternalData) {
setColumns(() => defaultColumns);
} else {
getColumns();
}
}, [useExternalData]);
// Update External Selected Items
React.useEffect(() => {
if (actions?.select?.action) {
actions.select.action(selectedItems);
}
}, [selectedItems?.length]);
React.useEffect(() => {
const searchableCol = columns.find((col) => col?.searchable);
if (searchableCol) {
setSearchField(searchableCol?.accessor);
}
}, []);
React.useEffect(() => {
if (useExternalData) {
setCurrentTableData(() => externalData);
} else {
getData(currentPage, pageSize, { filterConditions: [] });
}
}, [useExternalData]);
return (
<div className="!h-fit">
{refreshRef && (
<button
type="button"
ref={refreshRef}
onClick={() => getData(1, pageSize)}
className="hidden"
></button>
)}
<div
className={`flex w-full justify-between ${
tableTitle && hasFilter ? "flex-col gap-3" : "h-fit items-center"
} ${topClasses}`}
>
<h4 className="flex items-center font-inter text-[1rem] font-medium capitalize leading-[1.5rem] tracking-[-0.011em]">
{tableTitle ? tableTitle : ""}
</h4>
<div
className={`flex h-fit ${
hasFilter ? "w-full" : "w-fit"
} items-center justify-between gap-2 text-center`}
>
<div
className={`flex h-full w-fit justify-end gap-2 self-end ${
!tableTitle && !hasFilter ? "w-full" : ""
}`}
>
{Object.keys(actions).map((key, keyIndex) => {
if (
actions[key].show &&
actions[key].hasOwnProperty("type") &&
["toggle"].includes(actions[key].type)
) {
return (
<MkdInput
key={keyIndex}
type="toggle"
onChange={(e) => {
if (actions[key]?.action) {
actions[key]?.action(e?.target?.checked);
}
}}
label={actions[key]?.children ?? key}
value={actions[key]?.value}
/>
);
}
})}
{selectedItems?.length && actionPostion.includes("ontop") ? (
<TableActions actions={actions} selectedItems={selectedItems} />
) : null}
{Object.keys(actions).map((key, keyIndex) => {
if (
actions[key].show &&
actions[key].hasOwnProperty("type") &&
["static"].includes(actions[key].type)
) {
return (
<AddButton
key={keyIndex}
onClick={() => {
if (actions[key]?.action) {
actions[key]?.action();
}
}}
title={actions[key]?.title ?? key}
// showChildren={actions?.add?.showChildren}
showPlus={false}
className={`!h-[2.5rem] ${actions[key]?.className}`}
loading={actions[key]?.loading ?? false}
disabled={actions[key]?.disabled ?? false}
icon={actions[key]?.icon ?? null}
>
{key === "delete" ? <ExCircleIcon /> : null}
{actions[key].children ? (
actions[key].children
) : (
<>
{StringCaser(key === "delete" ? "Remove" : key, {
casetype: "capitalize",
separator: " ",
})}
</>
)}
</AddButton>
);
}
})}
{actions?.add?.show && (
<AddButton
onClick={() => {
if (actions?.add?.action) {
actions?.add?.action();
}
}}
showChildren={actions?.add?.showChildren}
className={`!h-[2.5rem] ${actions?.add?.className}`}
>
{actions?.add?.children}
</AddButton>
)}
</div>
</div>
</div>
<div className="my-2">
<MkdListTable
table={table}
onSort={onSort}
columns={columns}
actions={actions}
actionId={actionId}
tableRole={tableRole}
tableTitle={tableTitle}
columnData={columnData}
deleteItem={deleteItem}
setColumns={setColumns}
allowEditing={allowEditing}
setColumnData={setColumnData}
actionPostion={actionPostion}
deleteLoading={deleteLoading}
showDeleteModal={showDeleteModal}
allowSortColumns={allowSortColumns}
currentTableData={currentTableData}
setSelectedItems={setSelectedItems}
setShowDeleteModal={setShowDeleteModal}
loading={loading || columModel?.loading}
handleTableCellChange={handleTableCellChange}
// onPopoverStateChange={setPopoverShow}
popoverShown={popoverShown}
/>
</div>
{selectedItems?.length && actionPostion.includes("overlay") ? (
<OverlayTableActions actions={actions} selectedItems={selectedItems} />
) : null}
</div>
);
};
export default memo(MkdListTableV2);
@@ -0,0 +1,184 @@
import React from "react";
import { StringCaser, optionTypes } from "Utils/utils";
import { AddButton } from "Components/AddButton";
import { ChevronRightIcon, ExCircleIcon } from "Assets/svgs";
import { MkdPopover } from "Components/MkdPopover";
import { DropdownOption } from "Components/DropdownOptions";
const OverlayTableActions = ({ actions, selectedItems }) => {
return (
<div className="fixed inset-x-0 bottom-5 m-auto flex !h-[3.25rem] !max-h-[3.25rem] w-fit items-center justify-start gap-2 rounded-[.875rem] bg-black px-[.75rem] pb-[.5rem] pt-[.5rem]">
<div className="font-inter text-white">
Selected: {selectedItems.length}
</div>
{Object.keys(actions).filter(
(key) =>
actions[key]?.show &&
actions[key]?.locations &&
actions[key]?.locations?.includes("overlay")
)?.length ? (
<>
{Object.keys(actions)
.filter(
(key) =>
actions[key]?.show &&
actions[key]?.locations &&
actions[key]?.locations?.includes("overlay")
)
.map((key, keyIndex) => {
if (
actions[key]?.type &&
[optionTypes.DROPDOWN].includes(actions[key]?.type)
) {
return (
<RenderButtonDropdownActions
action={actions[key]}
actionKey={key}
selectedItems={selectedItems}
key={keyIndex}
/>
);
} else if (!actions[key]?.type) {
return (
<RenderButtons
action={actions[key]}
actionKey={key}
selectedItems={selectedItems}
key={keyIndex}
/>
);
}
})
.filter(Boolean)}
</>
) : null}
</div>
);
};
export default OverlayTableActions;
const RenderButtonDropdownActions = ({ action, actionKey, selectedItems }) => {
return (
<MkdPopover
display={
<span
className={`hover:text[#262626] relative flex h-[3rem] w-full cursor-pointer items-center justify-between gap-2 overflow-hidden rounded-[.625rem] border !border-white-200 border-primary !bg-white-100 bg-primary px-2 py-2 font-inter text-sm font-medium capitalize leading-loose tracking-wide text-white hover:bg-[#F4F4F4]`}
>
<span className="flex grow items-center justify-start gap-3 text-white">
{action?.icon}
{action?.children ?? actionKey}
</span>
<ChevronRightIcon className="-rotate-90" />
</span>
}
zIndex={999}
className={`w-full`}
tooltipClasses="!rounded-[.5rem] !text-white w-full !min-w-[11rem] !px-0 !right-[3.25rem] !border"
place={"top-start"}
backgroundColor="#18181B"
>
{action?.options && Object.keys(action?.options).length
? Object.keys(action?.options).map((key, keyIndex) => {
return (
<RenderButtonActions
key={keyIndex}
action={action?.options[key]}
selectedItems={selectedItems}
/>
);
})
: null}
</MkdPopover>
);
};
const RenderButtonActions = ({ action, selectedItems }) => {
return (
<DropdownOption
name={action?.children ?? key}
// key={keyIndex}
className="!text-white hover:!bg-white-100 "
icon={action?.icon}
onClick={() => {
if (action?.action) {
action?.action(selectedItems);
}
}}
/>
);
};
const RenderButtons = ({ selectedItems, action, actionKey }) => {
if (selectedItems && selectedItems?.length === 1 && !action?.multiple) {
return (
<AddButton
showPlus={false}
loading={action?.loading ?? false}
disabled={action?.disabled ?? false}
icon={action?.icon ?? null}
className={`flex cursor-pointer gap-2 !border-white-200 !bg-white-100 px-2 py-2 text-lg font-medium leading-loose tracking-wide ${
actionKey === "view"
? "text-blue-500"
: actionKey === "delete"
? "!text-red-500"
: "text-[#292829fd]"
} hover:underline`}
onClick={() => {
if (action?.action) {
console.log("actionKey >>", actionKey);
action.action(selectedItems);
}
}}
>
{actionKey === "delete" ? <ExCircleIcon /> : null}
{action.children ? (
action.children
) : (
<>
{StringCaser(actionKey === "delete" ? "Remove" : actionKey, {
casetype: "capitalize",
separator: " ",
})}
</>
)}
</AddButton>
);
}
if (selectedItems && selectedItems?.length >= 1 && action?.multiple) {
return (
<AddButton
actionKey={actionKey}
showPlus={false}
loading={action?.loading ?? false}
disabled={action?.disabled ?? false}
icon={action?.icon ?? null}
className={`cursor-pointer gap-2 !border-white-200 !bg-white-100 px-2 py-2 text-lg font-medium leading-loose tracking-wide ${
actionKey === "view"
? "text-blue-500"
: actionKey === "delete"
? "!text-red-500"
: "text-[#292829fd]"
} hover:underline`}
onClick={() => {
if (action?.action) {
console.log("actionKey >>", actionKey);
action.action(selectedItems);
}
}}
>
{actionKey === "delete" ? <ExCircleIcon /> : null}
{action.children ? (
action.children
) : (
<>
{StringCaser(actionKey === "delete" ? "Remove" : actionKey, {
casetype: "capitalize",
separator: " ",
})}
</>
)}
</AddButton>
);
}
};
@@ -0,0 +1,43 @@
import React from "react";
import { DropdownOption } from "Components/DropdownOptions";
import { processBind } from "./MkdListTableBindOperations";
const RenderActions = ({ action, row, actionId, key }) => {
if (action?.bind) {
switch (action?.bind?.action) {
case "hide":
if (!processBind(action, row)) {
return (
<DropdownOption
name={action?.children ?? key}
// key={keyIndex}
className="hover:!bg-white-100 "
icon={action?.icon}
onClick={() => {
if (action?.action) {
action?.action([row[actionId]]);
}
}}
/>
);
}
}
}
if (!action?.bind) {
return (
<DropdownOption
name={action?.children ?? key}
// key={keyIndex}
className="hover:!bg-white-100 "
icon={action?.icon}
onClick={() => {
if (action?.action) {
action?.action([row[actionId]]);
}
}}
/>
);
}
};
export default RenderActions;
@@ -0,0 +1,41 @@
import React from "react";
import { MkdPopover } from "Components/MkdPopover";
import { ChevronRightIcon } from "Assets/svgs";
import RenderActions from "./RenderActions";
const RenderDropdownActions = ({ action, actionKey, row, actionId }) => {
return (
<MkdPopover
display={
<span
className={`flex w-full cursor-pointer items-center justify-between gap-3 px-2 capitalize text-[#262626] hover:bg-[#F4F4F4]`}
>
<span className="flex grow gap-3">
{action?.icon}
{action?.children ?? actionKey}
</span>
<ChevronRightIcon />
</span>
}
className={`w-full`}
tooltipClasses="!rounded-[.5rem] w-full !min-w-[11rem] !px-0 !right-[3.25rem] !border bg-white"
place={"left-start"}
backgroundColor="#fff"
>
{action?.options && Object.keys(action?.options).length
? Object.keys(action?.options).map((key, keyIndex) => {
return (
<RenderActions
key={keyIndex}
action={action?.options[key]}
actionId={actionId}
row={row}
/>
);
})
: null}
</MkdPopover>
);
};
export default RenderDropdownActions;
@@ -0,0 +1,92 @@
import React from "react";
import { StringCaser } from "Utils/utils";
import { AddButton } from "Components/AddButton";
const TableActions = ({ actions, selectedItems }) => {
return (
<div className="flex gap-2">
{Object.keys(actions)
.map((key) => actions[key].show)
.includes(true) ? (
<>
{Object.keys(actions)
.map((key) => {
if (
actions[key].show &&
!["static"].includes(actions[key].type) &&
!["select", "add", "export"].includes(key)
) {
if (
selectedItems &&
selectedItems?.length === 1 &&
!actions[key]?.multiple
) {
return (
<AddButton
key={key}
showPlus={false}
loading={actions[key]?.loading ?? false}
disabled={actions[key]?.disabled ?? false}
icon={actions[key]?.icon ?? null}
className={`!h-[2.5rem] cursor-pointer px-2 py-2 text-lg font-medium leading-loose tracking-wide ${
key === "view"
? "text-blue-500"
: key === "delete"
? "text-red-500"
: "text-[#292829fd]"
} hover:underline`}
onClick={() => {
if (actions[key]?.action) {
actions[key].action(selectedItems);
}
}}
>
{StringCaser(key, {
casetype: "capitalize",
separator: " ",
})}
</AddButton>
);
}
if (
selectedItems &&
selectedItems?.length >= 1 &&
actions[key]?.multiple
) {
return (
<AddButton
key={key}
showPlus={false}
loading={actions[key]?.loading ?? false}
disabled={actions[key]?.disabled ?? false}
icon={actions[key]?.icon ?? null}
className={`!h-[2.5rem] cursor-pointer px-2 py-2 text-lg font-medium leading-loose tracking-wide ${
key === "view"
? "text-blue-500"
: key === "delete"
? "text-red-500"
: "text-[#292829fd]"
} hover:underline`}
onClick={() => {
if (actions[key]?.action) {
actions[key].action(selectedItems);
}
}}
>
{StringCaser(key, {
casetype: "capitalize",
separator: " ",
})}
</AddButton>
);
}
}
})
.filter(Boolean)}
</>
) : null}
</div>
);
};
export default TableActions;
+8
View File
@@ -0,0 +1,8 @@
export { default as MkdListTable } from "./MkdListTable";
export { default as MkdListTableV2 } from "./MkdListTableV2";
export { default as TableActions } from "./TableActions";
export { default as OverlayTableActions } from "./OverlayTableActions";
export { default as MkdListTableRow } from "./MkdListTableRow";
export { default as MkdListTableHead } from "./MkdListTableHead";
export { default as MkdListTableRowButtons } from "./MkdListTableRowButtons";
export { default as MkdListTableRowDropdown } from "./MkdListTableRowDropdown";
+10
View File
@@ -0,0 +1,10 @@
/* PopoverOnClick.css */
/* Customize the appearance of the tooltip */
/* .tooltip {
background-color: white !important;
color: #fff;
padding: 0.5rem;
border-radius: 0.25rem;
font-size: 0.875rem;
} */
+61
View File
@@ -0,0 +1,61 @@
import React, { useId } from "react";
import { Tooltip as ReactTooltip } from "react-tooltip";
import "./MkdPopover.css";
const MkdPopover = ({
display,
className,
children,
tooltipClasses,
place = "bottom",
openOnClick = true,
zIndex = 99999,
onPopoverStateChange,
backgroundColor = "#fff",
textColor = "#000",
}) => {
const tooltipId = useId();
const handleTooltipShow = () => {
if (onPopoverStateChange) {
onPopoverStateChange(true);
}
};
const handleTooltipHide = () => {
if (onPopoverStateChange) {
onPopoverStateChange(false);
}
};
return (
<>
<div className="App">
<button
type="button"
data-tooltip-id={tooltipId}
className={`${className}`}
>
{display ? display : null}
{/* <AlertCircle className="rotate-180" /> */}
</button>
</div>
<ReactTooltip
// globalCloseEvents={{ scroll: true }}
id={tooltipId}
openOnClick={openOnClick}
style={{ backgroundColor, color: textColor, zIndex: zIndex }}
className={`z-[${zIndex}] ${tooltipClasses} !shadow-md`}
clickable
place={place}
effect="solid"
afterShow={handleTooltipShow}
afterHide={handleTooltipHide}
// getTextColor={() => textColor}
>
{children}
</ReactTooltip>
</>
);
};
export default MkdPopover;
@@ -0,0 +1,47 @@
.button {
position: relative;
border: none;
color: #ffffff;
text-align: center;
-webkit-transition-duration: 0.4s; /* Safari */
transition-duration: 0.4s;
text-decoration: none;
overflow: hidden;
cursor: pointer;
}
.button:after {
content: "";
background: #4f46e5;
display: block;
position: absolute;
padding-top: 300%;
padding-left: 350%;
margin-left: -20px !important;
margin-top: -120%;
opacity: 0;
transition: all 0.8s;
}
.button:active:after {
padding: 0;
margin: 0;
opacity: 1;
transition: 0s;
}
.tip {
position: absolute;
top: anchor(bottom);
left: anchor(50%);
}
/* PopoverOnClick.css */
/* Customize the appearance of the tooltip */
.tooltip {
background-color: #333;
color: #fff;
padding: 8px;
border-radius: 4px;
font-size: 14px;
/* Add any other styles you need */
}
+3
View File
@@ -0,0 +1,3 @@
import { lazy } from "react";
export const MkdPopover = lazy(() => import("./MkdPopover"));
+41
View File
@@ -0,0 +1,41 @@
import React from "react";
import Skeleton from "react-loading-skeleton";
const SkeletonLoader = ({
className = "",
count = 5,
counts = [2, 1, 3, 1, 1],
circle = false,
}) => {
return (
<div
className={`flex h-fit max-h-screen min-h-fit w-full flex-col gap-5 overflow-hidden p-4 ${className} `}
>
{/* <Skeleton circle width={60} height={60} /> */}
{Array.from({ length: count }).map((_, index) => (
<Skeleton
key={`${_}${index}`}
count={counts[index] ?? 1}
height={
counts[index] && counts[index] > 1
? 25
: index + 1 === count
? 25
: 80
}
circle={circle}
// style={{ marginBottom: "0.6rem" }}
/>
))}
</div>
);
};
export default SkeletonLoader;
{
/* <Skeleton count={1} height={60} style={{ marginBottom: "0.6rem" }} />
<Skeleton count={3} height={25} style={{ marginBottom: "0.6rem" }} />
<Skeleton count={1} height={80} style={{ marginBottom: "0.6rem" }} />
<Skeleton count={1} height={25} style={{ marginBottom: "0.6rem" }} /> */
}
+6
View File
@@ -0,0 +1,6 @@
import { lazy } from "react";
export const SkeletonLoader = lazy(()=> import("./Skeleton"))
+117 -28
View File
@@ -1,39 +1,128 @@
import React from "react";
import { GlobalContext } from "../globalContext";
import { GlobalContext } from "Context/Global";
const SnackBar = () => {
const { state, dispatch } = React.useContext(GlobalContext);
const show = state.globalMessage.length > 0;
const {
state: { globalMessage, toastStatus },
dispatch,
} = React.useContext(GlobalContext);
const show = globalMessage.length > 0;
return show ? (
<div
id="mkd-toast"
className="absolute top-5 right-5 flex items-center w-full max-w-xs p-4 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400"
className={`fixed right-5 top-10 z-[100000000001] flex max-h-[5.625rem] min-h-[4.375rem] w-fit max-w-[50%] items-center justify-between rounded-lg border-2 bg-white px-4 py-2 text-sm shadow-2xl dark:text-gray-400`}
role="alert"
>
<div className="text-sm font-normal">{state.globalMessage}</div>
<div className="flex items-center ml-auto space-x-2">
<button
type="button"
className="bg-white text-gray-400 hover:text-gray-900 rounded-lg focus:ring-2 focus:ring-gray-300 p-1.5 hover:bg-gray-100 inline-flex h-8 w-8 dark:text-gray-500 dark:hover:text-white dark:hover:bg-gray-700"
aria-label="Close"
onClick={() => {
dispatch({ type: "SNACKBAR", payload: { message: "" } });
}}
>
<span className="sr-only">Close</span>
<svg
className="w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clipRule="evenodd"
></path>
</svg>
</button>
<div className="mr-8 flex w-fit items-center gap-4">
<span>
{toastStatus === "success" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM15.5805 9.97493C15.8428 9.65434 15.7955 9.18183 15.4749 8.91953C15.1543 8.65724 14.6818 8.70449 14.4195 9.02507L10.4443 13.8837L9.03033 12.4697C8.73744 12.1768 8.26256 12.1768 7.96967 12.4697C7.67678 12.7626 7.67678 13.2374 7.96967 13.5303L9.96967 15.5303C10.1195 15.6802 10.3257 15.7596 10.5374 15.7491C10.749 15.7385 10.9463 15.6389 11.0805 15.4749L15.5805 9.97493Z"
fill="#16A34A"
/>
</svg>
) : toastStatus === "error" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.4902 2.84406C11.1661 1.69 12.8343 1.69 13.5103 2.84406L22.0156 17.3654C22.699 18.5321 21.8576 19.9999 20.5056 19.9999H3.49483C2.14281 19.9999 1.30147 18.5321 1.98479 17.3654L10.4902 2.84406ZM12 9C12.4142 9 12.75 9.33579 12.75 9.75V13.25C12.75 13.6642 12.4142 14 12 14C11.5858 14 11.25 13.6642 11.25 13.25V9.75C11.25 9.33579 11.5858 9 12 9ZM13 15.75C13 16.3023 12.5523 16.75 12 16.75C11.4477 16.75 11 16.3023 11 15.75C11 15.1977 11.4477 14.75 12 14.75C12.5523 14.75 13 15.1977 13 15.75Z"
fill="#DC2626"
/>
</svg>
) : toastStatus === "info" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM10 11C10 10.5858 10.3358 10.25 10.75 10.25H12C12.4142 10.25 12.75 10.5858 12.75 11L12.75 16.25C12.75 16.6642 12.4142 17 12 17C11.5858 17 11.25 16.6642 11.25 16.25L11.25 11.75H10.75C10.3358 11.75 10 11.4142 10 11ZM12 7.25C11.5858 7.25 11.25 7.58579 11.25 8C11.25 8.41421 11.5858 8.75 12 8.75C12.4142 8.75 12.75 8.41421 12.75 8C12.75 7.58579 12.4142 7.25 12 7.25Z"
fill="#4F46E5"
/>
</svg>
) : toastStatus === "warning" ? (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 13C11.45 13 11 12.55 11 12V8C11 7.45 11.45 7 12 7C12.55 7 13 7.45 13 8V12C13 12.55 12.55 13 12 13ZM13 17H11V15H13V17Z"
fill="#F59E0B"
/>
</svg>
) : (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2ZM10 11C10 10.5858 10.3358 10.25 10.75 10.25H12C12.4142 10.25 12.75 10.5858 12.75 11L12.75 16.25C12.75 16.6642 12.4142 17 12 17C11.5858 17 11.25 16.6642 11.25 16.25L11.25 11.75H10.75C10.3358 11.75 10 11.4142 10 11ZM12 7.25C11.5858 7.25 11.25 7.58579 11.25 8C11.25 8.41421 11.5858 8.75 12 8.75C12.4142 8.75 12.75 8.41421 12.75 8C12.75 7.58579 12.4142 7.25 12 7.25Z"
fill="#4F46E5"
/>
</svg>
)}
</span>
<span className="max-h-[3.75rem] w-fit max-w-fit grow truncate whitespace-pre-wrap font-medium text-[#525252]">
{globalMessage}
{/* M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22
12C22 6.48 17.52 2 12 2ZM12 13C11.45 13 11 12.55 11 12V8C11 7.45 11.45
7 12 7C12.55 7 13 7.45 13 8V12C13 12.55 12.55 13 12 13ZM13
17H11V15H13V17Z"M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52
22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 13C11.45 13 11 12.55 11
12V8C11 7.45 11.45 7 12 7C12.55 7 13 7.45 13 8V12C13 12.55 12.55 13 12
13ZM13 17H11V15H13V17Z" */}
</span>
</div>
<button
aria-label="Close"
onClick={() => {
dispatch({ type: "SNACKBAR", payload: { message: "" } });
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
>
<path
d="M15 5L5 15M5 5L15 15"
stroke="#A8A8A8"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
</div>
) : null;
};