Date: Thu, 23 Oct 2025 15:09:39 +0700
Subject: [PATCH 06/10] chore(FE-114): add inputmask and its type definitions
to package.json
---
package-lock.json | 27 ++++++++++++++++++++++++++-
package.json | 2 ++
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/package-lock.json b/package-lock.json
index a39060ef..4a583dbd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"axios": "^1.12.2",
"clsx": "^2.1.1",
"formik": "^2.4.6",
+ "inputmask": "^5.0.9",
"moment": "^2.30.1",
"next": "15.5.3",
"react": "19.1.0",
@@ -29,6 +30,7 @@
"@eslint/eslintrc": "^3",
"@iconify/react": "^6.0.2",
"@tailwindcss/postcss": "^4",
+ "@types/inputmask": "^5.0.7",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
@@ -1640,6 +1642,13 @@
"@types/react": "*"
}
},
+ "node_modules/@types/inputmask": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/@types/inputmask/-/inputmask-5.0.7.tgz",
+ "integrity": "sha512-uojbVPWzBQ/n/0jc/d16fLqmGasFIptbrLD2WrCPWArlk+5PgblOqH4EDkI3AoobXLAlOK5yF01V8jMmvMG5qg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -1675,6 +1684,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
"integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -1744,6 +1754,7 @@
"integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.43.0",
"@typescript-eslint/types": "8.43.0",
@@ -2261,6 +2272,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2829,7 +2841,8 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/daisyui": {
"version": "5.1.12",
@@ -3263,6 +3276,7 @@
"integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3437,6 +3451,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -4229,6 +4244,12 @@
"node": ">=0.8.19"
}
},
+ "node_modules/inputmask": {
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/inputmask/-/inputmask-5.0.9.tgz",
+ "integrity": "sha512-s0lUfqcEbel+EQXtehXqwCJGShutgieOaIImFKC/r4reYNvX3foyrChl6LOEvaEgxEbesePIrw1Zi2jhZaDZbQ==",
+ "license": "MIT"
+ },
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@@ -5766,6 +5787,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5775,6 +5797,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@@ -6591,6 +6614,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -6758,6 +6782,7 @@
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/package.json b/package.json
index e970499c..70e5737f 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,7 @@
"axios": "^1.12.2",
"clsx": "^2.1.1",
"formik": "^2.4.6",
+ "inputmask": "^5.0.9",
"moment": "^2.30.1",
"next": "15.5.3",
"react": "19.1.0",
@@ -31,6 +32,7 @@
"@eslint/eslintrc": "^3",
"@iconify/react": "^6.0.2",
"@tailwindcss/postcss": "^4",
+ "@types/inputmask": "^5.0.7",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
From ae967c5ddb863582c6ce7db8aa5b271c4df2050e Mon Sep 17 00:00:00 2001
From: rstubryan
Date: Thu, 23 Oct 2025 16:00:24 +0700
Subject: [PATCH 07/10] refactor(FE-114): integrate inputmask for enhanced
numeric input handling and validation
---
src/components/input/NumberInput.tsx | 617 ++++++++++++---------------
1 file changed, 281 insertions(+), 336 deletions(-)
diff --git a/src/components/input/NumberInput.tsx b/src/components/input/NumberInput.tsx
index 2afe7e55..96c7f446 100644
--- a/src/components/input/NumberInput.tsx
+++ b/src/components/input/NumberInput.tsx
@@ -6,101 +6,47 @@ import {
FocusEventHandler,
ReactNode,
useEffect,
+ useRef,
useState,
} from 'react';
-import { Icon } from '@iconify/react';
import { cn } from '@/lib/helper';
+import Inputmask from 'inputmask';
-// Utility Functions
-const formatNumber = (
- value: number | string,
- decimals: number = 0,
- thousandSeparator: string = '.',
- decimalSeparator: string = ','
-): string => {
- if (value === '' || value === null || value === undefined) return '';
+const createInputMask = (
+ maskType: MaskType,
+ decimals: number,
+ thousandSeparator: string,
+ decimalSeparator: string,
+ allowNegative: boolean,
+ oncomplete?: () => void,
+ onincomplete?: () => void,
+ oncleared?: () => void
+): Inputmask.Instance => {
+ const options: Inputmask.Options = {
+ alias: 'numeric',
+ groupSeparator: thousandSeparator,
+ radixPoint: decimalSeparator,
+ digits: decimals,
+ allowMinus: allowNegative,
+ rightAlign: false,
+ insertMode: true,
+ autoUnmask: false,
+ clearMaskOnLostFocus: false,
+ digitsOptional: decimals > 0,
+ placeholder: '0',
+ numericInput: false,
+ positionCaretOnClick: 'radixFocus',
+ greedy: true,
+ oncomplete,
+ onincomplete,
+ oncleared
+ };
- const numValue = typeof value === 'string' ? parseFloat(value) : value;
- if (isNaN(numValue)) return '';
-
- const parts = numValue.toFixed(decimals).split('.');
- const integerPart = parts[0].replace(
- /\B(?=(\d{3})+(?!\d))/g,
- thousandSeparator
- );
- const decimalPart = parts[1];
-
- return decimals > 0 && decimalPart
- ? `${integerPart}${decimalSeparator}${decimalPart}`
- : integerPart;
+ return new Inputmask(options);
};
-const parseNumber = (
- value: string,
- thousandSeparator: string = '.',
- decimalSeparator: string = ','
-): number => {
- if (!value) return 0;
-
- // Remove thousand separators and replace decimal separator with dot
- const cleaned = value
- .replace(new RegExp(`\\${thousandSeparator}`, 'g'), '')
- .replace(decimalSeparator, '.');
-
- const parsed = parseFloat(cleaned);
- return isNaN(parsed) ? 0 : parsed;
-};
-
-const formatCurrency = (
- value: number | string,
- prefix: string = 'Rp ',
- decimals: number = 0
-): string => {
- if (value === '' || value === null || value === undefined) return '';
- const formatted = formatNumber(value, decimals);
- return formatted ? `${prefix}${formatted}` : '';
-};
-
-const formatWeight = (
- value: number | string,
- unit: string = 'kg',
- decimals: number = 2
-): string => {
- if (value === '' || value === null || value === undefined) return '';
- const formatted = formatNumber(value, decimals);
- return formatted ? `${formatted} ${unit}` : '';
-};
-
-const cleanNumericInput = (
- value: string,
- allowDecimal: boolean = false,
- decimalSeparator: string = ','
-): string => {
- // Only allow numbers, decimal separator (if allowed), and minus sign at the start
- let cleaned = value.replace(/[^\d,.-]/g, '');
-
- // Handle decimal separator
- if (allowDecimal) {
- const parts = cleaned.split(decimalSeparator);
- if (parts.length > 2) {
- // Keep only first decimal separator
- cleaned = parts[0] + decimalSeparator + parts.slice(1).join('');
- }
- } else {
- cleaned = cleaned.replace(new RegExp(decimalSeparator, 'g'), '');
- }
-
- // Handle minus sign (only at start)
- const hasMinusAtStart = cleaned.startsWith('-');
- cleaned = cleaned.replace(/-/g, '');
- if (hasMinusAtStart) cleaned = '-' + cleaned;
-
- return cleaned;
-};
-
-// Types
-export type MaskType = 'currency' | 'weight' | 'decimal' | 'number';
+export type MaskType = 'currency' | 'weight' | 'decimal' | 'number' | 'text';
export interface NumberInputProps {
label?: string;
@@ -108,252 +54,188 @@ export interface NumberInputProps {
name: string;
value?: number | string;
placeholder?: string;
+
className?: {
wrapper?: string;
label?: string;
inputWrapper?: string;
input?: string;
};
+
isError?: boolean;
isValid?: boolean;
+ errorMessage?: string;
disabled?: boolean;
readOnly?: boolean;
required?: boolean;
isLoading?: boolean;
- errorMessage?: string;
+
startAdornment?: ReactNode;
endAdornment?: ReactNode;
+
onChange?: ChangeEventHandler;
onBlur?: FocusEventHandler;
onFocus?: FocusEventHandler;
- // Masking Options
maskType?: MaskType;
decimals?: number;
thousandSeparator?: string;
decimalSeparator?: string;
-
- // Currency specific
currencyPrefix?: string;
-
- // Weight specific
weightUnit?: string;
- // Validation
min?: number;
max?: number;
allowNegative?: boolean;
- // Stepper (Increment/Decrement buttons)
- showSteppers?: boolean;
- step?: number;
+ oncomplete?: () => void;
+ onincomplete?: () => void;
+ oncleared?: () => void;
}
const NumberInput = ({
- label,
- bottomLabel,
- name,
- value,
- placeholder,
- className,
- isError,
- isValid,
- errorMessage,
- startAdornment,
- endAdornment,
- disabled = false,
- required = false,
- onChange,
- onBlur,
- onFocus,
- readOnly = false,
- isLoading = false,
- maskType = 'number',
- decimals = 0,
- thousandSeparator = '.',
- decimalSeparator = ',',
- currencyPrefix = 'Rp ',
- weightUnit = 'kg',
- min,
- max,
- allowNegative = false,
- showSteppers = false,
- step = 1,
-}: NumberInputProps) => {
- const [displayValue, setDisplayValue] = useState('');
-
- // Determine if decimals are allowed based on maskType
- const allowDecimal =
- maskType === 'decimal' || maskType === 'weight' || decimals > 0;
-
- // Format value for display based on maskType
- const getFormattedValue = (rawValue: number | string): string => {
- if (rawValue === '' || rawValue === null || rawValue === undefined)
- return '';
+ label,
+ bottomLabel,
+ name,
+ value,
+ placeholder,
+ className,
+ isError,
+ isValid,
+ errorMessage,
+ startAdornment,
+ endAdornment,
+ disabled = false,
+ required = false,
+ onChange,
+ onBlur,
+ onFocus,
+ readOnly = false,
+ isLoading = false,
+ maskType = 'number',
+ decimals = 0,
+ thousandSeparator = '.',
+ decimalSeparator = ',',
+ currencyPrefix = 'Rp ',
+ weightUnit = 'kg',
+ allowNegative = false,
+ oncomplete,
+ onincomplete,
+ oncleared,
+ }: NumberInputProps) => {
+ const inputRef = useRef(null);
+ const inputmaskRef = useRef(null);
+ const [maskComplete, setMaskComplete] = useState(false);
+ const [maskIncomplete, setMaskIncomplete] = useState(false);
+ const [maskCleared, setMaskCleared] = useState(false);
+ const getInputPrefix = (): string => {
switch (maskType) {
case 'currency':
- return formatCurrency(rawValue, currencyPrefix, decimals);
- case 'weight':
- return formatWeight(rawValue, weightUnit, decimals);
- case 'decimal':
- case 'number':
- return formatNumber(
- rawValue,
- decimals,
- thousandSeparator,
- decimalSeparator
- );
+ return currencyPrefix;
default:
- return String(rawValue);
+ return '';
+ }
+ };
+
+ const getInputSuffix = (): string => {
+ switch (maskType) {
+ case 'weight':
+ return weightUnit;
+ default:
+ return '';
}
};
- // Initialize display value when value prop changes
useEffect(() => {
- setDisplayValue(getFormattedValue(value || ''));
+ if (inputRef.current && !readOnly && !disabled) {
+ if (inputmaskRef.current) {
+ try {
+ inputmaskRef.current.remove();
+ } catch (error) {
+ console.warn('Error removing Inputmask:', error);
+ }
+ }
+
+ const handleComplete = () => {
+ setMaskComplete(true);
+ setMaskIncomplete(false);
+ setMaskCleared(false);
+ if (oncomplete) oncomplete();
+ };
+
+ const handleIncomplete = () => {
+ setMaskIncomplete(true);
+ setMaskComplete(false);
+ setMaskCleared(false);
+ if (onincomplete) onincomplete();
+ };
+
+ const handleCleared = () => {
+ setMaskCleared(true);
+ setMaskComplete(false);
+ setMaskIncomplete(false);
+ if (oncleared) oncleared();
+ };
+
+ const im = createInputMask(
+ maskType,
+ decimals,
+ thousandSeparator,
+ decimalSeparator,
+ allowNegative,
+ handleComplete,
+ handleIncomplete,
+ handleCleared
+ );
+
+ try {
+ im.mask(inputRef.current);
+ inputmaskRef.current = im;
+ } catch (error) {
+ console.warn('Error applying Inputmask:', error);
+ inputmaskRef.current = null;
+ }
+ }
+
+ return () => {
+ if (inputmaskRef.current) {
+ try {
+ inputmaskRef.current.remove();
+ } catch (error) {
+ console.warn('Error removing Inputmask on cleanup:', error);
+ }
+ }
+ };
+ }, [maskType, decimals, thousandSeparator, decimalSeparator, allowNegative, readOnly, disabled, oncomplete, onincomplete, oncleared]);
+
+ useEffect(() => {
+ if (inputRef.current && value !== undefined) {
+ if (value === null || value === '') {
+ inputRef.current.value = '';
+ } else {
+ inputRef.current.value = String(value);
+ }
+ }
}, [value]);
- const handleInputChange = (e: ChangeEvent) => {
- let inputValue = e.target.value;
+ const handleKeyUp = (e: React.KeyboardEvent) => {
+ const currentValue = (e.currentTarget as HTMLInputElement).value;
+ console.log('✅ After format:', currentValue);
- // Remove prefix/suffix for editing
- if (maskType === 'currency' && inputValue.startsWith(currencyPrefix)) {
- inputValue = inputValue.slice(currencyPrefix.length);
- }
- if (maskType === 'weight' && inputValue.endsWith(` ${weightUnit}`)) {
- inputValue = inputValue.slice(0, -weightUnit.length - 1);
- }
-
- // Clean input
- const cleaned = cleanNumericInput(
- inputValue,
- allowDecimal,
- decimalSeparator
- );
-
- // Parse to number
- let numericValue = parseNumber(
- cleaned,
- thousandSeparator,
- decimalSeparator
- );
-
- // Apply validation
- if (!allowNegative && numericValue < 0) {
- numericValue = 0;
- }
- if (min !== undefined && numericValue < min) {
- numericValue = min;
- }
- if (max !== undefined && numericValue > max) {
- numericValue = max;
- }
-
- // Update display value
- const formattedForDisplay = formatNumber(
- numericValue,
- decimals,
- thousandSeparator,
- decimalSeparator
- );
-
- setDisplayValue(formattedForDisplay);
-
- // Call onChange with modified event
- if (onChange) {
- // Create a synthetic event with the numeric value
- const syntheticEvent = {
- ...e,
- target: {
- ...e.target,
- name,
- value: numericValue.toString(),
- },
- } as ChangeEvent;
-
- onChange(syntheticEvent);
- }
- };
-
- // Handle Increment
- const handleIncrement = () => {
- if (disabled || readOnly) return;
-
- const currentValue = parseNumber(
- displayValue,
- thousandSeparator,
- decimalSeparator
- );
- let newValue = currentValue + step;
-
- // Apply max validation
- if (max !== undefined && newValue > max) {
- newValue = max;
- }
-
- // Update display
- const formattedForDisplay = formatNumber(
- newValue,
- decimals,
- thousandSeparator,
- decimalSeparator
- );
- setDisplayValue(formattedForDisplay);
-
- // Call onChange with synthetic event
if (onChange) {
const syntheticEvent = {
target: {
name,
- value: newValue.toString(),
+ value: currentValue,
},
} as ChangeEvent;
-
onChange(syntheticEvent);
}
};
- // Handle Decrement
- const handleDecrement = () => {
- if (disabled || readOnly) return;
-
- const currentValue = parseNumber(
- displayValue,
- thousandSeparator,
- decimalSeparator
- );
- let newValue = currentValue - step;
-
- // Apply min validation (prevent negative if not allowed)
- if (!allowNegative && newValue < 0) {
- newValue = 0;
- }
- if (min !== undefined && newValue < min) {
- newValue = min;
- }
-
- // Update display
- const formattedForDisplay = formatNumber(
- newValue,
- decimals,
- thousandSeparator,
- decimalSeparator
- );
- setDisplayValue(formattedForDisplay);
-
- // Call onChange with synthetic event
- if (onChange) {
- const syntheticEvent = {
- target: {
- name,
- value: newValue.toString(),
- },
- } as ChangeEvent;
-
- onChange(syntheticEvent);
- }
- };
+ const inputPrefix = getInputPrefix();
+ const inputSuffix = getInputSuffix();
return (
)}
-
- {/* Decrement Button */}
- {showSteppers && (
-
+ {(maskType === 'text' || (oncomplete || onincomplete || oncleared)) && (
+
+
+ Complete
+
+
+ Incomplete
+
+
+ Cleared
+
+
+ )}
+
{!isError && bottomLabel && (
{bottomLabel}
)}
@@ -468,3 +412,4 @@ const NumberInput = ({
};
export default NumberInput;
+
From 90ae7c469a9cfbc81b071a03a26c82f4153d6ba4 Mon Sep 17 00:00:00 2001
From: rstubryan
Date: Thu, 23 Oct 2025 16:48:55 +0700
Subject: [PATCH 08/10] refactor(FE-114): swap thousand and decimal separators
for improved usability
---
src/components/input/NumberInput.tsx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/components/input/NumberInput.tsx b/src/components/input/NumberInput.tsx
index 96c7f446..5b2188ee 100644
--- a/src/components/input/NumberInput.tsx
+++ b/src/components/input/NumberInput.tsx
@@ -114,8 +114,8 @@ const NumberInput = ({
isLoading = false,
maskType = 'number',
decimals = 0,
- thousandSeparator = '.',
- decimalSeparator = ',',
+ thousandSeparator = ',',
+ decimalSeparator = '.',
currencyPrefix = 'Rp ',
weightUnit = 'kg',
allowNegative = false,
@@ -178,11 +178,11 @@ const NumberInput = ({
if (oncleared) oncleared();
};
- const im = createInputMask(
+ const im = createInputMask(
maskType,
decimals,
- thousandSeparator,
- decimalSeparator,
+ ',',
+ '.',
allowNegative,
handleComplete,
handleIncomplete,
From 3cf8f4c89b35dd8b0ba52978e5eee5ea94bb7aba Mon Sep 17 00:00:00 2001
From: rstubryan
Date: Thu, 23 Oct 2025 16:49:32 +0700
Subject: [PATCH 09/10] refactor(FE-114): enhance numeric input handling for
chicken weight and count with improved formatting
---
.../production/recording/form/RecordingForm.tsx | 14 +++++++++-----
1 file changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx
index 2735a7e9..120cddb8 100644
--- a/src/components/pages/production/recording/form/RecordingForm.tsx
+++ b/src/components/pages/production/recording/form/RecordingForm.tsx
@@ -487,7 +487,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
// Create wrapper handlers that match NumberInput's onChange signature
const handleChickenWeightChangeWrapper = useCallback(
(idx: number) => (e: React.ChangeEvent) => {
- const value = parseFloat(e.target.value) || 0;
+ const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0;
handleChickenWeightChange(idx, value);
},
[handleChickenWeightChange]
@@ -495,7 +495,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const handleChickenCountChangeWrapper = useCallback(
(idx: number) => (e: React.ChangeEvent) => {
- const value = parseFloat(e.target.value) || 0;
+ const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0;
handleChickenCountChange(idx, value);
},
[handleChickenCountChange]
@@ -503,7 +503,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const handleAverageWeightChangeWrapper = useCallback(
(idx: number) => (e: React.ChangeEvent) => {
- const value = parseFloat(e.target.value) || 0;
+ const value = parseFloat(e.target.value.replace(/[^\d,.-]/g, '').replace(/,/g, '')) || 0;
handleAverageWeightChange(idx, value);
},
[handleAverageWeightChange]
@@ -1100,8 +1100,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
onBlur={formik.handleBlur}
maskType='weight'
weightUnit='gram'
- decimals={0}
+ decimals={2}
min={0}
+ thousandSeparator=','
+ decimalSeparator='.'
isError={
isRepeaterInputError(
'body_weight',
@@ -1162,8 +1164,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
onBlur={formik.handleBlur}
maskType='weight'
weightUnit='gram'
- decimals={0}
+ decimals={2}
min={0}
+ thousandSeparator=','
+ decimalSeparator='.'
isError={
isRepeaterInputError(
'body_weight',
From 3f76cb58fe5edbf3edc62de8a8a9a911463e7e6a Mon Sep 17 00:00:00 2001
From: rstubryan
Date: Thu, 23 Oct 2025 17:15:17 +0700
Subject: [PATCH 10/10] refactor(FE-114): improve alignment and styling of
checkbox inputs in RecordingForm
---
.../recording/form/RecordingForm.tsx | 48 ++++++++-----------
1 file changed, 20 insertions(+), 28 deletions(-)
diff --git a/src/components/pages/production/recording/form/RecordingForm.tsx b/src/components/pages/production/recording/form/RecordingForm.tsx
index 120cddb8..45f0cbf5 100644
--- a/src/components/pages/production/recording/form/RecordingForm.tsx
+++ b/src/components/pages/production/recording/form/RecordingForm.tsx
@@ -812,19 +812,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{formik.values.feed_data?.map((feed, idx) => (
{type !== 'detail' && (
-
-
+
+ e: React.ChangeEvent,
) => {
if (e.target.checked) {
setSelectedFeed([...selectedFeed, idx]);
} else {
setSelectedFeed(
- selectedFeed.filter((i) => i !== idx)
+ selectedFeed.filter((i) => i !== idx),
);
}
}}
@@ -833,11 +832,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
checkbox: 'checkbox checkbox-sm',
}}
/>
-
|
)}
- {
{type !== 'detail' && (
-
+
{
}
}}
classNames={{
- wrapper: 'flex justify-center',
+ wrapper: 'flex justify-center items-center h-full',
checkbox: 'checkbox checkbox-sm',
}}
/>
@@ -1067,8 +1065,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{formik.values.body_weight?.map((weight, idx) => (
{type !== 'detail' && (
-
-
+
{
}
}}
classNames={{
- wrapper: 'flex justify-center',
+ wrapper: 'flex justify-center items-center',
checkbox: 'checkbox checkbox-sm',
}}
/>
-
|
)}
@@ -1257,7 +1253,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
|
{type !== 'detail' && (
-
+
{
}
}}
classNames={{
- wrapper: 'flex justify-center',
+ wrapper: 'flex justify-center items-center h-full',
checkbox: 'checkbox checkbox-sm',
}}
/>
@@ -1312,19 +1308,18 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{formik.values.vaccination?.map((vaccine, idx) => (
{type !== 'detail' && (
-
-
+
+ e: React.ChangeEvent,
) => {
if (e.target.checked) {
setSelectedVaccine([...selectedVaccine, idx]);
} else {
setSelectedVaccine(
- selectedVaccine.filter((i) => i !== idx)
+ selectedVaccine.filter((i) => i !== idx),
);
}
}}
@@ -1333,11 +1328,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
checkbox: 'checkbox checkbox-sm',
}}
/>
-
|
)}
- {
{type !== 'detail' && (
-
+
{
}
}}
classNames={{
- wrapper: 'flex justify-center',
+ wrapper: 'flex justify-center items-center h-full',
checkbox: 'checkbox checkbox-sm',
}}
/>
@@ -1574,13 +1568,12 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
{formik.values.mortality?.map((mortality, idx) => (
{type !== 'detail' && (
- |
-
+ |
+ e: React.ChangeEvent,
) => {
if (e.target.checked) {
setSelectedMortality([
@@ -1589,7 +1582,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
]);
} else {
setSelectedMortality(
- selectedMortality.filter((i) => i !== idx)
+ selectedMortality.filter((i) => i !== idx),
);
}
}}
@@ -1598,11 +1591,10 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
checkbox: 'checkbox checkbox-sm',
}}
/>
-
|
)}
- opt.value === mortality.condition
| | | | | | | | |