mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
fix(merge): resolve conflict on merge
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
npm run lint
|
||||||
|
npm run build
|
||||||
Generated
+43
-1
@@ -13,6 +13,7 @@
|
|||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
|
"inputmask": "^5.0.9",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"next": "15.5.3",
|
"next": "15.5.3",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
@@ -29,12 +30,14 @@
|
|||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@iconify/react": "^6.0.2",
|
"@iconify/react": "^6.0.2",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/inputmask": "^5.0.7",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"daisyui": "^5.1.12",
|
"daisyui": "^5.1.12",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.5.3",
|
"eslint-config-next": "15.5.3",
|
||||||
|
"husky": "^9.1.7",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
@@ -1639,6 +1642,13 @@
|
|||||||
"@types/react": "*"
|
"@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": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -1674,6 +1684,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz",
|
||||||
"integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
|
"integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
@@ -1743,6 +1754,7 @@
|
|||||||
"integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==",
|
"integrity": "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.43.0",
|
"@typescript-eslint/scope-manager": "8.43.0",
|
||||||
"@typescript-eslint/types": "8.43.0",
|
"@typescript-eslint/types": "8.43.0",
|
||||||
@@ -2260,6 +2272,7 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@@ -2828,7 +2841,8 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/daisyui": {
|
"node_modules/daisyui": {
|
||||||
"version": "5.1.12",
|
"version": "5.1.12",
|
||||||
@@ -3262,6 +3276,7 @@
|
|||||||
"integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==",
|
"integrity": "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@@ -3436,6 +3451,7 @@
|
|||||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rtsao/scc": "^1.1.0",
|
"@rtsao/scc": "^1.1.0",
|
||||||
"array-includes": "^3.1.9",
|
"array-includes": "^3.1.9",
|
||||||
@@ -4176,6 +4192,22 @@
|
|||||||
"react-is": "^16.7.0"
|
"react-is": "^16.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/husky": {
|
||||||
|
"version": "9.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
|
||||||
|
"integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"husky": "bin.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/typicode"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
@@ -4212,6 +4244,12 @@
|
|||||||
"node": ">=0.8.19"
|
"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": {
|
"node_modules/internal-slot": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
|
||||||
@@ -5749,6 +5787,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@@ -5758,6 +5797,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.26.0"
|
"scheduler": "^0.26.0"
|
||||||
},
|
},
|
||||||
@@ -6574,6 +6614,7 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -6741,6 +6782,7 @@
|
|||||||
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|||||||
+5
-1
@@ -6,7 +6,8 @@
|
|||||||
"dev": "eslint && next dev --turbopack",
|
"dev": "eslint && next dev --turbopack",
|
||||||
"build": "next build --turbopack",
|
"build": "next build --turbopack",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint"
|
"lint": "eslint",
|
||||||
|
"prepare": "husky"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/match-sorter-utils": "^8.19.4",
|
"@tanstack/match-sorter-utils": "^8.19.4",
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
"axios": "^1.12.2",
|
"axios": "^1.12.2",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"formik": "^2.4.6",
|
"formik": "^2.4.6",
|
||||||
|
"inputmask": "^5.0.9",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"next": "15.5.3",
|
"next": "15.5.3",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
@@ -30,12 +32,14 @@
|
|||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@iconify/react": "^6.0.2",
|
"@iconify/react": "^6.0.2",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/inputmask": "^5.0.7",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"daisyui": "^5.1.12",
|
"daisyui": "^5.1.12",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.5.3",
|
"eslint-config-next": "15.5.3",
|
||||||
|
"husky": "^9.1.7",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,278 +1,87 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ChangeEventHandler, FocusEventHandler, ReactNode, useId } from 'react';
|
import { HTMLProps, useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import FieldMessage from '@/components/helper/FieldMessage';
|
|
||||||
|
|
||||||
export interface CheckboxInputProps {
|
interface CheckboxInputProps extends HTMLProps<HTMLInputElement> {
|
||||||
// Basic Props
|
|
||||||
name: string;
|
name: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
bottomLabel?: string;
|
|
||||||
checked?: boolean;
|
|
||||||
value?: string | number;
|
|
||||||
indeterminate?: boolean;
|
indeterminate?: boolean;
|
||||||
naked?: boolean; // New prop for checkbox-only mode
|
classNames?: {
|
||||||
|
|
||||||
// Styling Props
|
|
||||||
className?: {
|
|
||||||
wrapper?: string;
|
wrapper?: string;
|
||||||
label?: string;
|
inputWrapper?: string;
|
||||||
checkbox?: string;
|
checkbox?: string;
|
||||||
input?: string;
|
label?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// State Props
|
|
||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
isValid?: boolean;
|
isValid?: boolean;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
disabled?: boolean;
|
|
||||||
readOnly?: boolean;
|
|
||||||
required?: boolean;
|
|
||||||
isLoading?: boolean;
|
|
||||||
|
|
||||||
// Adornment Props
|
|
||||||
startAdornment?: ReactNode;
|
|
||||||
endAdornment?: ReactNode;
|
|
||||||
|
|
||||||
// Event Handlers
|
|
||||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
|
||||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
|
||||||
onFocus?: FocusEventHandler<HTMLInputElement>;
|
|
||||||
|
|
||||||
// Additional Props
|
|
||||||
tooltip?: string;
|
|
||||||
description?: string;
|
|
||||||
size?: 'sm' | 'md' | 'lg';
|
|
||||||
variant?:
|
|
||||||
| 'default'
|
|
||||||
| 'primary'
|
|
||||||
| 'secondary'
|
|
||||||
| 'success'
|
|
||||||
| 'warning'
|
|
||||||
| 'info'
|
|
||||||
| 'error';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CheckboxInput = ({
|
const CheckboxInput = ({
|
||||||
|
indeterminate,
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
bottomLabel,
|
|
||||||
checked = false,
|
|
||||||
value,
|
|
||||||
indeterminate = false,
|
|
||||||
naked = false,
|
|
||||||
className,
|
className,
|
||||||
|
classNames,
|
||||||
|
isValid,
|
||||||
isError,
|
isError,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
disabled = false,
|
...rest
|
||||||
readOnly = false,
|
|
||||||
required = false,
|
|
||||||
isLoading = false,
|
|
||||||
startAdornment,
|
|
||||||
endAdornment,
|
|
||||||
onChange,
|
|
||||||
onBlur,
|
|
||||||
onFocus,
|
|
||||||
tooltip,
|
|
||||||
description,
|
|
||||||
size = 'md',
|
|
||||||
variant = 'default',
|
|
||||||
}: CheckboxInputProps) => {
|
}: CheckboxInputProps) => {
|
||||||
const showErrorMessage = Boolean(isError && errorMessage);
|
const ref = useRef<HTMLInputElement>(null!);
|
||||||
const feedbackMessage = showErrorMessage ? errorMessage : bottomLabel;
|
|
||||||
|
|
||||||
// Size classes
|
useEffect(() => {
|
||||||
const sizeClasses = {
|
if (typeof indeterminate === 'boolean') {
|
||||||
sm: 'checkbox-sm',
|
ref.current.indeterminate = !rest.checked && indeterminate;
|
||||||
md: 'checkbox-md',
|
|
||||||
lg: 'checkbox-lg',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Variant classes
|
|
||||||
const variantClasses = {
|
|
||||||
default: '',
|
|
||||||
primary: 'checkbox-primary',
|
|
||||||
secondary: 'checkbox-secondary',
|
|
||||||
success: 'checkbox-success',
|
|
||||||
warning: 'checkbox-warning',
|
|
||||||
info: 'checkbox-info',
|
|
||||||
error: 'checkbox-error',
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate unique ID for accessibility using React's useId hook for SSR compatibility
|
|
||||||
const generatedId = useId();
|
|
||||||
const checkboxId = `checkbox-${name}-${generatedId}`;
|
|
||||||
|
|
||||||
// Naked mode - only checkbox, no wrapper structure
|
|
||||||
if (naked) {
|
|
||||||
return (
|
|
||||||
<div className='relative'>
|
|
||||||
<input
|
|
||||||
type='checkbox'
|
|
||||||
id={checkboxId}
|
|
||||||
name={name}
|
|
||||||
value={value}
|
|
||||||
checked={checked}
|
|
||||||
onChange={onChange}
|
|
||||||
onBlur={onBlur}
|
|
||||||
onFocus={onFocus}
|
|
||||||
disabled={disabled}
|
|
||||||
readOnly={readOnly}
|
|
||||||
className={cn(
|
|
||||||
'checkbox',
|
|
||||||
sizeClasses[size],
|
|
||||||
variantClasses[variant],
|
|
||||||
{
|
|
||||||
'opacity-50 cursor-not-allowed': disabled,
|
|
||||||
'cursor-pointer': !disabled && !readOnly,
|
|
||||||
},
|
|
||||||
className?.checkbox
|
|
||||||
)}
|
|
||||||
ref={(input) => {
|
|
||||||
if (input) {
|
|
||||||
input.indeterminate = indeterminate;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Loading State */}
|
|
||||||
{isLoading && (
|
|
||||||
<div className='absolute inset-0 flex items-center justify-center'>
|
|
||||||
<span className='loading loading-spinner loading-xs opacity-50' />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}, [ref, indeterminate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div
|
||||||
|
className={cn('flex flex-col items-center gap-1', classNames?.wrapper)}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full flex flex-col gap-2 text-start',
|
'flex flex-row justify-center items-center gap-2',
|
||||||
className?.wrapper
|
classNames?.inputWrapper
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Label with Tooltip Support */}
|
|
||||||
{label && (
|
|
||||||
<label
|
|
||||||
htmlFor={checkboxId}
|
|
||||||
className={cn(
|
|
||||||
'w-full text-sm font-normal leading-5 flex items-start gap-2',
|
|
||||||
{
|
|
||||||
'text-error': isError,
|
|
||||||
'text-gray-500': disabled,
|
|
||||||
},
|
|
||||||
className?.label
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{/* Checkbox */}
|
|
||||||
<div className='flex items-center gap-2'>
|
|
||||||
{/* Start Adornment */}
|
|
||||||
{startAdornment && (
|
|
||||||
<span className={cn({ 'opacity-50': disabled })}>
|
|
||||||
{startAdornment}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Checkbox Input */}
|
|
||||||
<div className='relative'>
|
|
||||||
<input
|
<input
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
id={checkboxId}
|
ref={ref}
|
||||||
|
id={name}
|
||||||
name={name}
|
name={name}
|
||||||
value={value}
|
|
||||||
checked={checked}
|
|
||||||
onChange={onChange}
|
|
||||||
onBlur={onBlur}
|
|
||||||
onFocus={onFocus}
|
|
||||||
disabled={disabled}
|
|
||||||
readOnly={readOnly}
|
|
||||||
className={cn(
|
className={cn(
|
||||||
'checkbox',
|
'checkbox cursor-pointer',
|
||||||
sizeClasses[size],
|
|
||||||
variantClasses[variant],
|
|
||||||
{
|
{
|
||||||
'opacity-50 cursor-not-allowed': disabled,
|
'border-error': isError,
|
||||||
'cursor-pointer': !disabled && !readOnly,
|
'border-success': isValid,
|
||||||
},
|
},
|
||||||
className?.checkbox
|
className,
|
||||||
|
classNames?.checkbox
|
||||||
)}
|
)}
|
||||||
ref={(input) => {
|
{...rest}
|
||||||
if (input) {
|
|
||||||
input.indeterminate = indeterminate;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Loading State */}
|
{label && (
|
||||||
{isLoading && (
|
<label
|
||||||
<div className='absolute inset-0 flex items-center justify-center'>
|
htmlFor={name}
|
||||||
<span className='loading loading-spinner loading-xs opacity-50' />
|
className={cn(
|
||||||
</div>
|
'text-inherit',
|
||||||
|
{
|
||||||
|
'text-error': isError,
|
||||||
|
'text-success': isValid,
|
||||||
|
},
|
||||||
|
classNames?.label
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Label Text */}
|
|
||||||
<span
|
|
||||||
className={cn('flex-1', {
|
|
||||||
'line-through opacity-50': disabled,
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
{required && (
|
|
||||||
<>
|
|
||||||
{' '}
|
|
||||||
<span
|
|
||||||
className={cn('tooltip', {
|
|
||||||
'tooltip-error': isError,
|
|
||||||
'tooltip-base': !isError,
|
|
||||||
})}
|
|
||||||
data-tip={tooltip || 'required'}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={cn('text-xs font-bold', {
|
|
||||||
'text-error': isError,
|
|
||||||
'text-base-content/70': !isError,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
{/* End Adornment */}
|
|
||||||
{endAdornment && (
|
|
||||||
<span className={cn({ 'opacity-50': disabled })}>
|
|
||||||
{endAdornment}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Description */}
|
{errorMessage && <span className='text-error'>{errorMessage}</span>}
|
||||||
{description && (
|
|
||||||
<p
|
|
||||||
className={cn('text-xs leading-4', {
|
|
||||||
'text-gray-500': !isError,
|
|
||||||
'text-error': isError,
|
|
||||||
'opacity-50': disabled,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{description}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Field Message */}
|
|
||||||
<FieldMessage
|
|
||||||
message={feedbackMessage}
|
|
||||||
tone={showErrorMessage ? 'error' : 'info'}
|
|
||||||
isVisible={showErrorMessage || Boolean(bottomLabel)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { Ref } from 'react';
|
|||||||
|
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import { TextInputProps } from '@/components/input/TextInput';
|
import { TextInputProps } from '@/components/input/TextInput';
|
||||||
import FieldMessage from '@/components/helper/FieldMessage';
|
|
||||||
|
|
||||||
interface FileInputProps
|
interface FileInputProps
|
||||||
extends Omit<
|
extends Omit<
|
||||||
@@ -38,9 +37,6 @@ const FileInput = ({
|
|||||||
onBlur,
|
onBlur,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
}: FileInputProps) => {
|
}: FileInputProps) => {
|
||||||
const showErrorMessage = Boolean(isError && errorMessage);
|
|
||||||
const feedbackMessage = showErrorMessage ? errorMessage : bottomLabel;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -80,11 +76,11 @@ const FileInput = ({
|
|||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldMessage
|
{bottomLabel && (
|
||||||
message={feedbackMessage}
|
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
|
||||||
tone={showErrorMessage ? 'error' : 'info'}
|
)}
|
||||||
isVisible={showErrorMessage || Boolean(bottomLabel)}
|
|
||||||
/>
|
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,24 +6,55 @@ import {
|
|||||||
FocusEventHandler,
|
FocusEventHandler,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { Icon } from '@iconify/react';
|
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import FieldMessage from '@/components/helper/FieldMessage';
|
import Inputmask from 'inputmask';
|
||||||
|
|
||||||
export type MaskType = 'currency' | 'weight' | 'decimal' | 'number';
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Inputmask(options);
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MaskType = 'currency' | 'weight' | 'decimal' | 'number' | 'text';
|
||||||
|
|
||||||
export interface NumberInputProps {
|
export interface NumberInputProps {
|
||||||
// Basic Props
|
|
||||||
label?: string;
|
label?: string;
|
||||||
bottomLabel?: string;
|
bottomLabel?: string;
|
||||||
name: string;
|
name: string;
|
||||||
value?: number | string;
|
value?: number | string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|
||||||
// Styling Props
|
|
||||||
className?: {
|
className?: {
|
||||||
wrapper?: string;
|
wrapper?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
@@ -31,7 +62,6 @@ export interface NumberInputProps {
|
|||||||
input?: string;
|
input?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// State Props
|
|
||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
isValid?: boolean;
|
isValid?: boolean;
|
||||||
errorMessage?: string;
|
errorMessage?: string;
|
||||||
@@ -40,16 +70,13 @@ export interface NumberInputProps {
|
|||||||
required?: boolean;
|
required?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
|
|
||||||
// Adornment Props
|
|
||||||
startAdornment?: ReactNode;
|
startAdornment?: ReactNode;
|
||||||
endAdornment?: ReactNode;
|
endAdornment?: ReactNode;
|
||||||
|
|
||||||
// Event Handlers
|
|
||||||
onChange?: ChangeEventHandler<HTMLInputElement>;
|
onChange?: ChangeEventHandler<HTMLInputElement>;
|
||||||
onBlur?: FocusEventHandler<HTMLInputElement>;
|
onBlur?: FocusEventHandler<HTMLInputElement>;
|
||||||
onFocus?: FocusEventHandler<HTMLInputElement>;
|
onFocus?: FocusEventHandler<HTMLInputElement>;
|
||||||
|
|
||||||
// Masking Options
|
|
||||||
maskType?: MaskType;
|
maskType?: MaskType;
|
||||||
decimals?: number;
|
decimals?: number;
|
||||||
thousandSeparator?: string;
|
thousandSeparator?: string;
|
||||||
@@ -57,95 +84,15 @@ export interface NumberInputProps {
|
|||||||
currencyPrefix?: string;
|
currencyPrefix?: string;
|
||||||
weightUnit?: string;
|
weightUnit?: string;
|
||||||
|
|
||||||
// Validation Props
|
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
allowNegative?: boolean;
|
allowNegative?: boolean;
|
||||||
|
|
||||||
// Stepper Options
|
oncomplete?: () => void;
|
||||||
showSteppers?: boolean;
|
onincomplete?: () => void;
|
||||||
step?: number;
|
oncleared?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UTILITY FUNCTIONS
|
|
||||||
/**
|
|
||||||
* Core number formatting function
|
|
||||||
* Formats number with thousand separator and decimal separator
|
|
||||||
*/
|
|
||||||
const formatNumber = (
|
|
||||||
value: number | string,
|
|
||||||
decimals: number = 0,
|
|
||||||
thousandSeparator: string = '.',
|
|
||||||
decimalSeparator: string = ','
|
|
||||||
): string => {
|
|
||||||
if (value === '' || value === null || value === undefined) return '';
|
|
||||||
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse formatted string to number
|
|
||||||
* Converts formatted input back to raw number for processing
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clean and validate numeric input while typing
|
|
||||||
* Ensures only valid characters are allowed
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
const NumberInput = ({
|
const NumberInput = ({
|
||||||
label,
|
label,
|
||||||
bottomLabel,
|
bottomLabel,
|
||||||
@@ -167,21 +114,20 @@ const NumberInput = ({
|
|||||||
isLoading = false,
|
isLoading = false,
|
||||||
maskType = 'number',
|
maskType = 'number',
|
||||||
decimals = 0,
|
decimals = 0,
|
||||||
thousandSeparator = '.',
|
thousandSeparator = ',',
|
||||||
decimalSeparator = ',',
|
decimalSeparator = '.',
|
||||||
currencyPrefix = 'Rp ',
|
currencyPrefix = 'Rp ',
|
||||||
weightUnit = 'kg',
|
weightUnit = 'kg',
|
||||||
min,
|
|
||||||
max,
|
|
||||||
allowNegative = false,
|
allowNegative = false,
|
||||||
showSteppers = false,
|
oncomplete,
|
||||||
step = 1,
|
onincomplete,
|
||||||
}: NumberInputProps) => {
|
oncleared,
|
||||||
const [displayValue, setDisplayValue] = useState<string>('');
|
}: NumberInputProps) => {
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
// CONFIG & HELPERS
|
const inputmaskRef = useRef<Inputmask.Instance | null>(null);
|
||||||
const allowDecimal =
|
const [maskComplete, setMaskComplete] = useState<boolean>(false);
|
||||||
maskType === 'decimal' || maskType === 'weight' || decimals > 0;
|
const [maskIncomplete, setMaskIncomplete] = useState<boolean>(false);
|
||||||
|
const [maskCleared, setMaskCleared] = useState<boolean>(false);
|
||||||
|
|
||||||
const getInputPrefix = (): string => {
|
const getInputPrefix = (): string => {
|
||||||
switch (maskType) {
|
switch (maskType) {
|
||||||
@@ -201,165 +147,93 @@ const NumberInput = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFormattedValue = (rawValue: number | string): string => {
|
useEffect(() => {
|
||||||
if (rawValue === '' || rawValue === null || rawValue === undefined)
|
if (inputRef.current && !readOnly && !disabled) {
|
||||||
return '';
|
if (inputmaskRef.current) {
|
||||||
|
try {
|
||||||
switch (maskType) {
|
inputmaskRef.current.remove();
|
||||||
case 'currency':
|
} catch (error) {
|
||||||
case 'weight':
|
console.warn('Error removing Inputmask:', error);
|
||||||
case 'decimal':
|
|
||||||
case 'number':
|
|
||||||
return formatNumber(
|
|
||||||
rawValue,
|
|
||||||
decimals,
|
|
||||||
thousandSeparator,
|
|
||||||
decimalSeparator
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return String(rawValue);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleComplete = () => {
|
||||||
|
setMaskComplete(true);
|
||||||
|
setMaskIncomplete(false);
|
||||||
|
setMaskCleared(false);
|
||||||
|
if (oncomplete) oncomplete();
|
||||||
};
|
};
|
||||||
|
|
||||||
// EFFECTS
|
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,
|
||||||
|
',',
|
||||||
|
'.',
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
setDisplayValue(getFormattedValue(value || ''));
|
if (inputRef.current && value !== undefined) {
|
||||||
|
if (value === null || value === '') {
|
||||||
|
inputRef.current.value = '';
|
||||||
|
} else {
|
||||||
|
inputRef.current.value = String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}, [value]);
|
}, [value]);
|
||||||
|
|
||||||
// EVENT HANDLERS
|
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const currentValue = (e.currentTarget as HTMLInputElement).value;
|
||||||
const inputValue = e.target.value;
|
console.log('✅ After format:', currentValue);
|
||||||
|
|
||||||
// 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) {
|
|
||||||
const syntheticEvent = {
|
|
||||||
...e,
|
|
||||||
target: {
|
|
||||||
...e.target,
|
|
||||||
name,
|
|
||||||
value: numericValue.toString(),
|
|
||||||
},
|
|
||||||
} as ChangeEvent<HTMLInputElement>;
|
|
||||||
|
|
||||||
onChange(syntheticEvent);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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) {
|
if (onChange) {
|
||||||
const syntheticEvent = {
|
const syntheticEvent = {
|
||||||
target: {
|
target: {
|
||||||
name,
|
name,
|
||||||
value: newValue.toString(),
|
value: currentValue,
|
||||||
},
|
},
|
||||||
} as ChangeEvent<HTMLInputElement>;
|
} as ChangeEvent<HTMLInputElement>;
|
||||||
|
|
||||||
onChange(syntheticEvent);
|
onChange(syntheticEvent);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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<HTMLInputElement>;
|
|
||||||
|
|
||||||
onChange(syntheticEvent);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// RENDER CALCULATIONS
|
|
||||||
const showErrorMessage = Boolean(isError && errorMessage);
|
|
||||||
const feedbackMessage = showErrorMessage ? errorMessage : bottomLabel;
|
|
||||||
const inputPrefix = getInputPrefix();
|
const inputPrefix = getInputPrefix();
|
||||||
const inputSuffix = getInputSuffix();
|
const inputSuffix = getInputSuffix();
|
||||||
|
|
||||||
@@ -393,9 +267,7 @@ const NumberInput = ({
|
|||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Input Container */}
|
|
||||||
<div className='relative flex'>
|
<div className='relative flex'>
|
||||||
{/* Prefix Block */}
|
|
||||||
{inputPrefix && (
|
{inputPrefix && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -420,7 +292,6 @@ const NumberInput = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Input Wrapper */}
|
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'input h-12 text-base font-normal leading-6 flex-1 rounded-lg! outline-none! transition-all duration-200 flex items-center bg-white',
|
'input h-12 text-base font-normal leading-6 flex-1 rounded-lg! outline-none! transition-all duration-200 flex items-center bg-white',
|
||||||
@@ -436,35 +307,15 @@ const NumberInput = ({
|
|||||||
className?.inputWrapper
|
className?.inputWrapper
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* Stepper Buttons */}
|
|
||||||
{showSteppers && (
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
onClick={handleDecrement}
|
|
||||||
disabled={disabled || readOnly}
|
|
||||||
className={cn(
|
|
||||||
'btn btn-ghost btn-sm h-8 w-8 min-h-0 p-0 rounded-md',
|
|
||||||
{
|
|
||||||
'opacity-50 cursor-not-allowed': disabled || readOnly,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
<Icon icon='ic:round-minus' width={20} height={20} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Start Adornment */}
|
|
||||||
{startAdornment && startAdornment}
|
{startAdornment && startAdornment}
|
||||||
|
|
||||||
{/* Main Input */}
|
|
||||||
<input
|
<input
|
||||||
type='text'
|
type='text'
|
||||||
id={name}
|
id={name}
|
||||||
name={name}
|
name={name}
|
||||||
placeholder={placeholder}
|
ref={inputRef}
|
||||||
value={displayValue}
|
placeholder={placeholder || '0'}
|
||||||
onChange={handleInputChange}
|
onKeyUp={handleKeyUp}
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@@ -477,37 +328,19 @@ const NumberInput = ({
|
|||||||
className?.input
|
className?.input
|
||||||
)}
|
)}
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
inputMode='decimal'
|
inputMode='text'
|
||||||
|
autoComplete='off'
|
||||||
|
spellCheck={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* End Adornment & Loading */}
|
|
||||||
{(isLoading || endAdornment) && (
|
{(isLoading || endAdornment) && (
|
||||||
<div className='flex flex-row gap-2'>
|
<div className='flex flex-row gap-2'>
|
||||||
{isLoading && <span className='loading loading-spinner' />}
|
{isLoading && <span className='loading loading-spinner' />}
|
||||||
{endAdornment && endAdornment}
|
{endAdornment && endAdornment}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Increment Button */}
|
|
||||||
{showSteppers && (
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
onClick={handleIncrement}
|
|
||||||
disabled={disabled || readOnly}
|
|
||||||
className={cn(
|
|
||||||
'btn btn-ghost btn-sm h-8 w-8 min-h-0 p-0 rounded-md',
|
|
||||||
{
|
|
||||||
'opacity-50 cursor-not-allowed': disabled || readOnly,
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
<Icon icon='ic:round-plus' width={20} height={20} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Suffix Block */}
|
|
||||||
{inputSuffix && (
|
{inputSuffix && (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -533,14 +366,50 @@ const NumberInput = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Field Message */}
|
{(maskType === 'text' || (oncomplete || onincomplete || oncleared)) && (
|
||||||
<FieldMessage
|
<div className='flex gap-2 text-xs'>
|
||||||
message={feedbackMessage}
|
<span
|
||||||
tone={showErrorMessage ? 'error' : 'info'}
|
className={cn(
|
||||||
isVisible={showErrorMessage || Boolean(bottomLabel)}
|
'px-2 py-1 rounded transition-all duration-200',
|
||||||
/>
|
maskComplete
|
||||||
|
? 'bg-green-100 text-green-700 border border-green-200'
|
||||||
|
: 'bg-gray-50 text-gray-400 border border-gray-200'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Complete
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'px-2 py-1 rounded transition-all duration-200',
|
||||||
|
maskIncomplete
|
||||||
|
? 'bg-yellow-100 text-yellow-700 border border-yellow-200'
|
||||||
|
: 'bg-gray-50 text-gray-400 border border-gray-200'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Incomplete
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'px-2 py-1 rounded transition-all duration-200',
|
||||||
|
maskCleared
|
||||||
|
? 'bg-blue-100 text-blue-700 border border-blue-200'
|
||||||
|
: 'bg-gray-50 text-gray-400 border border-gray-200'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Cleared
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isError && bottomLabel && (
|
||||||
|
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
|
||||||
|
)}
|
||||||
|
{isError && errorMessage && (
|
||||||
|
<p className='w-full text-sm text-error'>{errorMessage}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NumberInput;
|
export default NumberInput;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { ChangeEventHandler, ReactNode } from 'react';
|
import { ChangeEventHandler, ReactNode } from 'react';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import FieldMessage from '@/components/helper/FieldMessage';
|
|
||||||
|
|
||||||
export interface RadioOption {
|
export interface RadioOption {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -48,8 +47,6 @@ const RadioInput = ({
|
|||||||
onChange,
|
onChange,
|
||||||
onBlur,
|
onBlur,
|
||||||
}: RadioInputProps) => {
|
}: RadioInputProps) => {
|
||||||
const showErrorMessage = Boolean(isError && errorMessage);
|
|
||||||
const feedbackMessage = showErrorMessage ? errorMessage : bottomLabel;
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('w-full flex flex-col gap-2', className?.wrapper)}>
|
<div className={cn('w-full flex flex-col gap-2', className?.wrapper)}>
|
||||||
{/* Label atas */}
|
{/* Label atas */}
|
||||||
@@ -100,11 +97,15 @@ const RadioInput = ({
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FieldMessage
|
{/* Label bawah */}
|
||||||
message={feedbackMessage}
|
{!isError && bottomLabel && (
|
||||||
tone={showErrorMessage ? 'error' : 'info'}
|
<p className='text-sm opacity-60'>{bottomLabel}</p>
|
||||||
isVisible={showErrorMessage || Boolean(bottomLabel)}
|
)}
|
||||||
/>
|
|
||||||
|
{/* Pesan error */}
|
||||||
|
{isError && errorMessage && (
|
||||||
|
<p className='text-sm text-error'>{errorMessage}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import CreatableSelect from 'react-select/creatable';
|
|||||||
import makeAnimated from 'react-select/animated';
|
import makeAnimated from 'react-select/animated';
|
||||||
import { useDebounce } from 'use-debounce';
|
import { useDebounce } from 'use-debounce';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import FieldMessage from '@/components/helper/FieldMessage';
|
|
||||||
|
|
||||||
export interface OptionType {
|
export interface OptionType {
|
||||||
value: string | number;
|
value: string | number;
|
||||||
@@ -118,9 +117,6 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showErrorMessage = Boolean(isError && errorMessage);
|
|
||||||
const feedbackMessage = showErrorMessage ? errorMessage : bottomLabel;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -218,11 +214,10 @@ const SelectInput = <T extends OptionType>(props: SelectInputProps<T>) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FieldMessage
|
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
|
||||||
message={feedbackMessage}
|
{!isError && bottomLabel && (
|
||||||
tone={showErrorMessage ? 'error' : 'info'}
|
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
|
||||||
isVisible={showErrorMessage || Boolean(bottomLabel)}
|
)}
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import React, { useState, KeyboardEvent, ChangeEvent, useEffect } from 'react';
|
import React, { useState, KeyboardEvent, ChangeEvent, useEffect } from 'react';
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import FieldMessage from '@/components/helper/FieldMessage';
|
|
||||||
|
|
||||||
export interface TagInputProps {
|
export interface TagInputProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
@@ -74,9 +73,6 @@ const TagInput: React.FC<TagInputProps> = ({
|
|||||||
setInputValue(e.target.value);
|
setInputValue(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showErrorMessage = Boolean(isError && errorMessage);
|
|
||||||
const feedbackMessage = showErrorMessage ? errorMessage : bottomLabel;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -161,11 +157,11 @@ const TagInput: React.FC<TagInputProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FieldMessage
|
{/* Bottom label or error message */}
|
||||||
message={feedbackMessage}
|
{!isError && bottomLabel && (
|
||||||
tone={showErrorMessage ? 'error' : 'info'}
|
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
|
||||||
isVisible={showErrorMessage || Boolean(bottomLabel)}
|
)}
|
||||||
/>
|
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import { ChangeEventHandler, FocusEventHandler, ReactNode } from 'react';
|
import { ChangeEventHandler, FocusEventHandler, ReactNode } from 'react';
|
||||||
|
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import FieldMessage from '@/components/helper/FieldMessage';
|
|
||||||
|
|
||||||
export interface TextAreaProps {
|
export interface TextAreaProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
@@ -51,9 +50,6 @@ const TextArea = ({
|
|||||||
isLoading = false,
|
isLoading = false,
|
||||||
rows = 3,
|
rows = 3,
|
||||||
}: TextAreaProps) => {
|
}: TextAreaProps) => {
|
||||||
const showErrorMessage = Boolean(isError && errorMessage);
|
|
||||||
const feedbackMessage = showErrorMessage ? errorMessage : bottomLabel;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -113,11 +109,10 @@ const TextArea = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FieldMessage
|
{!isError && bottomLabel && (
|
||||||
message={feedbackMessage}
|
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
|
||||||
tone={showErrorMessage ? 'error' : 'info'}
|
)}
|
||||||
isVisible={showErrorMessage || Boolean(bottomLabel)}
|
{isError && <p className='w-full text-sm text-error'>{errorMessage}</p>}
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { cn } from '@/lib/helper';
|
import { cn } from '@/lib/helper';
|
||||||
import FieldMessage from '@/components/helper/FieldMessage';
|
|
||||||
|
|
||||||
export interface TextInputProps {
|
export interface TextInputProps {
|
||||||
type?: HTMLInputTypeAttribute;
|
type?: HTMLInputTypeAttribute;
|
||||||
@@ -56,9 +55,6 @@ const TextInput = ({
|
|||||||
readOnly = false,
|
readOnly = false,
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
}: TextInputProps) => {
|
}: TextInputProps) => {
|
||||||
const showErrorMessage = Boolean(isError && errorMessage);
|
|
||||||
const feedbackMessage = showErrorMessage ? errorMessage : bottomLabel;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -123,11 +119,12 @@ const TextInput = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FieldMessage
|
{!isError && bottomLabel && (
|
||||||
message={feedbackMessage}
|
<p className='w-full text-sm opacity-60'>{bottomLabel}</p>
|
||||||
tone={showErrorMessage ? 'error' : 'info'}
|
)}
|
||||||
isVisible={showErrorMessage || Boolean(bottomLabel)}
|
{isError && errorMessage && (
|
||||||
/>
|
<p className='w-full text-sm text-error'>{errorMessage}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import { SupplierApi, WarehouseApi } from '@/services/api/master-data';
|
|||||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import FileInput from '@/components/input/FileInput';
|
import FileInput from '@/components/input/FileInput';
|
||||||
import FieldMessage from '@/components/helper/FieldMessage';
|
|
||||||
import CheckboxInput from '@/components/input/CheckboxInput';
|
import CheckboxInput from '@/components/input/CheckboxInput';
|
||||||
|
|
||||||
interface MovementFormProps {
|
interface MovementFormProps {
|
||||||
@@ -848,7 +847,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
selectedProducts.length &&
|
selectedProducts.length &&
|
||||||
formik.values.products?.length > 0
|
formik.values.products?.length > 0
|
||||||
}
|
}
|
||||||
onChange={(e) => {
|
onChange={(
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setSelectedProducts(
|
setSelectedProducts(
|
||||||
formik.values.products?.map(
|
formik.values.products?.map(
|
||||||
@@ -859,8 +860,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
setSelectedProducts([]);
|
setSelectedProducts([]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
naked={true}
|
classNames={{
|
||||||
size='sm'
|
wrapper: 'flex justify-center',
|
||||||
|
checkbox: 'checkbox checkbox-sm',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
@@ -891,11 +894,13 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<tr key={`product-row-${idx}-${product.product_id}`}>
|
<tr key={`product-row-${idx}-${product.product_id}`}>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td>
|
||||||
<div className='flex flex-col items-center gap-2'>
|
<div className='flex justify-center'>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name={`product-${idx}`}
|
name={`product-${idx}`}
|
||||||
checked={selectedProducts.includes(idx)}
|
checked={selectedProducts.includes(idx)}
|
||||||
onChange={(e) => {
|
onChange={(
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setSelectedProducts([
|
setSelectedProducts([
|
||||||
...selectedProducts,
|
...selectedProducts,
|
||||||
@@ -907,10 +912,11 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
naked={true}
|
classNames={{
|
||||||
size='sm'
|
wrapper: 'flex justify-center',
|
||||||
|
checkbox: 'checkbox checkbox-sm',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<FieldMessage message={null} isVisible={false} />
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
@@ -1006,7 +1012,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
height={24}
|
height={24}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
<FieldMessage message={null} isVisible={false} />
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
@@ -1064,7 +1069,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
selectedDeliveries.length &&
|
selectedDeliveries.length &&
|
||||||
formik.values.deliveries?.length > 0
|
formik.values.deliveries?.length > 0
|
||||||
}
|
}
|
||||||
onChange={(e) => {
|
onChange={(
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setSelectedDeliveries(
|
setSelectedDeliveries(
|
||||||
formik.values.deliveries?.map(
|
formik.values.deliveries?.map(
|
||||||
@@ -1075,8 +1082,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
setSelectedDeliveries([]);
|
setSelectedDeliveries([]);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
naked={true}
|
classNames={{
|
||||||
size='sm'
|
wrapper: 'flex justify-center',
|
||||||
|
checkbox: 'checkbox checkbox-sm',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
@@ -1153,11 +1162,13 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
<tr key={`delivery-row-${idx}`}>
|
<tr key={`delivery-row-${idx}`}>
|
||||||
{type !== 'detail' && (
|
{type !== 'detail' && (
|
||||||
<td>
|
<td>
|
||||||
<div className='flex flex-col items-start gap-2'>
|
<div className='flex justify-center'>
|
||||||
<CheckboxInput
|
<CheckboxInput
|
||||||
name={`delivery-${idx}`}
|
name={`delivery-${idx}`}
|
||||||
checked={selectedDeliveries.includes(idx)}
|
checked={selectedDeliveries.includes(idx)}
|
||||||
onChange={(e) => {
|
onChange={(
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
setSelectedDeliveries([
|
setSelectedDeliveries([
|
||||||
...selectedDeliveries,
|
...selectedDeliveries,
|
||||||
@@ -1171,10 +1182,11 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
naked={true}
|
classNames={{
|
||||||
size='sm'
|
wrapper: 'flex justify-center',
|
||||||
|
checkbox: 'checkbox checkbox-sm',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<FieldMessage message={null} isVisible={false} />
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
@@ -1323,10 +1335,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
'-'
|
'-'
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<FieldMessage
|
|
||||||
message={null}
|
|
||||||
isVisible={false}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
@@ -1444,7 +1452,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
|
|||||||
height={24}
|
height={24}
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
<FieldMessage message={null} isVisible={false} />
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -8,4 +8,8 @@
|
|||||||
--step-bg: var(--color-error);
|
--step-bg: var(--color-error);
|
||||||
--step-fg: var(--color-error-content);
|
--step-fg: var(--color-error-content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table :where(th, td) {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user