refactor(FE-114): replace button elements with Button component for consistency and improved styling

This commit is contained in:
rstubryan
2025-10-23 20:44:59 +07:00
parent 392e211181
commit b653cc1dab
2 changed files with 140 additions and 177 deletions
-65
View File
@@ -1,65 +0,0 @@
'use client';
import { ReactNode } from 'react';
import { cn } from '@/lib/helper';
type FieldMessageTone = 'error' | 'info' | 'success';
export interface FieldMessageProps {
message?: ReactNode;
tone?: FieldMessageTone;
isVisible?: boolean;
persistent?: boolean;
className?: string;
ariaLive?: 'off' | 'polite' | 'assertive';
}
const toneClassName: Record<FieldMessageTone, string> = {
error: 'text-error',
info: 'text-base-content/60',
success: 'text-success',
};
/**
* Shared helper to render bottom field feedback without causing layout shift.
* Keeps a minimal slot height, but expands when the content wraps onto multiple lines.
*/
export const FieldMessage = ({
message,
tone = 'info',
isVisible,
persistent = true,
className,
ariaLive,
}: FieldMessageProps) => {
const hasMessage = Boolean(message);
const visible = isVisible ?? hasMessage;
const liveRegion = ariaLive ?? (tone === 'error' ? 'assertive' : 'polite');
return (
<div
aria-live={liveRegion}
aria-hidden={!visible && !hasMessage}
className={cn(
'relative w-full text-sm leading-5 transition-[opacity,transform] duration-150 ease-out',
persistent && 'min-h-[1.25rem]',
className
)}
>
<span
className={cn(
'block whitespace-pre-line',
toneClassName[tone],
visible
? 'opacity-100 translate-y-0'
: 'opacity-0 -translate-y-1 pointer-events-none'
)}
>
{visible || persistent ? (message ?? '\u00A0') : message}
</span>
</div>
);
};
export default FieldMessage;
@@ -3,6 +3,7 @@
import { useMemo, useState } from 'react';
import { useFormik } from 'formik';
import { Icon } from '@iconify/react';
import Button from '@/components/Button';
import TextInput from '@/components/input/TextInput';
import CheckboxInput from '@/components/input/CheckboxInput';
@@ -262,7 +263,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
body: 'flex flex-col gap-6',
}}
>
<div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6'>
<div className='grid grid-cols-1 md:grid-cols-2 gap-6'>
<TextInput
required
label='Project Flock Kandang ID'
@@ -355,7 +356,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<tr>
{type !== 'detail' && (
<th>
<div className='flex justify-center'>
<CheckboxInput
name='select-all-body-weights'
checked={
@@ -374,8 +374,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
setSelectedBodyWeights([]);
}
}}
classNames={{
wrapper: 'flex justify-center',
checkbox: 'checkbox checkbox-sm',
}}
/>
</div>
</th>
)}
<th>
@@ -404,29 +407,31 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{formik.values.body_weights?.map((bw, idx) => (
<tr key={`body-weight-${idx}`}>
{type !== 'detail' && (
<td>
<div className='flex justify-center'>
<CheckboxInput
name={`body-weight-${idx}`}
checked={selectedBodyWeights.includes(idx)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setSelectedBodyWeights([
...selectedBodyWeights,
idx,
]);
} else {
setSelectedBodyWeights(
selectedBodyWeights.filter((i) => i !== idx)
);
}
}}
/>
</div>
<td className="!align-middle">
<CheckboxInput
name={`body-weight-${idx}`}
checked={selectedBodyWeights.includes(idx)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setSelectedBodyWeights([
...selectedBodyWeights,
idx,
]);
} else {
setSelectedBodyWeights(
selectedBodyWeights.filter((i) => i !== idx),
);
}
}}
classNames={{
wrapper: 'flex justify-center',
checkbox: 'checkbox checkbox-sm',
}}
/>
</td>
)}
<td>
<TextInput
<TextInput
required
name={`body_weights.${idx}.weight`}
type='number'
@@ -494,13 +499,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</td>
{type !== 'detail' && (
<td>
<button
type='button'
onClick={() => removeBodyWeight(idx)}
className='btn btn-error btn-sm'
>
<Icon icon='mdi:trash-can' className='h-4 w-4' />
</button>
<div className='flex justify-center'>
<Button
type='button'
color='error'
onClick={() => removeBodyWeight(idx)}
>
<Icon icon='mdi:trash-can' width={24} height={24} />
</Button>
</div>
</td>
)}
</tr>
@@ -509,25 +516,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</table>
</div>
{type !== 'detail' && (
<div className='flex justify-end mt-4 gap-2'>
<div className='flex justify-center items-center mt-4 gap-4'>
{selectedBodyWeights.length > 0 && (
<button
<Button
type='button'
color='error'
onClick={removeSelectedBodyWeights}
className='btn btn-error btn-sm'
disabled={selectedBodyWeights.length === 0}
className='w-fit'
>
<Icon icon='mdi:trash-can' className='h-4 w-4' />
<Icon icon='mdi:trash-can' width={24} height={24} />
Hapus Terpilih ({selectedBodyWeights.length})
</button>
</Button>
)}
<button
<Button
type='button'
color='success'
onClick={addBodyWeight}
className='btn btn-success btn-sm'
className='w-fit'
>
<Icon icon='ic:round-plus' className='h-4 w-4' />
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah Bobot Badan
</button>
</Button>
</div>
)}
</Card>
@@ -546,7 +556,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<tr>
{type !== 'detail' && (
<th>
<div className='flex justify-center'>
<CheckboxInput
name='select-all-stocks'
checked={
@@ -564,8 +573,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
setSelectedStocks([]);
}
}}
classNames={{
wrapper: 'flex justify-center',
checkbox: 'checkbox checkbox-sm',
}}
/>
</div>
</th>
)}
<th>
@@ -588,26 +600,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{formik.values.stocks?.map((stock, idx) => (
<tr key={`stock-${idx}`}>
{type !== 'detail' && (
<td>
<div className='flex justify-center'>
<CheckboxInput
name={`stock-${idx}`}
checked={selectedStocks.includes(idx)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setSelectedStocks([...selectedStocks, idx]);
} else {
setSelectedStocks(
selectedStocks.filter((i) => i !== idx)
);
}
}}
/>
</div>
<td className="!align-middle">
<CheckboxInput
name={`stock-${idx}`}
checked={selectedStocks.includes(idx)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setSelectedStocks([...selectedStocks, idx]);
} else {
setSelectedStocks(
selectedStocks.filter((i) => i !== idx),
);
}
}}
classNames={{
wrapper: 'flex justify-center',
checkbox: 'checkbox checkbox-sm',
}}
/>
</td>
)}
<td>
<TextInput
<TextInput
required
name={`stocks.${idx}.product_warehouse_id`}
type='number'
@@ -728,13 +742,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</td>
{type !== 'detail' && (
<td>
<button
type='button'
onClick={() => removeStock(idx)}
className='btn btn-error btn-sm'
>
<Icon icon='mdi:trash-can' className='h-4 w-4' />
</button>
<div className='flex justify-center'>
<Button
type='button'
color='error'
onClick={() => removeStock(idx)}
>
<Icon icon='mdi:trash-can' width={24} height={24} />
</Button>
</div>
</td>
)}
</tr>
@@ -743,25 +759,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</table>
</div>
{type !== 'detail' && (
<div className='flex justify-end mt-4 gap-2'>
<div className='flex justify-center items-center mt-4 gap-4'>
{selectedStocks.length > 0 && (
<button
<Button
type='button'
color='error'
onClick={removeSelectedStocks}
className='btn btn-error btn-sm'
disabled={selectedStocks.length === 0}
className='w-fit'
>
<Icon icon='mdi:trash-can' className='h-4 w-4' />
<Icon icon='mdi:trash-can' width={24} height={24} />
Hapus Terpilih ({selectedStocks.length})
</button>
</Button>
)}
<button
<Button
type='button'
color='success'
onClick={addStock}
className='btn btn-success btn-sm'
className='w-fit'
>
<Icon icon='ic:round-plus' className='h-4 w-4' />
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah Stok
</button>
</Button>
</div>
)}
</Card>
@@ -780,7 +799,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<tr>
{type !== 'detail' && (
<th>
<div className='flex justify-center'>
<CheckboxInput
name='select-all-depletions'
checked={
@@ -799,8 +817,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
setSelectedDepletions([]);
}
}}
classNames={{
wrapper: 'flex justify-center',
checkbox: 'checkbox checkbox-sm',
}}
/>
</div>
</th>
)}
<th>
@@ -838,29 +859,31 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{formik.values.depletions?.map((depletion, idx) => (
<tr key={`depletion-${idx}`}>
{type !== 'detail' && (
<td>
<div className='flex justify-center'>
<CheckboxInput
name={`depletion-${idx}`}
checked={selectedDepletions.includes(idx)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setSelectedDepletions([
...selectedDepletions,
idx,
]);
} else {
setSelectedDepletions(
selectedDepletions.filter((i) => i !== idx)
);
}
}}
/>
</div>
<td className="!align-middle">
<CheckboxInput
name={`depletion-${idx}`}
checked={selectedDepletions.includes(idx)}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.checked) {
setSelectedDepletions([
...selectedDepletions,
idx,
]);
} else {
setSelectedDepletions(
selectedDepletions.filter((i) => i !== idx),
);
}
}}
classNames={{
wrapper: 'flex justify-center',
checkbox: 'checkbox checkbox-sm',
}}
/>
</td>
)}
<td>
<TextInput
<TextInput
required
name={`depletions.${idx}.product_warehouse_id`}
type='number'
@@ -954,13 +977,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</td>
{type !== 'detail' && (
<td>
<button
type='button'
onClick={() => removeDepletion(idx)}
className='btn btn-error btn-sm'
>
<Icon icon='mdi:trash-can' className='h-4 w-4' />
</button>
<div className='flex justify-center'>
<Button
type='button'
color='error'
onClick={() => removeDepletion(idx)}
>
<Icon icon='mdi:trash-can' width={24} height={24} />
</Button>
</div>
</td>
)}
</tr>
@@ -969,25 +994,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</table>
</div>
{type !== 'detail' && (
<div className='flex justify-end mt-4 gap-2'>
<div className='flex justify-center items-center mt-4 gap-4'>
{selectedDepletions.length > 0 && (
<button
<Button
type='button'
color='error'
onClick={removeSelectedDepletions}
className='btn btn-error btn-sm'
disabled={selectedDepletions.length === 0}
className='w-fit'
>
<Icon icon='mdi:trash-can' className='h-4 w-4' />
<Icon icon='mdi:trash-can' width={24} height={24} />
Hapus Terpilih ({selectedDepletions.length})
</button>
</Button>
)}
<button
<Button
type='button'
color='success'
onClick={addDepletion}
className='btn btn-success btn-sm'
className='w-fit'
>
<Icon icon='ic:round-plus' className='h-4 w-4' />
<Icon icon='ic:round-plus' width={24} height={24} />
Tambah Depletion
</button>
</Button>
</div>
)}
</Card>