feat: add Up and Down SVG components and improve dropdown menu label
This commit is contained in:
parent
b68a99cb22
commit
af386a2b07
|
|
@ -188,6 +188,7 @@ export function Popover({
|
|||
classNameTrigger,
|
||||
arrow,
|
||||
popoverClassName,
|
||||
onMouseDown,
|
||||
...restOptions
|
||||
}: {
|
||||
root?: HTMLElement;
|
||||
|
|
@ -197,6 +198,7 @@ export function Popover({
|
|||
content?: React.ReactNode;
|
||||
popoverClassName?: string;
|
||||
arrow?: boolean;
|
||||
onMouseDown?: (event: any) => void;
|
||||
} & PopoverOptions) {
|
||||
const popover = usePopover({ modal, ...restOptions });
|
||||
|
||||
|
|
@ -231,6 +233,7 @@ export function Popover({
|
|||
`
|
||||
)
|
||||
)}
|
||||
onMouseDown={onMouseDown}
|
||||
>
|
||||
{_content}
|
||||
{(typeof arrow === "undefined" || arrow) && <PopoverArrow />}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import { AsyncPaginate } from "react-select-async-paginate";
|
|||
import { components } from "react-select";
|
||||
import { useLocal } from "@/lib/utils/use-local";
|
||||
import { empty } from "@/lib/utils/isStringEmpty";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import debounce from "lodash.debounce";
|
||||
import get from "lodash.get";
|
||||
import { Popover } from "../../Popover/Popover";
|
||||
|
||||
export const TypeAsyncDropdown: React.FC<any> = ({
|
||||
name,
|
||||
|
|
@ -23,13 +24,28 @@ export const TypeAsyncDropdown: React.FC<any> = ({
|
|||
search = "api",
|
||||
required = false,
|
||||
}) => {
|
||||
const [cacheUniq, setCacheUniq] = useState(Date.now());
|
||||
const [open, setOpen] = useState(false as boolean);
|
||||
const [refreshKey, setRefreshKey] = useState(Date.now());
|
||||
|
||||
const selectRef = useRef<HTMLDivElement>(null);
|
||||
const [width, setWidth] = useState<number>(0);
|
||||
const getValue =
|
||||
typeof onValue === "string" ? (e: any) => get(e, onValue) : onValue;
|
||||
typeof onValue === "string"
|
||||
? (e: any) => {
|
||||
if (typeof e !== "object" && !Array.isArray(e)) {
|
||||
return e;
|
||||
}
|
||||
return get(e, onValue);
|
||||
}
|
||||
: onValue;
|
||||
const getLabel =
|
||||
typeof onLabel === "string" ? (e: any) => get(e, onLabel) : onLabel;
|
||||
typeof onLabel === "string"
|
||||
? (e: any) => {
|
||||
if (typeof e !== "object" && !Array.isArray(e)) {
|
||||
return e;
|
||||
}
|
||||
return get(e, onLabel);
|
||||
}
|
||||
: onLabel;
|
||||
let placeholderField =
|
||||
mode === "multi"
|
||||
? placeholder || `Add ${label}`
|
||||
|
|
@ -227,82 +243,125 @@ export const TypeAsyncDropdown: React.FC<any> = ({
|
|||
label: getLabel(value),
|
||||
};
|
||||
}
|
||||
const CustomMenu = (props: any) => {
|
||||
return (
|
||||
<Popover
|
||||
onMouseDown={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
classNameTrigger={""}
|
||||
arrow={false}
|
||||
className="rounded-md"
|
||||
onOpenChange={(open: any) => {}}
|
||||
open={true}
|
||||
content={
|
||||
<div
|
||||
className={cx(
|
||||
"flex flex-col flex-grow",
|
||||
css`
|
||||
width: ${width}px;
|
||||
`
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<></>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (selectRef.current) {
|
||||
setWidth(selectRef.current.offsetWidth);
|
||||
}
|
||||
}, [selectRef]);
|
||||
return (
|
||||
<AsyncPaginate
|
||||
// menuIsOpen={true}
|
||||
key={refreshKey}
|
||||
placeholder={disabled ? "" : placeholderField}
|
||||
isDisabled={disabled}
|
||||
className={cx(
|
||||
"rounded-md border-none text-sm",
|
||||
css`
|
||||
[role="listbox"] {
|
||||
padding: 0px !important;
|
||||
z-index: 5;
|
||||
<div ref={selectRef} className="w-full">
|
||||
<AsyncPaginate
|
||||
menuIsOpen={open}
|
||||
key={refreshKey}
|
||||
placeholder={disabled ? "" : placeholderField}
|
||||
isDisabled={disabled}
|
||||
className={cx(
|
||||
"rounded-md border-none text-sm",
|
||||
css`
|
||||
[role="listbox"] {
|
||||
padding: 0px !important;
|
||||
z-index: 5;
|
||||
}
|
||||
input:focus {
|
||||
outline: 0px !important;
|
||||
border: 0px !important;
|
||||
outline-offset: 0px !important;
|
||||
--tw-ring-color: transparent !important;
|
||||
}
|
||||
.css-13cymwt-control {
|
||||
border-color: transparent;
|
||||
border-width: 0px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.css-t3ipsp-control {
|
||||
border-color: transparent;
|
||||
border-width: 0px;
|
||||
box-shadow: none;
|
||||
border-radius: 6px;
|
||||
}
|
||||
> :nth-child(4) {
|
||||
z-index: 4 !important;
|
||||
}
|
||||
`,
|
||||
disabled
|
||||
? css`
|
||||
> div {
|
||||
border-width: 0px !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
> div > div:last-child {
|
||||
display: none !important;
|
||||
}
|
||||
> div > div:first-child > div {
|
||||
color: black !important;
|
||||
}
|
||||
`
|
||||
: ``
|
||||
)}
|
||||
isClearable={clearable}
|
||||
onMenuOpen={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
onMenuClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
// closeMenuOnSelect={mode === "dropdown" ? true : false}
|
||||
closeMenuOnSelect={false}
|
||||
getOptionValue={(item) => item.value}
|
||||
getOptionLabel={(item) => item.label}
|
||||
value={value}
|
||||
components={{ MultiValue, Option, Menu: CustomMenu }}
|
||||
loadOptions={loadOptions}
|
||||
isSearchable={true}
|
||||
isMulti={mode === "multi"}
|
||||
onChange={(e) => {
|
||||
setOpen(mode === "dropdown" ? false : true);
|
||||
if (target) {
|
||||
fm.data[target] = getValue(e);
|
||||
}
|
||||
input:focus {
|
||||
outline: 0px !important;
|
||||
border: 0px !important;
|
||||
outline-offset: 0px !important;
|
||||
--tw-ring-color: transparent !important;
|
||||
if (mode === "dropdown" && !target) {
|
||||
fm.data[name] = getValue(e);
|
||||
} else {
|
||||
fm.data[name] = e;
|
||||
}
|
||||
.css-13cymwt-control {
|
||||
border-color: transparent;
|
||||
border-width: 0px;
|
||||
border-radius: 6px;
|
||||
fm.render();
|
||||
if (typeof onChange === "function") {
|
||||
onChange({ data: e });
|
||||
}
|
||||
.css-t3ipsp-control {
|
||||
border-color: transparent;
|
||||
border-width: 0px;
|
||||
box-shadow: none;
|
||||
border-radius: 6px;
|
||||
}
|
||||
> :nth-child(4) {
|
||||
z-index: 4 !important;
|
||||
}
|
||||
`,
|
||||
disabled
|
||||
? css`
|
||||
> div {
|
||||
border-width: 0px !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
> div > div:last-child {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
> div > div:first-child > div {
|
||||
color: black !important;
|
||||
}
|
||||
`
|
||||
: ``
|
||||
)}
|
||||
isClearable={clearable}
|
||||
closeMenuOnSelect={mode === "dropdown" ? true : false}
|
||||
getOptionValue={(item) => item.value}
|
||||
getOptionLabel={(item) => item.label}
|
||||
value={value}
|
||||
components={{ MultiValue, Option }}
|
||||
loadOptions={loadOptions}
|
||||
isSearchable={true}
|
||||
isMulti={mode === "multi"}
|
||||
onChange={(e) => {
|
||||
if (target) {
|
||||
fm.data[target] = getValue(e);
|
||||
}
|
||||
if (mode === "dropdown" && !target) {
|
||||
fm.data[name] = getValue(e);
|
||||
} else {
|
||||
fm.data[name] = e;
|
||||
}
|
||||
fm.render();
|
||||
if (typeof onChange === "function") {
|
||||
onChange({ data: e });
|
||||
}
|
||||
}}
|
||||
additional={{
|
||||
page: 1,
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
additional={{
|
||||
page: 1,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import { Rating } from "../../ui/ratings";
|
|||
import { getNumber } from "@/lib/utils/getNumber";
|
||||
import MaskedInput from "../../ui/MaskedInput";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { getStatusLabel } from "@/constants/status-mpp";
|
||||
import { getLabel } from "@/lib/utils/getLabel";
|
||||
|
||||
export const TypeInput: React.FC<any> = ({
|
||||
name,
|
||||
|
|
@ -332,7 +332,7 @@ export const TypeInput: React.FC<any> = ({
|
|||
disabled={disabled}
|
||||
required={required}
|
||||
placeholder={placeholder || ""}
|
||||
value={getStatusLabel(value)}
|
||||
value={getLabel(value)}
|
||||
type={!type ? "text" : type_field}
|
||||
onChange={(ev) => {
|
||||
fm.data[name] = ev.currentTarget.value;
|
||||
|
|
|
|||
|
|
@ -286,7 +286,13 @@ const Input: React.FC<Props> = (e: Props) => {
|
|||
return (
|
||||
<>
|
||||
{disabled ? (
|
||||
<div className={"flex h-9 w-full rounded-md border border-gray-200 border-input bg-gray-100 items-center px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"}>{inputText ? inputText : "-"}</div>
|
||||
<div
|
||||
className={
|
||||
"flex h-9 w-full rounded-md border-input bg-gray-100 items-center px-3 py-1 text-base transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm"
|
||||
}
|
||||
>
|
||||
{inputText ? inputText : "-"}
|
||||
</div>
|
||||
) : (
|
||||
<input
|
||||
ref={inputRef}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { cn } from "@/lib/utils";
|
||||
import { useLocal } from "@/lib/utils/use-local";
|
||||
import { useEffect } from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
|
@ -40,11 +41,17 @@ export const PinterestLayout: React.FC<{
|
|||
<>
|
||||
<div className="flex flex-grow flex-1 flex-col w-full h-full">
|
||||
<div
|
||||
className={cx(
|
||||
className={cn(
|
||||
`grid gap-${gap}`,
|
||||
css`
|
||||
grid-template-columns: repeat(${col}, minmax(0, 1fr));
|
||||
`
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||
}
|
||||
@media (min-width: 769px) {
|
||||
grid-template-columns: repeat(${col}, minmax(0, 1fr));
|
||||
}
|
||||
`,
|
||||
""
|
||||
)}
|
||||
>
|
||||
{local.data.map((el, idx) => {
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ const buttonVariants = cva(
|
|||
destructive: "bg-red-500 text-white shadow-sm hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
clean:
|
||||
"border-none bg-background shadow-none hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ const DialogContent = React.forwardRef<
|
|||
{typeof onClick === "function" ? (
|
||||
<div
|
||||
onClick={onClick}
|
||||
className="cursor-pointer absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
|
||||
className="dialog-close cursor-pointer absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ const DropdownHamburgerBetter: React.FC<{
|
|||
setOpen(!open);
|
||||
}}
|
||||
>
|
||||
More Actions
|
||||
Actions
|
||||
{open ? <IoIosArrowDown /> : <IoIosArrowUp />}
|
||||
</ButtonBetter>
|
||||
</DropdownMenuTrigger>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export const userToken = async () => {
|
|||
if (user) {
|
||||
try {
|
||||
await userRoleMe();
|
||||
return true;
|
||||
} catch (ex: any) {
|
||||
const error = get(ex, "response.data.meta.message") || ex.message;
|
||||
if (error === "Request failed with status code 401") {
|
||||
|
|
@ -26,6 +27,36 @@ export const userToken = async () => {
|
|||
w.user = JSON.parse(user);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
try {
|
||||
let user = await apix({
|
||||
port: "portal",
|
||||
value: "data.data",
|
||||
path: "/api/users/me",
|
||||
});
|
||||
console.log(user);
|
||||
if (user) {
|
||||
let profile = null;
|
||||
try {
|
||||
const data = await apix({
|
||||
port: "recruitment",
|
||||
value: "data.data",
|
||||
path: "/api/user-profiles/user",
|
||||
});
|
||||
profile = data;
|
||||
delete profile["user"];
|
||||
user = {
|
||||
...user,
|
||||
profile,
|
||||
};
|
||||
} catch (ex) {}
|
||||
const w = window as any;
|
||||
w.user = JSON.parse(user);
|
||||
return true;
|
||||
}
|
||||
} catch (ex: any) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import { SVGProps } from "react";
|
||||
const SvgComponent = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 7" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M8 6.5a.47.47 0 0 1-.35-.15l-4.5-4.5c-.2-.2-.2-.51 0-.71s.51-.2.71 0l4.15 4.15 4.14-4.14c.2-.2.51-.2.71 0s.2.51 0 .71l-4.5 4.5c-.1.1-.23.15-.35.15Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export { SvgComponent as Down };
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { SVGProps } from "react";
|
||||
const SvgComponent = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 7" {...props}>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12.5 6a.47.47 0 0 1-.35-.15L8 1.71 3.85 5.85c-.2.2-.51.2-.71 0s-.2-.51 0-.71L7.65.65c.2-.2.51-.2.71 0l4.5 4.5c.2.2.2.51 0 .71-.1.1-.23.15-.35.15Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
export { SvgComponent as Up };
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
export const statusMpp = [
|
||||
{ value: "DRAFTED", label: "Draft" },
|
||||
{ value: "DRAFT", label: "Draft" },
|
||||
{ value: "IN_PROGRESS", label: "In Progress" },
|
||||
{ value: "IN PROGRESS", label: "In Progress" },
|
||||
{ value: "NEED APPROVAL", label: "Need Approval" },
|
||||
{ value: "APPROVED", label: "Approved" },
|
||||
{ value: "REJECTED", label: "Rejected" },
|
||||
{ value: "COMPLETED", label: "Completed" },
|
||||
{ value: "SUBMITTED", label: "Submitted" },
|
||||
{ value: "open", label: "Open" },
|
||||
{ value: "close", label: "Close" },
|
||||
{ value: "complete", label: "Complete" },
|
||||
{ value: "not_open", label: "Not Open" },
|
||||
{ value: "OFF_BUDGET", label: "Off Budget" },
|
||||
{ value: "ON_BUDGET", label: "On Budget" },
|
||||
{ value: "APPLIED", label: "Applied" },
|
||||
{ value: "PENDING", label: "Pending" },
|
||||
{ value: "ADMINISTRATIVE_SELECTION", label: "Administrative" },
|
||||
{ value: "TEST", label: "Test" },
|
||||
{ value: "INTERVIEW", label: "Interview" },
|
||||
{ value: "FGD", label: "FGD" },
|
||||
{ value: "SURAT_PENGANTAR_MASUK", label: "Surat Pengantar Masuk" },
|
||||
{ value: "SURAT_IZIN_ORANG_TUA", label: "Surat Izin Orang Tua" },
|
||||
{ value: "FINAL_INTERVIEW", label: "Final Interview" },
|
||||
{ value: "KARYAWAN_TETAP", label: "Karyawan Tetap" },
|
||||
{ value: "OFFERING_LETTER", label: "Offering Letter" },
|
||||
{ value: "CONTRACT_DOCUMENT", label: "Contract Document" },
|
||||
{ value: "DOCUMENT_CHECKING", label: "Document Checking" },
|
||||
{ value: "FINAL_RESULT", label: "Final Result" },
|
||||
];
|
||||
export const getLabel = (value: string) => {
|
||||
const status = statusMpp.find(
|
||||
(item) => item.value.toLowerCase() === value.toLowerCase()
|
||||
);
|
||||
return status ? status.label : value;
|
||||
};
|
||||
Loading…
Reference in New Issue