mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-21 13:55:45 +00:00
feat(FE-92-94): Slicing UI detail chickin & refactor number input chickin form
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
"use client";
|
||||
|
||||
import { HTMLAttributes, ReactNode } from "react";
|
||||
|
||||
import { cn } from "@/lib/helper";
|
||||
import Image from "next/image";
|
||||
|
||||
export interface CardProps
|
||||
extends Omit<HTMLAttributes<HTMLDivElement>, "className"> {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
image?: string;
|
||||
imageAlt?: string;
|
||||
imageWidth?: number;
|
||||
imageHeight?: number;
|
||||
actions?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
className?: {
|
||||
wrapper?: string;
|
||||
image?: string;
|
||||
body?: string;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
actions?: string;
|
||||
footer?: string;
|
||||
};
|
||||
variant?: "default" | "compact" | "bordered" | "shadow" | "image-full";
|
||||
size?: "sm" | "md" | "lg";
|
||||
}
|
||||
|
||||
const Card = ({
|
||||
title,
|
||||
subtitle,
|
||||
image,
|
||||
imageAlt,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
actions,
|
||||
footer,
|
||||
className,
|
||||
variant = "default",
|
||||
size = "md",
|
||||
children,
|
||||
...props
|
||||
}: CardProps) => {
|
||||
const getCardClasses = () => {
|
||||
const baseClasses = "card bg-base-100";
|
||||
|
||||
const variantClasses = {
|
||||
default: "",
|
||||
compact: "card-compact",
|
||||
bordered: "border border-base-300",
|
||||
shadow: "shadow-xl",
|
||||
"image-full": "card-side card-compact shadow-xl",
|
||||
};
|
||||
|
||||
const sizeClasses = {
|
||||
sm: "w-64",
|
||||
md: "w-96",
|
||||
lg: "w-[28rem]",
|
||||
};
|
||||
|
||||
return cn(
|
||||
baseClasses,
|
||||
variantClasses[variant],
|
||||
variant !== "image-full" ? sizeClasses[size] : "",
|
||||
className?.wrapper,
|
||||
);
|
||||
};
|
||||
|
||||
const getImageDimensions = () => {
|
||||
if (variant === "image-full") {
|
||||
return {
|
||||
width: imageWidth || 128,
|
||||
height: imageHeight || 128,
|
||||
};
|
||||
}
|
||||
|
||||
const cardWidths = {
|
||||
sm: 256, // w-64
|
||||
md: 384, // w-96
|
||||
lg: 448, // w-[28rem]
|
||||
};
|
||||
|
||||
return {
|
||||
width: imageWidth || cardWidths[size],
|
||||
height: imageHeight || 192,
|
||||
};
|
||||
};
|
||||
|
||||
const getImageClasses = () => {
|
||||
if (variant === "image-full") {
|
||||
return cn("object-cover", className?.image);
|
||||
}
|
||||
return cn("w-full object-cover", className?.image);
|
||||
};
|
||||
|
||||
const getBodyClasses = () => {
|
||||
const baseClasses = "card-body";
|
||||
|
||||
if (variant === "compact" || variant === "image-full") {
|
||||
return cn(baseClasses, "p-4", className?.body);
|
||||
}
|
||||
|
||||
return cn(baseClasses, "p-6", className?.body);
|
||||
};
|
||||
|
||||
const getTitleClasses = () => {
|
||||
const sizeClasses = {
|
||||
sm: "text-lg",
|
||||
md: "text-xl",
|
||||
lg: "text-2xl",
|
||||
};
|
||||
|
||||
return cn("card-title font-bold", sizeClasses[size], className?.title);
|
||||
};
|
||||
|
||||
const getSubtitleClasses = () => {
|
||||
return cn("text-base-content/70 text-sm mt-1", className?.subtitle);
|
||||
};
|
||||
|
||||
const getActionsClasses = () => {
|
||||
return cn("card-actions justify-end mt-4", className?.actions);
|
||||
};
|
||||
|
||||
const getFooterClasses = () => {
|
||||
return cn("border-t border-base-300 mt-4 pt-4", className?.footer);
|
||||
};
|
||||
|
||||
if (variant === "image-full" && image) {
|
||||
const imageDimensions = getImageDimensions();
|
||||
return (
|
||||
<div className={getCardClasses()} {...props}>
|
||||
<figure>
|
||||
<Image
|
||||
src={image}
|
||||
alt={imageAlt || title || "Card image"}
|
||||
width={imageDimensions.width}
|
||||
height={imageDimensions.height}
|
||||
className={getImageClasses()}
|
||||
/>
|
||||
</figure>
|
||||
<div className={getBodyClasses()}>
|
||||
{title && <h2 className={getTitleClasses()}>{title}</h2>}
|
||||
{subtitle && <p className={getSubtitleClasses()}>{subtitle}</p>}
|
||||
{children}
|
||||
{actions && <div className={getActionsClasses()}>{actions}</div>}
|
||||
</div>
|
||||
{footer && <div className={getFooterClasses()}>{footer}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={getCardClasses()} {...props}>
|
||||
{image && (
|
||||
<figure>
|
||||
<Image
|
||||
src={image}
|
||||
alt={imageAlt || title || "Card image"}
|
||||
width={getImageDimensions().width}
|
||||
height={getImageDimensions().height}
|
||||
className={getImageClasses()}
|
||||
/>
|
||||
</figure>
|
||||
)}
|
||||
<div className={getBodyClasses()}>
|
||||
{title && <h2 className={getTitleClasses()}>{title}</h2>}
|
||||
{subtitle && <p className={getSubtitleClasses()}>{subtitle}</p>}
|
||||
{children}
|
||||
{actions && <div className={getActionsClasses()}>{actions}</div>}
|
||||
</div>
|
||||
{footer && <div className={getFooterClasses()}>{footer}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Card;
|
||||
@@ -0,0 +1,53 @@
|
||||
'use client';
|
||||
|
||||
import { ChangeEvent } from 'react';
|
||||
import { NumericFormat, OnValueChange } from 'react-number-format';
|
||||
import TextInput, { TextInputProps } from '@/components/input/TextInput';
|
||||
|
||||
interface NumberInputProps extends Omit<TextInputProps, 'type'> {
|
||||
thousandSeparator?: string;
|
||||
decimalSeparator?: string;
|
||||
decimalScale?: number;
|
||||
allowNegative?: boolean;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
fixedDecimalScale?: boolean;
|
||||
}
|
||||
|
||||
const NumberInput = ({
|
||||
thousandSeparator = ',',
|
||||
decimalSeparator = '.',
|
||||
decimalScale = 5,
|
||||
allowNegative = true,
|
||||
onChange,
|
||||
...restProps
|
||||
}: NumberInputProps) => {
|
||||
const valueChangeHandler: OnValueChange = (
|
||||
numberFormatValues,
|
||||
sourceInfo
|
||||
) => {
|
||||
const newChangeEvent = sourceInfo.event as
|
||||
| ChangeEvent<HTMLInputElement>
|
||||
| undefined;
|
||||
|
||||
if (newChangeEvent) {
|
||||
newChangeEvent.target.value = numberFormatValues.value;
|
||||
|
||||
onChange?.(newChangeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<NumericFormat
|
||||
thousandSeparator={thousandSeparator}
|
||||
decimalSeparator={decimalSeparator}
|
||||
customInput={TextInput}
|
||||
onValueChange={valueChangeHandler}
|
||||
decimalScale={decimalScale}
|
||||
allowNegative={allowNegative}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default NumberInput;
|
||||
@@ -11,8 +11,8 @@ import RowDropdownOptions from '@/components/table/RowDropdownOptions';
|
||||
import { TableRowSizeSelector } from '@/components/table/TableRowSizeSelector';
|
||||
import { ROWS_OPTIONS } from '@/config/constant';
|
||||
import { isResponseSuccess } from '@/lib/api-helper';
|
||||
import { cn } from '@/lib/helper';
|
||||
import { ChickinApi, ProjectFlockApi } from '@/services/api/production';
|
||||
import { cn, formatNumber } from '@/lib/helper';
|
||||
import { ChickinApi } from '@/services/api/production';
|
||||
import { useTableFilter } from '@/services/hooks/useTableFilter';
|
||||
import { Chickin } from '@/types/api/production/chickin';
|
||||
import { Icon } from '@iconify/react';
|
||||
@@ -124,6 +124,13 @@ const ChickinTable = () => {
|
||||
{
|
||||
accessorFn: (row) => row.quantity,
|
||||
header: 'Jumlah Chickin',
|
||||
cell: (props) => {
|
||||
if (props.row.original.quantity) {
|
||||
return formatNumber(props.row.original.quantity);
|
||||
} else {
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorFn: (row) => row.chick_in_date,
|
||||
@@ -159,7 +166,7 @@ const ChickinTable = () => {
|
||||
deleteModal.openModal();
|
||||
};
|
||||
|
||||
const editClickHandler = () => {
|
||||
const editClickHandler = () => {
|
||||
setSelectedChickin(props.row.original);
|
||||
chickinModal.openModal();
|
||||
};
|
||||
@@ -279,7 +286,7 @@ const RowOptionsMenu = ({
|
||||
'p-2.5 mr-2 flex flex-col gap-1 bg-base-100 rounded-box z-10 border border-black/10 shadow'
|
||||
)}
|
||||
>
|
||||
{/* <Button
|
||||
<Button
|
||||
href={`/production/chickin/detail?chickinId=${props.row.original.id}`}
|
||||
variant='ghost'
|
||||
color='primary'
|
||||
@@ -287,7 +294,7 @@ const RowOptionsMenu = ({
|
||||
>
|
||||
<Icon icon='mdi:eye-outline' width={16} height={16} />
|
||||
Detail
|
||||
</Button> */}
|
||||
</Button>
|
||||
<Button
|
||||
variant='ghost'
|
||||
color='warning'
|
||||
|
||||
@@ -20,6 +20,7 @@ import toast from 'react-hot-toast';
|
||||
import { Icon } from '@iconify/react';
|
||||
import TextArea from '@/components/input/TextArea';
|
||||
import TextInput from '@/components/input/TextInput';
|
||||
import NumberInput from '@/components/input/NumberInput';
|
||||
|
||||
interface ChickinFormProps {
|
||||
formType?: 'add' | 'detail' | 'edit';
|
||||
@@ -46,7 +47,10 @@ const ChickinForm = ({
|
||||
return {
|
||||
chick_in_date: formatDateForInput(initialValues?.chick_in_date) ?? '',
|
||||
note: initialValues?.note ?? '',
|
||||
quantity: initialValues?.quantity ?? initialValues?.project_flock_kandang?.available_quantity ?? 0,
|
||||
quantity:
|
||||
initialValues?.quantity ??
|
||||
initialValues?.project_flock_kandang?.available_quantity ??
|
||||
0,
|
||||
};
|
||||
}, [initialValues]);
|
||||
|
||||
@@ -71,10 +75,7 @@ const ChickinForm = ({
|
||||
payload: UpdateChickinPayload,
|
||||
afterSubmit: (() => void) | undefined
|
||||
) => {
|
||||
const res = await ChickinApi.update(
|
||||
payload.project_flock_kandang_id as number,
|
||||
payload
|
||||
);
|
||||
const res = await ChickinApi.update(payload.id, payload);
|
||||
if (isResponseError(res)) {
|
||||
setChickinFormErrorMessage(res.message);
|
||||
return;
|
||||
@@ -95,7 +96,10 @@ const ChickinForm = ({
|
||||
// reset error message
|
||||
setChickinFormErrorMessage('');
|
||||
|
||||
if (initialValues?.project_flock_kandang?.id == undefined) {
|
||||
if (
|
||||
initialValues?.project_flock_kandang?.id == undefined ||
|
||||
(formType == 'edit' && initialValues?.id == undefined)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -105,9 +109,11 @@ const ChickinForm = ({
|
||||
project_flock_kandang_id: initialValues?.project_flock_kandang?.id,
|
||||
note: values.note,
|
||||
quantity: values.quantity,
|
||||
id: initialValues.id ?? 0,
|
||||
};
|
||||
|
||||
// cek type form yang disubmit
|
||||
console.log(formType);
|
||||
switch (formType) {
|
||||
case 'add':
|
||||
handleCreate(payload, afterSubmit);
|
||||
@@ -146,12 +152,12 @@ const ChickinForm = ({
|
||||
}
|
||||
errorMessage={formik.errors.chick_in_date}
|
||||
/>
|
||||
<TextInput
|
||||
<NumberInput
|
||||
value={formik.values.quantity}
|
||||
onChange={formik.handleChange}
|
||||
onBlur={formik.handleBlur}
|
||||
name='quantity'
|
||||
label='Jumlah Chickin'
|
||||
label='Jumlah (Ekor)'
|
||||
required
|
||||
isError={
|
||||
(formik.touched.quantity && Boolean(formik.errors.quantity)) ||
|
||||
@@ -162,7 +168,6 @@ const ChickinForm = ({
|
||||
? 'Masukan Persediaan Day Old Chick terlebih dahulu.'
|
||||
: formik.errors.quantity
|
||||
}
|
||||
type='number'
|
||||
readOnly
|
||||
/>
|
||||
<TextArea
|
||||
|
||||
@@ -144,7 +144,6 @@ const ProjectFlockTable = () => {
|
||||
const deleteModal = useModal();
|
||||
const confirmModal = useModal();
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
const [selectedFlocks, setSelectedFlocks] = useState<ProjectFlock[]>([]);
|
||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||
|
||||
// Fetch Data
|
||||
|
||||
Reference in New Issue
Block a user