lib
This commit is contained in:
parent
372bf8bd52
commit
c3dbeb8c33
|
|
@ -79,7 +79,8 @@ export const Field: React.FC<any> = ({
|
|||
? "flex flex-row rounded-md flex-grow border-red-500 border items-center"
|
||||
: "flex flex-row rounded-md flex-grow items-center",
|
||||
is_disable ? "bg-gray-100" : "",
|
||||
"relative"
|
||||
"relative",
|
||||
""
|
||||
)}
|
||||
>
|
||||
{before && (
|
||||
|
|
|
|||
|
|
@ -269,7 +269,7 @@ function generateRandomColor(str: string): string {
|
|||
return color;
|
||||
}
|
||||
const getFileName = (url: string) => {
|
||||
if (url.startsWith("[")) {
|
||||
if (url && url.startsWith("[")) {
|
||||
try {
|
||||
const list = JSON.parse(url);
|
||||
if (list.length === 0) return "Empty";
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ import { useLocal } from "@/lib/utils/use-local";
|
|||
import Datepicker from "../../ui/Datepicker";
|
||||
import { Input } from "../../ui/input";
|
||||
import { Textarea } from "../../ui/text-area";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import tinycolor from "tinycolor2";
|
||||
import { FieldColorPicker } from "../../ui/FieldColorPopover";
|
||||
import { FaRegStar, FaStar } from "react-icons/fa6";
|
||||
import { Rating } from "../../ui/ratings";
|
||||
import { getNumber } from "@/lib/utils/getNumber";
|
||||
|
||||
export const TypeInput: React.FC<any> = ({
|
||||
name,
|
||||
|
|
@ -17,7 +20,15 @@ export const TypeInput: React.FC<any> = ({
|
|||
onChange,
|
||||
className,
|
||||
}) => {
|
||||
const [hover, setHover] = useState(0); // State untuk menyimpan nilai hover
|
||||
|
||||
let value: any = fm.data?.[name] || "";
|
||||
const [rating, setRating] = useState(value); // State untuk menyimpan nilai rating
|
||||
const handleClick = (index: number) => {
|
||||
setRating(index); // Update nilai rating
|
||||
fm.data[name] = rating + 1;
|
||||
fm.render();
|
||||
};
|
||||
const input = useLocal({
|
||||
value: 0 as any,
|
||||
ref: null as any,
|
||||
|
|
@ -35,6 +46,8 @@ export const TypeInput: React.FC<any> = ({
|
|||
const convertColor = tinycolor(meta.inputValue);
|
||||
meta.rgbValue = convertColor.toRgbString();
|
||||
meta.render();
|
||||
} else {
|
||||
setRating(value ? value - 1 : value);
|
||||
}
|
||||
}, [value]);
|
||||
useEffect(() => {
|
||||
|
|
@ -85,6 +98,59 @@ export const TypeInput: React.FC<any> = ({
|
|||
);
|
||||
break;
|
||||
|
||||
case "rating":
|
||||
return (
|
||||
<div className="flex">
|
||||
<Rating
|
||||
rating={getNumber(fm.data?.[name])}
|
||||
totalStars={5}
|
||||
size={24}
|
||||
variant="yellow"
|
||||
disabled={disabled}
|
||||
className="h-1"
|
||||
showText={false}
|
||||
onRatingChange={(e) => {
|
||||
fm.data[name] = getNumber(e);
|
||||
fm.render();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<div className="flex">
|
||||
{Array.from({ length: 5 }, (_, index) => index + 1).map(
|
||||
(number) => (
|
||||
<button
|
||||
key={number}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleClick(number);
|
||||
}}
|
||||
onMouseEnter={() => setHover(number)} // Set nilai hover saat mouse masuk
|
||||
onMouseLeave={() => setHover(number)} // Reset nilai hover saat mouse keluar
|
||||
className={cx(
|
||||
"focus:outline-none px-0.5",
|
||||
disabled ? "" : "transition-transform duration-200"
|
||||
)}
|
||||
style={{
|
||||
transform:
|
||||
hover === number && !disabled ? "scale(1.2)" : "scale(1)",
|
||||
}}
|
||||
>
|
||||
{hover >= number || rating >= number ? (
|
||||
<FaStar className="text-yellow-400" /> // Star yang diisi (fill)
|
||||
) : (
|
||||
<FaRegStar className="text-gray-400" /> // Star yang kosong (outline)
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
break;
|
||||
case "color":
|
||||
return (
|
||||
<div className="flex flex-row items-center">
|
||||
|
|
|
|||
|
|
@ -770,7 +770,7 @@ export const TypeRichText: React.FC<any> = ({
|
|||
return (
|
||||
<div
|
||||
className={cx(
|
||||
"flex flex-col relative bg-white border border-gray-300 rounded-md w-full",
|
||||
"flex flex-col relative bg-white border border-gray-300 rounded-md w-full richtext-field",
|
||||
css`
|
||||
.tiptap h1 {
|
||||
font-size: 1.4rem !important;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Star } from "lucide-react";
|
||||
import { cn } from "@/lib/utils/utils";
|
||||
|
||||
const ratingVariants = {
|
||||
default: {
|
||||
star: "text-foreground",
|
||||
emptyStar: "text-muted-foreground",
|
||||
},
|
||||
destructive: {
|
||||
star: "text-red-500",
|
||||
emptyStar: "text-red-200",
|
||||
},
|
||||
yellow: {
|
||||
star: "text-yellow-300",
|
||||
emptyStar: "text-yellow-300",
|
||||
},
|
||||
};
|
||||
|
||||
interface RatingProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
rating: number;
|
||||
totalStars?: number;
|
||||
size?: number;
|
||||
fill?: boolean;
|
||||
Icon?: React.ReactElement;
|
||||
variant?: keyof typeof ratingVariants;
|
||||
onRatingChange?: (rating: number) => void;
|
||||
showText?: boolean; // Add showText prop
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const Rating = ({
|
||||
rating: initialRating,
|
||||
totalStars = 5,
|
||||
size = 20,
|
||||
fill = true,
|
||||
Icon = <Star />,
|
||||
variant = "default",
|
||||
onRatingChange,
|
||||
showText = true, // Default to true if disabled prop is not provided
|
||||
disabled = false, // Default to false if disabled prop is not provided
|
||||
...props
|
||||
}: RatingProps) => {
|
||||
const [hoverRating, setHoverRating] = useState<number | null>(null);
|
||||
const [currentRating, setCurrentRating] = useState(initialRating);
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
|
||||
const handleMouseEnter = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!disabled) {
|
||||
setIsHovering(true);
|
||||
const starIndex = parseInt(
|
||||
(event.currentTarget as HTMLDivElement).dataset.starIndex || "0"
|
||||
);
|
||||
setHoverRating(starIndex);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeave = () => {
|
||||
setIsHovering(false);
|
||||
setHoverRating(null);
|
||||
};
|
||||
|
||||
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (!disabled) {
|
||||
const starIndex = parseInt(
|
||||
(event.currentTarget as HTMLDivElement).dataset.starIndex || "0"
|
||||
);
|
||||
setCurrentRating(starIndex);
|
||||
setHoverRating(null);
|
||||
if (onRatingChange) {
|
||||
onRatingChange(starIndex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const displayRating = disabled ? initialRating : hoverRating ?? currentRating;
|
||||
const fullStars = Math.floor(displayRating);
|
||||
const partialStar =
|
||||
displayRating % 1 > 0 ? (
|
||||
<PartialStar
|
||||
fillPercentage={displayRating % 1}
|
||||
size={size}
|
||||
className={cn(ratingVariants[variant].star)}
|
||||
Icon={Icon}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("flex w-fit flex-col gap-2", {
|
||||
"pointer-events-none": disabled,
|
||||
})}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
{...props}
|
||||
>
|
||||
<div className="flex items-center" onMouseEnter={handleMouseEnter}>
|
||||
{[...Array(fullStars)].map((_, i) =>
|
||||
React.cloneElement(Icon, {
|
||||
key: i,
|
||||
size,
|
||||
className: cn(
|
||||
fill ? "fill-current stroke-1" : "fill-transparent",
|
||||
ratingVariants[variant].star
|
||||
),
|
||||
onClick: handleClick,
|
||||
onMouseEnter: handleMouseEnter,
|
||||
"data-star-index": i + 1,
|
||||
})
|
||||
)}
|
||||
{partialStar}
|
||||
{[
|
||||
...Array(Math.max(0, totalStars - fullStars - (partialStar ? 1 : 0))),
|
||||
].map((_, i) =>
|
||||
React.cloneElement(Icon, {
|
||||
key: i + fullStars + 1,
|
||||
size,
|
||||
className: cn("stroke-1", ratingVariants[variant].emptyStar),
|
||||
onClick: handleClick,
|
||||
onMouseEnter: handleMouseEnter,
|
||||
"data-star-index": i + fullStars + 1,
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
{showText && (
|
||||
<span className="text-xs text-muted-foreground font-semibold">
|
||||
Current Rating: {`${currentRating}`}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface PartialStarProps {
|
||||
fillPercentage: number;
|
||||
size: number;
|
||||
className?: string;
|
||||
Icon: React.ReactElement;
|
||||
}
|
||||
|
||||
const PartialStar = ({
|
||||
fillPercentage,
|
||||
size,
|
||||
className,
|
||||
Icon,
|
||||
}: PartialStarProps) => {
|
||||
return (
|
||||
<div style={{ position: "relative", display: "inline-block" }}>
|
||||
{React.cloneElement(Icon, {
|
||||
size,
|
||||
className: cn("fill-transparent", className),
|
||||
})}
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
overflow: "hidden",
|
||||
width: `${fillPercentage * 100}%`,
|
||||
}}
|
||||
>
|
||||
{React.cloneElement(Icon, {
|
||||
size,
|
||||
className: cn("fill-current", className),
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -42,7 +42,7 @@ export const apix = async ({
|
|||
const requestData =
|
||||
type === "form" && data
|
||||
? Object.entries(data as any).reduce((formData, [key, value]) => {
|
||||
formData.append(key, value as any);
|
||||
formData.append(key.replace(/\[\d+\]/, ""), value as any);
|
||||
return formData;
|
||||
}, new FormData())
|
||||
: data;
|
||||
|
|
@ -105,3 +105,13 @@ export const apix = async ({
|
|||
throw error;
|
||||
}
|
||||
};
|
||||
function removeIndexFromKey(obj: any) {
|
||||
let result = {} as any;
|
||||
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
const newKey = key.replace(/\[\d+\]/, ""); // Hapus indeks [n] dari key
|
||||
result[newKey] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
import { normalDate } from "./date";
|
||||
|
||||
export const flattenObject = (
|
||||
obj: any,
|
||||
parentKey: string = "",
|
||||
result: any = {},
|
||||
idx: any = ""
|
||||
): any => {
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
// Buat key baru
|
||||
const newKey = parentKey
|
||||
? `${parentKey}.${key}${idx ? `[${idx}]` : ``}`
|
||||
: key;
|
||||
console.log({ newKey });
|
||||
if (Array.isArray(value)) {
|
||||
// Jika value adalah array, loop dan tambahkan indeks
|
||||
value.forEach((item, index) => {
|
||||
if (typeof item === "object" && item !== null) {
|
||||
// Rekursi jika elemen adalah object
|
||||
flattenObject(item, `${newKey}`, result, `${index}`);
|
||||
} else {
|
||||
console.log(`${newKey}.${key}[${index}]`);
|
||||
// Simpan value langsung jika elemen bukan object
|
||||
result[`${newKey}.${key}[${index}]`] = item;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Simpan value langsung jika bukan array atau object
|
||||
if (["end_date", "birth_date"].includes(key)) {
|
||||
result[newKey] = normalDate(value as any);
|
||||
} else {
|
||||
if (["ktp", "certificate", "curriculum_vitae"].includes(key)) {
|
||||
if (typeof value !== "string" && value) result[newKey] = value;
|
||||
} else {
|
||||
result[newKey] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import dotenv from 'dotenv';
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
export const siteurl = (param: string) => {
|
||||
return `${process.env.NEXT_PUBLIC_BASE_URL + param}`
|
||||
}
|
||||
if (param.startsWith("http")) return param;
|
||||
return `${process.env.NEXT_PUBLIC_BASE_URL + param}`;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue