mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-23 23:05:46 +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 { useMemo, useState } from 'react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
|
||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
@@ -262,7 +263,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
body: 'flex flex-col gap-6',
|
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
|
<TextInput
|
||||||
required
|
required
|
||||||
label='Project Flock Kandang ID'
|
label='Project Flock Kandang ID'
|
||||||
@@ -355,7 +356,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<tr>
|
<tr>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<th>
|
<th>
|
||||||
<div className='flex justify-center'>
|
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name='select-all-body-weights'
|
name='select-all-body-weights'
|
||||||
checked={
|
checked={
|
||||||
@@ -374,8 +374,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setSelectedBodyWeights([]);
|
setSelectedBodyWeights([]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
classNames={{
|
||||||
|
wrapper: 'flex justify-center',
|
||||||
|
checkbox: 'checkbox checkbox-sm',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</th>
|
</th>
|
||||||
)}
|
)}
|
||||||
<th>
|
<th>
|
||||||
@@ -404,29 +407,31 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
{formik.values.body_weights?.map((bw, idx) => (
|
{formik.values.body_weights?.map((bw, idx) => (
|
||||||
<tr key={`body-weight-${idx}`}>
|
<tr key={`body-weight-${idx}`}>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td className="!align-middle">
|
||||||
<div className='flex justify-center'>
|
<CheckboxInput
|
||||||
<CheckboxInput
|
name={`body-weight-${idx}`}
|
||||||
name={`body-weight-${idx}`}
|
checked={selectedBodyWeights.includes(idx)}
|
||||||
checked={selectedBodyWeights.includes(idx)}
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
if (e.target.checked) {
|
||||||
if (e.target.checked) {
|
setSelectedBodyWeights([
|
||||||
setSelectedBodyWeights([
|
...selectedBodyWeights,
|
||||||
...selectedBodyWeights,
|
idx,
|
||||||
idx,
|
]);
|
||||||
]);
|
} else {
|
||||||
} else {
|
setSelectedBodyWeights(
|
||||||
setSelectedBodyWeights(
|
selectedBodyWeights.filter((i) => i !== idx),
|
||||||
selectedBodyWeights.filter((i) => i !== idx)
|
);
|
||||||
);
|
}
|
||||||
}
|
}}
|
||||||
}}
|
classNames={{
|
||||||
/>
|
wrapper: 'flex justify-center',
|
||||||
</div>
|
checkbox: 'checkbox checkbox-sm',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
<td>
|
<td>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
name={`body_weights.${idx}.weight`}
|
name={`body_weights.${idx}.weight`}
|
||||||
type='number'
|
type='number'
|
||||||
@@ -494,13 +499,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</td>
|
</td>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td>
|
||||||
<button
|
<div className='flex justify-center'>
|
||||||
type='button'
|
<Button
|
||||||
onClick={() => removeBodyWeight(idx)}
|
type='button'
|
||||||
className='btn btn-error btn-sm'
|
color='error'
|
||||||
>
|
onClick={() => removeBodyWeight(idx)}
|
||||||
<Icon icon='mdi:trash-can' className='h-4 w-4' />
|
>
|
||||||
</button>
|
<Icon icon='mdi:trash-can' width={24} height={24} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -509,25 +516,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{type !== 'detail' && (
|
{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 && (
|
{selectedBodyWeights.length > 0 && (
|
||||||
<button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
|
color='error'
|
||||||
onClick={removeSelectedBodyWeights}
|
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})
|
Hapus Terpilih ({selectedBodyWeights.length})
|
||||||
</button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
|
color='success'
|
||||||
onClick={addBodyWeight}
|
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
|
Tambah Bobot Badan
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
@@ -546,7 +556,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<tr>
|
<tr>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<th>
|
<th>
|
||||||
<div className='flex justify-center'>
|
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name='select-all-stocks'
|
name='select-all-stocks'
|
||||||
checked={
|
checked={
|
||||||
@@ -564,8 +573,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setSelectedStocks([]);
|
setSelectedStocks([]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
classNames={{
|
||||||
|
wrapper: 'flex justify-center',
|
||||||
|
checkbox: 'checkbox checkbox-sm',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</th>
|
</th>
|
||||||
)}
|
)}
|
||||||
<th>
|
<th>
|
||||||
@@ -588,26 +600,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
{formik.values.stocks?.map((stock, idx) => (
|
{formik.values.stocks?.map((stock, idx) => (
|
||||||
<tr key={`stock-${idx}`}>
|
<tr key={`stock-${idx}`}>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td className="!align-middle">
|
||||||
<div className='flex justify-center'>
|
<CheckboxInput
|
||||||
<CheckboxInput
|
name={`stock-${idx}`}
|
||||||
name={`stock-${idx}`}
|
checked={selectedStocks.includes(idx)}
|
||||||
checked={selectedStocks.includes(idx)}
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
if (e.target.checked) {
|
||||||
if (e.target.checked) {
|
setSelectedStocks([...selectedStocks, idx]);
|
||||||
setSelectedStocks([...selectedStocks, idx]);
|
} else {
|
||||||
} else {
|
setSelectedStocks(
|
||||||
setSelectedStocks(
|
selectedStocks.filter((i) => i !== idx),
|
||||||
selectedStocks.filter((i) => i !== idx)
|
);
|
||||||
);
|
}
|
||||||
}
|
}}
|
||||||
}}
|
classNames={{
|
||||||
/>
|
wrapper: 'flex justify-center',
|
||||||
</div>
|
checkbox: 'checkbox checkbox-sm',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
<td>
|
<td>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
name={`stocks.${idx}.product_warehouse_id`}
|
name={`stocks.${idx}.product_warehouse_id`}
|
||||||
type='number'
|
type='number'
|
||||||
@@ -728,13 +742,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</td>
|
</td>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td>
|
||||||
<button
|
<div className='flex justify-center'>
|
||||||
type='button'
|
<Button
|
||||||
onClick={() => removeStock(idx)}
|
type='button'
|
||||||
className='btn btn-error btn-sm'
|
color='error'
|
||||||
>
|
onClick={() => removeStock(idx)}
|
||||||
<Icon icon='mdi:trash-can' className='h-4 w-4' />
|
>
|
||||||
</button>
|
<Icon icon='mdi:trash-can' width={24} height={24} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -743,25 +759,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{type !== 'detail' && (
|
{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 && (
|
{selectedStocks.length > 0 && (
|
||||||
<button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
|
color='error'
|
||||||
onClick={removeSelectedStocks}
|
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})
|
Hapus Terpilih ({selectedStocks.length})
|
||||||
</button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
|
color='success'
|
||||||
onClick={addStock}
|
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
|
Tambah Stok
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
@@ -780,7 +799,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
<tr>
|
<tr>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<th>
|
<th>
|
||||||
<div className='flex justify-center'>
|
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name='select-all-depletions'
|
name='select-all-depletions'
|
||||||
checked={
|
checked={
|
||||||
@@ -799,8 +817,11 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
setSelectedDepletions([]);
|
setSelectedDepletions([]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
classNames={{
|
||||||
|
wrapper: 'flex justify-center',
|
||||||
|
checkbox: 'checkbox checkbox-sm',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</th>
|
</th>
|
||||||
)}
|
)}
|
||||||
<th>
|
<th>
|
||||||
@@ -838,29 +859,31 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
{formik.values.depletions?.map((depletion, idx) => (
|
{formik.values.depletions?.map((depletion, idx) => (
|
||||||
<tr key={`depletion-${idx}`}>
|
<tr key={`depletion-${idx}`}>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td className="!align-middle">
|
||||||
<div className='flex justify-center'>
|
<CheckboxInput
|
||||||
<CheckboxInput
|
name={`depletion-${idx}`}
|
||||||
name={`depletion-${idx}`}
|
checked={selectedDepletions.includes(idx)}
|
||||||
checked={selectedDepletions.includes(idx)}
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
if (e.target.checked) {
|
||||||
if (e.target.checked) {
|
setSelectedDepletions([
|
||||||
setSelectedDepletions([
|
...selectedDepletions,
|
||||||
...selectedDepletions,
|
idx,
|
||||||
idx,
|
]);
|
||||||
]);
|
} else {
|
||||||
} else {
|
setSelectedDepletions(
|
||||||
setSelectedDepletions(
|
selectedDepletions.filter((i) => i !== idx),
|
||||||
selectedDepletions.filter((i) => i !== idx)
|
);
|
||||||
);
|
}
|
||||||
}
|
}}
|
||||||
}}
|
classNames={{
|
||||||
/>
|
wrapper: 'flex justify-center',
|
||||||
</div>
|
checkbox: 'checkbox checkbox-sm',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
<td>
|
<td>
|
||||||
<TextInput
|
<TextInput
|
||||||
required
|
required
|
||||||
name={`depletions.${idx}.product_warehouse_id`}
|
name={`depletions.${idx}.product_warehouse_id`}
|
||||||
type='number'
|
type='number'
|
||||||
@@ -954,13 +977,15 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</td>
|
</td>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td>
|
||||||
<button
|
<div className='flex justify-center'>
|
||||||
type='button'
|
<Button
|
||||||
onClick={() => removeDepletion(idx)}
|
type='button'
|
||||||
className='btn btn-error btn-sm'
|
color='error'
|
||||||
>
|
onClick={() => removeDepletion(idx)}
|
||||||
<Icon icon='mdi:trash-can' className='h-4 w-4' />
|
>
|
||||||
</button>
|
<Icon icon='mdi:trash-can' width={24} height={24} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -969,25 +994,28 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{type !== 'detail' && (
|
{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 && (
|
{selectedDepletions.length > 0 && (
|
||||||
<button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
|
color='error'
|
||||||
onClick={removeSelectedDepletions}
|
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})
|
Hapus Terpilih ({selectedDepletions.length})
|
||||||
</button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<button
|
<Button
|
||||||
type='button'
|
type='button'
|
||||||
|
color='success'
|
||||||
onClick={addDepletion}
|
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
|
Tambah Depletion
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user