mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 05:22:02 +00:00
refactor(FE-114): replace button elements with Button component for consistency and improved styling
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user