This commit is contained in:
faisolavolut 2025-01-21 14:20:18 +07:00
parent 372bf8bd52
commit c3dbeb8c33
8 changed files with 296 additions and 8 deletions

View File

@ -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 && (

View File

@ -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";

View File

@ -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">

View File

@ -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;

169
components/ui/ratings.tsx Normal file
View File

@ -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>
);
};

View File

@ -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;
}

41
utils/flattenObject.ts Normal file
View File

@ -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;
};

View File

@ -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}`;
};