diff --git a/frontend/client-portal/src/components/hook-form/v2/FormProvider.tsx b/frontend/client-portal/src/components/hook-form/v2/FormProvider.tsx new file mode 100644 index 00000000..c57e3cd8 --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/FormProvider.tsx @@ -0,0 +1,26 @@ +import { ReactNode } from 'react'; +// form +import { FormProvider as Form, UseFormReturn } from 'react-hook-form'; + +// ---------------------------------------------------------------------- + +type Props = { + children: ReactNode; + methods: UseFormReturn; + onSubmit?: VoidFunction; + preventEnterSubmit?: boolean; +}; + +export default function FormProvider({ children, onSubmit, methods, preventEnterSubmit }: Props) { + const checkKeyDown = (e: any) => { + if (e.key === 'Enter' && preventEnterSubmit){ + e.preventDefault(); + } + }; + + return ( +
+ checkKeyDown(e)}>{children}
+ + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFAutocomplete.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFAutocomplete.tsx new file mode 100644 index 00000000..b52f932a --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFAutocomplete.tsx @@ -0,0 +1,102 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { Autocomplete, AutocompleteProps, FormHelperText, TextField, UseAutocompleteProps } from '@mui/material'; +import { useState } from 'react'; +import { FilterOptionsState } from '@mui/material/useAutocomplete'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string, + label: string, + options: any, + getOptionLabel: (option: any) => string, + isOptionEqualToValue: any, + disableClearable: boolean, + freeSolo: boolean, + renderOption?: any, + onInputChange?: any, + onKeyDown?: any, + onKeyPress?: any, + noOptionsText?: string, + popupIcon?: any, + disabled?: boolean, + sx?: any, + sxTextField?: any, + InputProps?: any, + onSelect?: (option: any) => void, + filterOptions?: (options: any[], state: FilterOptionsState) => any[]; +} + +const RHFAutocomplete = ( {name, label, options, ...rest} : IProps ) => { + const { control } = useFormContext(); + const { + sx, sxTextField, InputProps, + onSelect, + getOptionLabel, filterOptions, + popupIcon, + isOptionEqualToValue, disableClearable, + freeSolo, renderOption, + onInputChange, onKeyDown, onKeyPress, + noOptionsText, disabled } = rest; + + return ( + { + return ( + <> + ( + + )} + onChange={(e, newValue) => { + onChange(newValue); + onSelect && onSelect(newValue); + }} + /> + + {!!error && ( + + {error.message} + + )} + + + + ); + }} + /> + ); + + + +} + +export default RHFAutocomplete; \ No newline at end of file diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFAutocompleteTags.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFAutocompleteTags.tsx new file mode 100644 index 00000000..c8195440 --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFAutocompleteTags.tsx @@ -0,0 +1,75 @@ +import * as React from 'react'; +import Chip from '@mui/material/Chip'; +import TextField from '@mui/material/TextField'; +import Autocomplete from '@mui/material/Autocomplete'; +import { FormHelperText } from '@mui/material'; +import { Controller, useFormContext } from 'react-hook-form'; +import { useEffect } from 'react'; + +interface IProps { + name: string, + label: string, + options: any, + defaultValue: any +} + +const RHFAutocompleteTags = ({ name, label, options, defaultValue, ...rest }: IProps) => { + const { control } = useFormContext(); + const fixedOptions: any = []; + const [value, setValue] = React.useState([...fixedOptions]); + + useEffect(() => { + setValue(defaultValue) + }, [options, defaultValue]) + + return ( + { + return ( + <> + { + setValue([ + ...fixedOptions, + ...newValue.filter((option) => fixedOptions.indexOf(option) === -1), + ]); + onChange(newValue); + }} + isOptionEqualToValue={(option, value)=>{ + return option.optionID === value.optionID + }} + options={options} + getOptionLabel={(option: { optionID: string, optionLabel: string }) => `${option.optionLabel}` || ""} + renderTags={(tagValue, getTagProps) => + tagValue.map((option, index) => ( + + + + )) + } + renderInput={(params) => ( + + )} + /> + {!!error && ( + + {error.message} + + )} + + ); + }} + /> + + ); +} + +export default RHFAutocompleteTags; \ No newline at end of file diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFCheckbox.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFCheckbox.tsx new file mode 100644 index 00000000..c1ad1d7c --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFCheckbox.tsx @@ -0,0 +1,111 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { Checkbox, FormControlLabel, FormGroup, FormControlLabelProps, Grid, FormHelperText } from '@mui/material'; +import RHFDatePicker from './RHFDatePicker'; + +// ---------------------------------------------------------------------- + +interface RHFCheckboxProps extends Omit { + name: string; +} + +export function RHFCheckbox({ name, ...other }: RHFCheckboxProps) { + const { control } = useFormContext(); + + return ( + ( + <> + + {!!error && ( + + {error.message} + + )} + + + )} + /> + } + {...other} + /> + ); +} + +interface RHFCheckboxEndDateProps extends Omit { + name: string; + endPeriodProp: any; + idx?: any; +} + +export function RHFCheckboxEndDate({ name, endPeriodProp, idx, ...other }: RHFCheckboxEndDateProps) { + const { control } = useFormContext(); + + return ( + + { + endPeriodProp(e.target.checked, idx) + }} + /> + } + /> + } + {...other} + /> + ); +} + + +// ---------------------------------------------------------------------- + +interface RHFMultiCheckboxProps extends Omit { + name: string; + options: string[]; +} + +export function RHFMultiCheckbox({ name, options, ...other }: RHFMultiCheckboxProps) { + const { control } = useFormContext(); + + return ( + { + const onSelected = (option: string) => + field.value.includes(option) + ? field.value.filter((value: string) => value !== option) + : [...field.value, option]; + + return ( + + {options.map((option) => ( + field.onChange(onSelected(option))} + /> + } + label={option} + {...other} + /> + ))} + + ); + }} + /> + ); +} \ No newline at end of file diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFDatePicker.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFDatePicker.tsx new file mode 100644 index 00000000..e2fa986d --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFDatePicker.tsx @@ -0,0 +1,54 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText, TextField } from '@mui/material'; +import { DesktopDatePicker, LocalizationProvider } from '@mui/lab'; +import AdapterDateFns from '@mui/lab/AdapterDateFns'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + label: string; + dateFormat: string; + fullWidth?: boolean; + minDate?: any; + maxDate?: any; + disabled?: boolean; +} + +export default function RHFDatePicker({ name, label, dateFormat, minDate, maxDate, disabled, ...other }: IProps) { + const { control } = useFormContext(); + + const { fullWidth } = other; + + return ( + + ( + <> + field.onChange(date)} + inputFormat={dateFormat} + value={field.value} + mask={''} + minDate={(minDate) ? new Date(minDate) : null} + maxDate={(maxDate) ? new Date(maxDate) : null} + renderInput={(params) => } + /> + {!!error && ( + + {error.message} + + )} + + )} + /> + + ); +} \ No newline at end of file diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFDateTimePicker.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFDateTimePicker.tsx new file mode 100644 index 00000000..6dbc7eb2 --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFDateTimePicker.tsx @@ -0,0 +1,53 @@ +// form +import { useFormContext, Controller, useForm } from 'react-hook-form'; +// @mui +import { FormHelperText, TextField } from '@mui/material'; +import { DesktopDateTimePicker, LocalizationProvider } from '@mui/lab'; +import AdapterDateFns from '@mui/lab/AdapterDateFns'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + label: string; + dateFormat: string; + fullWidth?: boolean; + onChange?: (date: any) => void; + disabled?: boolean; +} + +export default function RHFDateTimePicker({ name, label, dateFormat, ...other }: IProps) { + const { control } = useFormContext(); + + const { fullWidth, onChange, disabled } = other; + + return ( + + ( + <> + { + field.onChange(date); + onChange && onChange(date); + }} + inputFormat={dateFormat} + value={field.value} + mask={''} + disabled={disabled} + renderInput={(params) => } + /> + {!!error && ( + + {error.message} + + )} + + )} + /> + + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFEditor.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFEditor.tsx new file mode 100644 index 00000000..84cf1c27 --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFEditor.tsx @@ -0,0 +1,37 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText } from '@mui/material'; +// +import Editor, { Props as EditorProps } from '../../editor'; + +// ---------------------------------------------------------------------- + +interface Props extends EditorProps { + name: string; +} + +export default function RHFEditor({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + ( + + {error?.message} + + } + {...other} + /> + )} + /> + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFRadioGroup.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFRadioGroup.tsx new file mode 100644 index 00000000..a2776f13 --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFRadioGroup.tsx @@ -0,0 +1,61 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { + Radio, + RadioGroup, + FormHelperText, + RadioGroupProps, + FormControlLabel, +} from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + options: string[]; + getOptionLabel?: string[]; + fullWidth?: boolean; + disabled?: boolean; +} + +export default function RHFRadioGroup({ + name, + options, + getOptionLabel, + fullWidth, + disabled, + ...other +}: IProps & RadioGroupProps) { + const { control } = useFormContext(); + + return ( + ( +
+ + {options.map((option, index) => ( + } + label={getOptionLabel?.length ? getOptionLabel[index] : option} + disabled={disabled} + /> + + ))} + + + {!!error && ( + + {error.message} + + )} +
+ )} + /> + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFSelect.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFSelect.tsx new file mode 100644 index 00000000..4f6c02b8 --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFSelect.tsx @@ -0,0 +1,35 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { TextField, TextFieldProps } from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + children: any; +} + +export default function RHFSelect({ name, children, ...other }: IProps & TextFieldProps) { + const { control } = useFormContext(); + + return ( + ( + + {children} + + )} + /> + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFSelectV2.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFSelectV2.tsx new file mode 100644 index 00000000..9337605f --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFSelectV2.tsx @@ -0,0 +1,32 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormControl, FormHelperText, InputLabel, Select, SelectProps } from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + id: string; + children: any; +} + +export default function RHFSelectV2({ name, id, children, ...other }: IProps & SelectProps) { + const { control } = useFormContext(); + + return ( + ( + + {other.label} + + {error && {error.message}} + + )} + /> + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFSwitch.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFSwitch.tsx new file mode 100644 index 00000000..a56c548a --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFSwitch.tsx @@ -0,0 +1,29 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { Switch, FormControlLabel, FormControlLabelProps } from '@mui/material'; + +// ---------------------------------------------------------------------- + +type IProps = Omit; + +interface Props extends IProps { + name: string; +} + +export default function RHFSwitch({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + } + /> + } + {...other} + /> + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFTextField.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFTextField.tsx new file mode 100644 index 00000000..6ffc8208 --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFTextField.tsx @@ -0,0 +1,29 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { TextField, TextFieldProps, Typography } from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; +} + +export default function RHFTextField({ name, ...other }: IProps & TextFieldProps) { + const { control } = useFormContext(); + + return ( + ( + <> + {/* + * + */} + + + )} + /> + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFTextFieldMoney.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFTextFieldMoney.tsx new file mode 100644 index 00000000..078c39b2 --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFTextFieldMoney.tsx @@ -0,0 +1,42 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { InputAdornment, TextField, TextFieldProps, Typography } from '@mui/material'; +import MoneyFormat from '../../numeric_format/MoneyFormat'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; +} + +export default function RHFTextFieldMoney({ name, ...other }: IProps & TextFieldProps) { + const { control, watch, setValue } = useFormContext(); + const values = watch(); + + return ( + ( + <> + { (values[name] === '0') && setValue(name, '') }} + onBlur={() => { (values[name] === '') && setValue(name, '0') }} + {...other} + inputProps={{ min: 0, max: 5, style: { textAlign: 'right' } }} + InputProps={{ + startAdornment: Rp, + inputComponent: MoneyFormat as any, + }} + /> + + )} + /> + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFTextFieldNumber.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFTextFieldNumber.tsx new file mode 100644 index 00000000..1646f7a8 --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFTextFieldNumber.tsx @@ -0,0 +1,61 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { InputAdornment, TextField, TextFieldProps, Typography } from '@mui/material'; +import MoneyFormat from '../../numeric_format/MoneyFormat'; +// import AutoNumeric from "autonumeric" +// import { useEffect, useRef } from 'react'; +// import React from 'react'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + endAdornment?: React.ReactNode; +} + +export default function RHFTextFieldNumber({ name, endAdornment, ...other }: IProps & TextFieldProps) { + const { control, watch, setValue } = useFormContext(); + const values = watch(); + + // const ref = React.createRef(); + // const mountedRef = useRef(false); + // useEffect(() => { + // mountedRef.current = true; + // new AutoNumeric(ref.current as HTMLElement) + + // return () => { + // mountedRef.current = false; + // } + // }, []) + + + + return ( + ( + <> + { (watch(name) == '0') && setValue(name, '') }} + onBlur={() => { (watch(name) == '') && setValue(name, '0') }} + {...other} + inputProps={{ min: 0, max: 5, style: { textAlign: 'right' } }} + InputProps={{ + inputComponent: MoneyFormat as any, + endAdornment: endAdornment, + + }} + + /> + + )} + /> + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFTextFieldPercentage.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFTextFieldPercentage.tsx new file mode 100644 index 00000000..f09eba7f --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFTextFieldPercentage.tsx @@ -0,0 +1,39 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { InputAdornment, TextField, TextFieldProps, Typography } from '@mui/material'; +import MoneyFormat from '../../numeric_format/MoneyFormat'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; +} + +export default function RHFTextFieldPercentage({ name, ...other }: IProps & TextFieldProps) { + const { control } = useFormContext(); + + return ( + ( + <> + %, + inputComponent: MoneyFormat as any, + }} + /> + + )} + /> + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFTimePicker.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFTimePicker.tsx new file mode 100644 index 00000000..70e9d8fe --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFTimePicker.tsx @@ -0,0 +1,46 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText, TextField } from '@mui/material'; +import { DesktopDatePicker, LocalizationProvider, TimePicker } from '@mui/lab'; +import AdapterDateFns from '@mui/lab/AdapterDateFns'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + label: string; + fullWidth?: boolean; + disabled?: boolean; +} + +export default function RHFTimePicker({ name, label, disabled, ...other }: IProps) { + const { control } = useFormContext(); + + const { fullWidth } = other; + + return ( + + ( + <> + field.onChange(date)} + value={field.value} + renderInput={(params) => } + /> + {!!error && ( + + {error.message} + + )} + + )} + /> + + ); +} \ No newline at end of file diff --git a/frontend/client-portal/src/components/hook-form/v2/RHFUpload.tsx b/frontend/client-portal/src/components/hook-form/v2/RHFUpload.tsx new file mode 100644 index 00000000..fddf48d6 --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/RHFUpload.tsx @@ -0,0 +1,112 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText } from '@mui/material'; +// type +import { + UploadAvatar, + UploadMultiFile, + UploadSingleFile, + UploadProps, + UploadMultiFileProps, +} from '../../upload'; +import { Accept } from 'react-dropzone'; + +// ---------------------------------------------------------------------- + +interface Props extends Omit { + name: string; +} + +export function RHFUploadAvatar({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + { + const checkError = !!error && !field.value; + + return ( +
+ + {checkError && ( + + {error.message} + + )} +
+ ); + }} + /> + ); +} + +// ---------------------------------------------------------------------- + +export function RHFUploadSingleFile({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + { + const checkError = !!error && !field.value; + + return ( + + {error.message} + + ) + } + {...other} + /> + ); + }} + /> + ); +} + +// ---------------------------------------------------------------------- + +interface RHFUploadMultiFileProps extends Omit { + name: string; +} + +export function RHFUploadMultiFile({ name, ...other }: RHFUploadMultiFileProps) { + const { control } = useFormContext(); + + return ( + { + const checkError = !!error && field.value?.length === 0; + + return ( + + {error?.message} + + ) + } + {...other} + /> + ); + }} + /> + ); +} diff --git a/frontend/client-portal/src/components/hook-form/v2/index.ts b/frontend/client-portal/src/components/hook-form/v2/index.ts new file mode 100644 index 00000000..f58a39a7 --- /dev/null +++ b/frontend/client-portal/src/components/hook-form/v2/index.ts @@ -0,0 +1,12 @@ +export * from './RHFCheckbox'; +export * from './RHFUpload'; + +export { default as FormProvider } from './FormProvider'; + +export { default as RHFSwitch } from './RHFSwitch'; +export { default as RHFAutocomplete } from './RHFAutocomplete'; +export { default as RHFSelect } from './RHFSelect'; +export { default as RHFEditor } from './RHFEditor'; +export { default as RHFTextField } from './RHFTextField'; +export { default as RHFRadioGroup } from './RHFRadioGroup'; +export { default as RHFSelectV2 } from './RHFSelectV2'; diff --git a/frontend/client-portal/src/components/numeric_format/DiscountPctFormat.tsx b/frontend/client-portal/src/components/numeric_format/DiscountPctFormat.tsx new file mode 100644 index 00000000..78c3ec6e --- /dev/null +++ b/frontend/client-portal/src/components/numeric_format/DiscountPctFormat.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { InputAttributes, NumericFormat, NumericFormatProps } from "react-number-format"; + +interface CustomProps { + onChange: (event: { target: { name: string; value: string } }) => void; + name: string; +} + +const DiscountPctFormat = React.forwardRef< + NumericFormatProps, + CustomProps +>(function DiscountPctFormat(props, ref) { + const { onChange, ...other } = props; + + return ( + { + onChange({ + target: { + name: props.name, + value: values.value, + }, + }); + }} + thousandSeparator + valueIsNumericString + allowLeadingZeros={false} + /> + ); +}); + +export default DiscountPctFormat; \ No newline at end of file diff --git a/frontend/client-portal/src/components/numeric_format/MoneyFormat.tsx b/frontend/client-portal/src/components/numeric_format/MoneyFormat.tsx new file mode 100644 index 00000000..6d564143 --- /dev/null +++ b/frontend/client-portal/src/components/numeric_format/MoneyFormat.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { InputAttributes, NumericFormat, NumericFormatProps } from "react-number-format"; + +interface CustomProps { + onChange: (event: { target: { name: string; value: string } }) => void; + name: string; +} + +const MoneyFormat = React.forwardRef< + NumericFormatProps, + CustomProps +>(function MoneyFormat(props, ref) { + const { onChange, ...other } = props; + + return ( + { + onChange({ + target: { + name: props.name, + value: values.value, + }, + }); + }} + thousandSeparator + valueIsNumericString + allowLeadingZeros={false} + /> + ); +}); + +export default MoneyFormat; \ No newline at end of file diff --git a/frontend/dashboard/package.json b/frontend/dashboard/package.json index 618d2c0b..83b1a540 100644 --- a/frontend/dashboard/package.json +++ b/frontend/dashboard/package.json @@ -76,6 +76,7 @@ "react-hook-form": "^7.45.4", "react-intersection-observer": "^8.34.0", "react-lazy-load-image-component": "^1.6.0", + "react-number-format": "^5.3.1", "react-quill": "2.0.0-beta.4", "react-redux": "^8.1.2", "react-router": "^6.15.0", diff --git a/frontend/dashboard/pnpm-lock.yaml b/frontend/dashboard/pnpm-lock.yaml index 54e4fb31..b026ce7e 100644 --- a/frontend/dashboard/pnpm-lock.yaml +++ b/frontend/dashboard/pnpm-lock.yaml @@ -119,6 +119,9 @@ dependencies: react-lazy-load-image-component: specifier: ^1.6.0 version: 1.6.0(react-dom@17.0.2)(react@17.0.2) + react-number-format: + specifier: ^5.3.1 + version: 5.3.1(react-dom@17.0.2)(react@17.0.2) react-quill: specifier: 2.0.0-beta.4 version: 2.0.0-beta.4(react-dom@17.0.2)(react@17.0.2) @@ -6797,6 +6800,17 @@ packages: react-dom: 17.0.2(react@17.0.2) dev: false + /react-number-format@5.3.1(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-qpYcQLauIeEhCZUZY9jXZnnroOtdy3jYaS1zQ3M1Sr6r/KMOBEIGNIb7eKT19g2N1wbYgFgvDzs19hw5TrB8XQ==} + peerDependencies: + react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + prop-types: 15.8.1 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + /react-quill@2.0.0-beta.4(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-KyAHvAlPjP4xLElKZJefMth91Z6FbbXRvq9OSu6xN3KBaoasLP9p+3dcxg4Ywr4tBlpMGXcPszYSAgd5CpJ45Q==} peerDependencies: diff --git a/frontend/dashboard/src/components/hook-form/v2/FormProvider.tsx b/frontend/dashboard/src/components/hook-form/v2/FormProvider.tsx new file mode 100644 index 00000000..c57e3cd8 --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/FormProvider.tsx @@ -0,0 +1,26 @@ +import { ReactNode } from 'react'; +// form +import { FormProvider as Form, UseFormReturn } from 'react-hook-form'; + +// ---------------------------------------------------------------------- + +type Props = { + children: ReactNode; + methods: UseFormReturn; + onSubmit?: VoidFunction; + preventEnterSubmit?: boolean; +}; + +export default function FormProvider({ children, onSubmit, methods, preventEnterSubmit }: Props) { + const checkKeyDown = (e: any) => { + if (e.key === 'Enter' && preventEnterSubmit){ + e.preventDefault(); + } + }; + + return ( +
+ checkKeyDown(e)}>{children}
+ + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFAutocomplete.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFAutocomplete.tsx new file mode 100644 index 00000000..b52f932a --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFAutocomplete.tsx @@ -0,0 +1,102 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { Autocomplete, AutocompleteProps, FormHelperText, TextField, UseAutocompleteProps } from '@mui/material'; +import { useState } from 'react'; +import { FilterOptionsState } from '@mui/material/useAutocomplete'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string, + label: string, + options: any, + getOptionLabel: (option: any) => string, + isOptionEqualToValue: any, + disableClearable: boolean, + freeSolo: boolean, + renderOption?: any, + onInputChange?: any, + onKeyDown?: any, + onKeyPress?: any, + noOptionsText?: string, + popupIcon?: any, + disabled?: boolean, + sx?: any, + sxTextField?: any, + InputProps?: any, + onSelect?: (option: any) => void, + filterOptions?: (options: any[], state: FilterOptionsState) => any[]; +} + +const RHFAutocomplete = ( {name, label, options, ...rest} : IProps ) => { + const { control } = useFormContext(); + const { + sx, sxTextField, InputProps, + onSelect, + getOptionLabel, filterOptions, + popupIcon, + isOptionEqualToValue, disableClearable, + freeSolo, renderOption, + onInputChange, onKeyDown, onKeyPress, + noOptionsText, disabled } = rest; + + return ( + { + return ( + <> + ( + + )} + onChange={(e, newValue) => { + onChange(newValue); + onSelect && onSelect(newValue); + }} + /> + + {!!error && ( + + {error.message} + + )} + + + + ); + }} + /> + ); + + + +} + +export default RHFAutocomplete; \ No newline at end of file diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFAutocompleteTags.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFAutocompleteTags.tsx new file mode 100644 index 00000000..c8195440 --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFAutocompleteTags.tsx @@ -0,0 +1,75 @@ +import * as React from 'react'; +import Chip from '@mui/material/Chip'; +import TextField from '@mui/material/TextField'; +import Autocomplete from '@mui/material/Autocomplete'; +import { FormHelperText } from '@mui/material'; +import { Controller, useFormContext } from 'react-hook-form'; +import { useEffect } from 'react'; + +interface IProps { + name: string, + label: string, + options: any, + defaultValue: any +} + +const RHFAutocompleteTags = ({ name, label, options, defaultValue, ...rest }: IProps) => { + const { control } = useFormContext(); + const fixedOptions: any = []; + const [value, setValue] = React.useState([...fixedOptions]); + + useEffect(() => { + setValue(defaultValue) + }, [options, defaultValue]) + + return ( + { + return ( + <> + { + setValue([ + ...fixedOptions, + ...newValue.filter((option) => fixedOptions.indexOf(option) === -1), + ]); + onChange(newValue); + }} + isOptionEqualToValue={(option, value)=>{ + return option.optionID === value.optionID + }} + options={options} + getOptionLabel={(option: { optionID: string, optionLabel: string }) => `${option.optionLabel}` || ""} + renderTags={(tagValue, getTagProps) => + tagValue.map((option, index) => ( + + + + )) + } + renderInput={(params) => ( + + )} + /> + {!!error && ( + + {error.message} + + )} + + ); + }} + /> + + ); +} + +export default RHFAutocompleteTags; \ No newline at end of file diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFCheckbox.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFCheckbox.tsx new file mode 100644 index 00000000..c1ad1d7c --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFCheckbox.tsx @@ -0,0 +1,111 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { Checkbox, FormControlLabel, FormGroup, FormControlLabelProps, Grid, FormHelperText } from '@mui/material'; +import RHFDatePicker from './RHFDatePicker'; + +// ---------------------------------------------------------------------- + +interface RHFCheckboxProps extends Omit { + name: string; +} + +export function RHFCheckbox({ name, ...other }: RHFCheckboxProps) { + const { control } = useFormContext(); + + return ( + ( + <> + + {!!error && ( + + {error.message} + + )} + + + )} + /> + } + {...other} + /> + ); +} + +interface RHFCheckboxEndDateProps extends Omit { + name: string; + endPeriodProp: any; + idx?: any; +} + +export function RHFCheckboxEndDate({ name, endPeriodProp, idx, ...other }: RHFCheckboxEndDateProps) { + const { control } = useFormContext(); + + return ( + + { + endPeriodProp(e.target.checked, idx) + }} + /> + } + /> + } + {...other} + /> + ); +} + + +// ---------------------------------------------------------------------- + +interface RHFMultiCheckboxProps extends Omit { + name: string; + options: string[]; +} + +export function RHFMultiCheckbox({ name, options, ...other }: RHFMultiCheckboxProps) { + const { control } = useFormContext(); + + return ( + { + const onSelected = (option: string) => + field.value.includes(option) + ? field.value.filter((value: string) => value !== option) + : [...field.value, option]; + + return ( + + {options.map((option) => ( + field.onChange(onSelected(option))} + /> + } + label={option} + {...other} + /> + ))} + + ); + }} + /> + ); +} \ No newline at end of file diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFDatePicker.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFDatePicker.tsx new file mode 100644 index 00000000..e2fa986d --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFDatePicker.tsx @@ -0,0 +1,54 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText, TextField } from '@mui/material'; +import { DesktopDatePicker, LocalizationProvider } from '@mui/lab'; +import AdapterDateFns from '@mui/lab/AdapterDateFns'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + label: string; + dateFormat: string; + fullWidth?: boolean; + minDate?: any; + maxDate?: any; + disabled?: boolean; +} + +export default function RHFDatePicker({ name, label, dateFormat, minDate, maxDate, disabled, ...other }: IProps) { + const { control } = useFormContext(); + + const { fullWidth } = other; + + return ( + + ( + <> + field.onChange(date)} + inputFormat={dateFormat} + value={field.value} + mask={''} + minDate={(minDate) ? new Date(minDate) : null} + maxDate={(maxDate) ? new Date(maxDate) : null} + renderInput={(params) => } + /> + {!!error && ( + + {error.message} + + )} + + )} + /> + + ); +} \ No newline at end of file diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFDateTimePicker.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFDateTimePicker.tsx new file mode 100644 index 00000000..6dbc7eb2 --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFDateTimePicker.tsx @@ -0,0 +1,53 @@ +// form +import { useFormContext, Controller, useForm } from 'react-hook-form'; +// @mui +import { FormHelperText, TextField } from '@mui/material'; +import { DesktopDateTimePicker, LocalizationProvider } from '@mui/lab'; +import AdapterDateFns from '@mui/lab/AdapterDateFns'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + label: string; + dateFormat: string; + fullWidth?: boolean; + onChange?: (date: any) => void; + disabled?: boolean; +} + +export default function RHFDateTimePicker({ name, label, dateFormat, ...other }: IProps) { + const { control } = useFormContext(); + + const { fullWidth, onChange, disabled } = other; + + return ( + + ( + <> + { + field.onChange(date); + onChange && onChange(date); + }} + inputFormat={dateFormat} + value={field.value} + mask={''} + disabled={disabled} + renderInput={(params) => } + /> + {!!error && ( + + {error.message} + + )} + + )} + /> + + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFEditor.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFEditor.tsx new file mode 100644 index 00000000..84cf1c27 --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFEditor.tsx @@ -0,0 +1,37 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText } from '@mui/material'; +// +import Editor, { Props as EditorProps } from '../../editor'; + +// ---------------------------------------------------------------------- + +interface Props extends EditorProps { + name: string; +} + +export default function RHFEditor({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + ( + + {error?.message} + + } + {...other} + /> + )} + /> + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFRadioGroup.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFRadioGroup.tsx new file mode 100644 index 00000000..a2776f13 --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFRadioGroup.tsx @@ -0,0 +1,61 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { + Radio, + RadioGroup, + FormHelperText, + RadioGroupProps, + FormControlLabel, +} from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + options: string[]; + getOptionLabel?: string[]; + fullWidth?: boolean; + disabled?: boolean; +} + +export default function RHFRadioGroup({ + name, + options, + getOptionLabel, + fullWidth, + disabled, + ...other +}: IProps & RadioGroupProps) { + const { control } = useFormContext(); + + return ( + ( +
+ + {options.map((option, index) => ( + } + label={getOptionLabel?.length ? getOptionLabel[index] : option} + disabled={disabled} + /> + + ))} + + + {!!error && ( + + {error.message} + + )} +
+ )} + /> + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFSelect.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFSelect.tsx new file mode 100644 index 00000000..4f6c02b8 --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFSelect.tsx @@ -0,0 +1,35 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { TextField, TextFieldProps } from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + children: any; +} + +export default function RHFSelect({ name, children, ...other }: IProps & TextFieldProps) { + const { control } = useFormContext(); + + return ( + ( + + {children} + + )} + /> + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFSelectV2.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFSelectV2.tsx new file mode 100644 index 00000000..9337605f --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFSelectV2.tsx @@ -0,0 +1,32 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormControl, FormHelperText, InputLabel, Select, SelectProps } from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + id: string; + children: any; +} + +export default function RHFSelectV2({ name, id, children, ...other }: IProps & SelectProps) { + const { control } = useFormContext(); + + return ( + ( + + {other.label} + + {error && {error.message}} + + )} + /> + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFSwitch.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFSwitch.tsx new file mode 100644 index 00000000..a56c548a --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFSwitch.tsx @@ -0,0 +1,29 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { Switch, FormControlLabel, FormControlLabelProps } from '@mui/material'; + +// ---------------------------------------------------------------------- + +type IProps = Omit; + +interface Props extends IProps { + name: string; +} + +export default function RHFSwitch({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + } + /> + } + {...other} + /> + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFTextField.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFTextField.tsx new file mode 100644 index 00000000..6ffc8208 --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFTextField.tsx @@ -0,0 +1,29 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { TextField, TextFieldProps, Typography } from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; +} + +export default function RHFTextField({ name, ...other }: IProps & TextFieldProps) { + const { control } = useFormContext(); + + return ( + ( + <> + {/* + * + */} + + + )} + /> + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFTextFieldMoney.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFTextFieldMoney.tsx new file mode 100644 index 00000000..078c39b2 --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFTextFieldMoney.tsx @@ -0,0 +1,42 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { InputAdornment, TextField, TextFieldProps, Typography } from '@mui/material'; +import MoneyFormat from '../../numeric_format/MoneyFormat'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; +} + +export default function RHFTextFieldMoney({ name, ...other }: IProps & TextFieldProps) { + const { control, watch, setValue } = useFormContext(); + const values = watch(); + + return ( + ( + <> + { (values[name] === '0') && setValue(name, '') }} + onBlur={() => { (values[name] === '') && setValue(name, '0') }} + {...other} + inputProps={{ min: 0, max: 5, style: { textAlign: 'right' } }} + InputProps={{ + startAdornment: Rp, + inputComponent: MoneyFormat as any, + }} + /> + + )} + /> + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFTextFieldNumber.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFTextFieldNumber.tsx new file mode 100644 index 00000000..1646f7a8 --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFTextFieldNumber.tsx @@ -0,0 +1,61 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { InputAdornment, TextField, TextFieldProps, Typography } from '@mui/material'; +import MoneyFormat from '../../numeric_format/MoneyFormat'; +// import AutoNumeric from "autonumeric" +// import { useEffect, useRef } from 'react'; +// import React from 'react'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + endAdornment?: React.ReactNode; +} + +export default function RHFTextFieldNumber({ name, endAdornment, ...other }: IProps & TextFieldProps) { + const { control, watch, setValue } = useFormContext(); + const values = watch(); + + // const ref = React.createRef(); + // const mountedRef = useRef(false); + // useEffect(() => { + // mountedRef.current = true; + // new AutoNumeric(ref.current as HTMLElement) + + // return () => { + // mountedRef.current = false; + // } + // }, []) + + + + return ( + ( + <> + { (watch(name) == '0') && setValue(name, '') }} + onBlur={() => { (watch(name) == '') && setValue(name, '0') }} + {...other} + inputProps={{ min: 0, max: 5, style: { textAlign: 'right' } }} + InputProps={{ + inputComponent: MoneyFormat as any, + endAdornment: endAdornment, + + }} + + /> + + )} + /> + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFTextFieldPercentage.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFTextFieldPercentage.tsx new file mode 100644 index 00000000..f09eba7f --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFTextFieldPercentage.tsx @@ -0,0 +1,39 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { InputAdornment, TextField, TextFieldProps, Typography } from '@mui/material'; +import MoneyFormat from '../../numeric_format/MoneyFormat'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; +} + +export default function RHFTextFieldPercentage({ name, ...other }: IProps & TextFieldProps) { + const { control } = useFormContext(); + + return ( + ( + <> + %, + inputComponent: MoneyFormat as any, + }} + /> + + )} + /> + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFTimePicker.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFTimePicker.tsx new file mode 100644 index 00000000..70e9d8fe --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFTimePicker.tsx @@ -0,0 +1,46 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText, TextField } from '@mui/material'; +import { DesktopDatePicker, LocalizationProvider, TimePicker } from '@mui/lab'; +import AdapterDateFns from '@mui/lab/AdapterDateFns'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + label: string; + fullWidth?: boolean; + disabled?: boolean; +} + +export default function RHFTimePicker({ name, label, disabled, ...other }: IProps) { + const { control } = useFormContext(); + + const { fullWidth } = other; + + return ( + + ( + <> + field.onChange(date)} + value={field.value} + renderInput={(params) => } + /> + {!!error && ( + + {error.message} + + )} + + )} + /> + + ); +} \ No newline at end of file diff --git a/frontend/dashboard/src/components/hook-form/v2/RHFUpload.tsx b/frontend/dashboard/src/components/hook-form/v2/RHFUpload.tsx new file mode 100644 index 00000000..fddf48d6 --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/RHFUpload.tsx @@ -0,0 +1,112 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText } from '@mui/material'; +// type +import { + UploadAvatar, + UploadMultiFile, + UploadSingleFile, + UploadProps, + UploadMultiFileProps, +} from '../../upload'; +import { Accept } from 'react-dropzone'; + +// ---------------------------------------------------------------------- + +interface Props extends Omit { + name: string; +} + +export function RHFUploadAvatar({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + { + const checkError = !!error && !field.value; + + return ( +
+ + {checkError && ( + + {error.message} + + )} +
+ ); + }} + /> + ); +} + +// ---------------------------------------------------------------------- + +export function RHFUploadSingleFile({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + { + const checkError = !!error && !field.value; + + return ( + + {error.message} + + ) + } + {...other} + /> + ); + }} + /> + ); +} + +// ---------------------------------------------------------------------- + +interface RHFUploadMultiFileProps extends Omit { + name: string; +} + +export function RHFUploadMultiFile({ name, ...other }: RHFUploadMultiFileProps) { + const { control } = useFormContext(); + + return ( + { + const checkError = !!error && field.value?.length === 0; + + return ( + + {error?.message} + + ) + } + {...other} + /> + ); + }} + /> + ); +} diff --git a/frontend/dashboard/src/components/hook-form/v2/index.ts b/frontend/dashboard/src/components/hook-form/v2/index.ts new file mode 100644 index 00000000..f58a39a7 --- /dev/null +++ b/frontend/dashboard/src/components/hook-form/v2/index.ts @@ -0,0 +1,12 @@ +export * from './RHFCheckbox'; +export * from './RHFUpload'; + +export { default as FormProvider } from './FormProvider'; + +export { default as RHFSwitch } from './RHFSwitch'; +export { default as RHFAutocomplete } from './RHFAutocomplete'; +export { default as RHFSelect } from './RHFSelect'; +export { default as RHFEditor } from './RHFEditor'; +export { default as RHFTextField } from './RHFTextField'; +export { default as RHFRadioGroup } from './RHFRadioGroup'; +export { default as RHFSelectV2 } from './RHFSelectV2'; diff --git a/frontend/dashboard/src/components/numeric_format/DiscountPctFormat.tsx b/frontend/dashboard/src/components/numeric_format/DiscountPctFormat.tsx new file mode 100644 index 00000000..78c3ec6e --- /dev/null +++ b/frontend/dashboard/src/components/numeric_format/DiscountPctFormat.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { InputAttributes, NumericFormat, NumericFormatProps } from "react-number-format"; + +interface CustomProps { + onChange: (event: { target: { name: string; value: string } }) => void; + name: string; +} + +const DiscountPctFormat = React.forwardRef< + NumericFormatProps, + CustomProps +>(function DiscountPctFormat(props, ref) { + const { onChange, ...other } = props; + + return ( + { + onChange({ + target: { + name: props.name, + value: values.value, + }, + }); + }} + thousandSeparator + valueIsNumericString + allowLeadingZeros={false} + /> + ); +}); + +export default DiscountPctFormat; \ No newline at end of file diff --git a/frontend/dashboard/src/components/numeric_format/MoneyFormat.tsx b/frontend/dashboard/src/components/numeric_format/MoneyFormat.tsx new file mode 100644 index 00000000..6d564143 --- /dev/null +++ b/frontend/dashboard/src/components/numeric_format/MoneyFormat.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { InputAttributes, NumericFormat, NumericFormatProps } from "react-number-format"; + +interface CustomProps { + onChange: (event: { target: { name: string; value: string } }) => void; + name: string; +} + +const MoneyFormat = React.forwardRef< + NumericFormatProps, + CustomProps +>(function MoneyFormat(props, ref) { + const { onChange, ...other } = props; + + return ( + { + onChange({ + target: { + name: props.name, + value: values.value, + }, + }); + }} + thousandSeparator + valueIsNumericString + allowLeadingZeros={false} + /> + ); +}); + +export default MoneyFormat; \ No newline at end of file diff --git a/frontend/hospital-portal/package.json b/frontend/hospital-portal/package.json index a20f100e..1c90c6b0 100644 --- a/frontend/hospital-portal/package.json +++ b/frontend/hospital-portal/package.json @@ -74,6 +74,7 @@ "react-hook-form": "^7.43.0", "react-intersection-observer": "^8.34.0", "react-lazy-load-image-component": "^1.5.6", + "react-number-format": "^5.3.1", "react-quill": "2.0.0-beta.4", "react-router": "^6.8.0", "react-router-dom": "^6.8.0", diff --git a/frontend/hospital-portal/pnpm-lock.yaml b/frontend/hospital-portal/pnpm-lock.yaml index 7fb2a7cf..6f2da469 100644 --- a/frontend/hospital-portal/pnpm-lock.yaml +++ b/frontend/hospital-portal/pnpm-lock.yaml @@ -113,6 +113,9 @@ dependencies: react-lazy-load-image-component: specifier: ^1.5.6 version: 1.5.6(react-dom@17.0.2)(react@17.0.2) + react-number-format: + specifier: ^5.3.1 + version: 5.3.1(react-dom@17.0.2)(react@17.0.2) react-quill: specifier: 2.0.0-beta.4 version: 2.0.0-beta.4(react-dom@17.0.2)(react@17.0.2) @@ -5313,6 +5316,17 @@ packages: react-dom: 17.0.2(react@17.0.2) dev: false + /react-number-format@5.3.1(react-dom@17.0.2)(react@17.0.2): + resolution: {integrity: sha512-qpYcQLauIeEhCZUZY9jXZnnroOtdy3jYaS1zQ3M1Sr6r/KMOBEIGNIb7eKT19g2N1wbYgFgvDzs19hw5TrB8XQ==} + peerDependencies: + react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + prop-types: 15.8.1 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + dev: false + /react-quill@2.0.0-beta.4(react-dom@17.0.2)(react@17.0.2): resolution: {integrity: sha512-KyAHvAlPjP4xLElKZJefMth91Z6FbbXRvq9OSu6xN3KBaoasLP9p+3dcxg4Ywr4tBlpMGXcPszYSAgd5CpJ45Q==} peerDependencies: diff --git a/frontend/hospital-portal/src/components/hook-form/v2/FormProvider.tsx b/frontend/hospital-portal/src/components/hook-form/v2/FormProvider.tsx new file mode 100644 index 00000000..c57e3cd8 --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/FormProvider.tsx @@ -0,0 +1,26 @@ +import { ReactNode } from 'react'; +// form +import { FormProvider as Form, UseFormReturn } from 'react-hook-form'; + +// ---------------------------------------------------------------------- + +type Props = { + children: ReactNode; + methods: UseFormReturn; + onSubmit?: VoidFunction; + preventEnterSubmit?: boolean; +}; + +export default function FormProvider({ children, onSubmit, methods, preventEnterSubmit }: Props) { + const checkKeyDown = (e: any) => { + if (e.key === 'Enter' && preventEnterSubmit){ + e.preventDefault(); + } + }; + + return ( +
+ checkKeyDown(e)}>{children}
+ + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFAutocomplete.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFAutocomplete.tsx new file mode 100644 index 00000000..b52f932a --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFAutocomplete.tsx @@ -0,0 +1,102 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { Autocomplete, AutocompleteProps, FormHelperText, TextField, UseAutocompleteProps } from '@mui/material'; +import { useState } from 'react'; +import { FilterOptionsState } from '@mui/material/useAutocomplete'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string, + label: string, + options: any, + getOptionLabel: (option: any) => string, + isOptionEqualToValue: any, + disableClearable: boolean, + freeSolo: boolean, + renderOption?: any, + onInputChange?: any, + onKeyDown?: any, + onKeyPress?: any, + noOptionsText?: string, + popupIcon?: any, + disabled?: boolean, + sx?: any, + sxTextField?: any, + InputProps?: any, + onSelect?: (option: any) => void, + filterOptions?: (options: any[], state: FilterOptionsState) => any[]; +} + +const RHFAutocomplete = ( {name, label, options, ...rest} : IProps ) => { + const { control } = useFormContext(); + const { + sx, sxTextField, InputProps, + onSelect, + getOptionLabel, filterOptions, + popupIcon, + isOptionEqualToValue, disableClearable, + freeSolo, renderOption, + onInputChange, onKeyDown, onKeyPress, + noOptionsText, disabled } = rest; + + return ( + { + return ( + <> + ( + + )} + onChange={(e, newValue) => { + onChange(newValue); + onSelect && onSelect(newValue); + }} + /> + + {!!error && ( + + {error.message} + + )} + + + + ); + }} + /> + ); + + + +} + +export default RHFAutocomplete; \ No newline at end of file diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFAutocompleteTags.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFAutocompleteTags.tsx new file mode 100644 index 00000000..c8195440 --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFAutocompleteTags.tsx @@ -0,0 +1,75 @@ +import * as React from 'react'; +import Chip from '@mui/material/Chip'; +import TextField from '@mui/material/TextField'; +import Autocomplete from '@mui/material/Autocomplete'; +import { FormHelperText } from '@mui/material'; +import { Controller, useFormContext } from 'react-hook-form'; +import { useEffect } from 'react'; + +interface IProps { + name: string, + label: string, + options: any, + defaultValue: any +} + +const RHFAutocompleteTags = ({ name, label, options, defaultValue, ...rest }: IProps) => { + const { control } = useFormContext(); + const fixedOptions: any = []; + const [value, setValue] = React.useState([...fixedOptions]); + + useEffect(() => { + setValue(defaultValue) + }, [options, defaultValue]) + + return ( + { + return ( + <> + { + setValue([ + ...fixedOptions, + ...newValue.filter((option) => fixedOptions.indexOf(option) === -1), + ]); + onChange(newValue); + }} + isOptionEqualToValue={(option, value)=>{ + return option.optionID === value.optionID + }} + options={options} + getOptionLabel={(option: { optionID: string, optionLabel: string }) => `${option.optionLabel}` || ""} + renderTags={(tagValue, getTagProps) => + tagValue.map((option, index) => ( + + + + )) + } + renderInput={(params) => ( + + )} + /> + {!!error && ( + + {error.message} + + )} + + ); + }} + /> + + ); +} + +export default RHFAutocompleteTags; \ No newline at end of file diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFCheckbox.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFCheckbox.tsx new file mode 100644 index 00000000..c1ad1d7c --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFCheckbox.tsx @@ -0,0 +1,111 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { Checkbox, FormControlLabel, FormGroup, FormControlLabelProps, Grid, FormHelperText } from '@mui/material'; +import RHFDatePicker from './RHFDatePicker'; + +// ---------------------------------------------------------------------- + +interface RHFCheckboxProps extends Omit { + name: string; +} + +export function RHFCheckbox({ name, ...other }: RHFCheckboxProps) { + const { control } = useFormContext(); + + return ( + ( + <> + + {!!error && ( + + {error.message} + + )} + + + )} + /> + } + {...other} + /> + ); +} + +interface RHFCheckboxEndDateProps extends Omit { + name: string; + endPeriodProp: any; + idx?: any; +} + +export function RHFCheckboxEndDate({ name, endPeriodProp, idx, ...other }: RHFCheckboxEndDateProps) { + const { control } = useFormContext(); + + return ( + + { + endPeriodProp(e.target.checked, idx) + }} + /> + } + /> + } + {...other} + /> + ); +} + + +// ---------------------------------------------------------------------- + +interface RHFMultiCheckboxProps extends Omit { + name: string; + options: string[]; +} + +export function RHFMultiCheckbox({ name, options, ...other }: RHFMultiCheckboxProps) { + const { control } = useFormContext(); + + return ( + { + const onSelected = (option: string) => + field.value.includes(option) + ? field.value.filter((value: string) => value !== option) + : [...field.value, option]; + + return ( + + {options.map((option) => ( + field.onChange(onSelected(option))} + /> + } + label={option} + {...other} + /> + ))} + + ); + }} + /> + ); +} \ No newline at end of file diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFDatePicker.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFDatePicker.tsx new file mode 100644 index 00000000..e2fa986d --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFDatePicker.tsx @@ -0,0 +1,54 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText, TextField } from '@mui/material'; +import { DesktopDatePicker, LocalizationProvider } from '@mui/lab'; +import AdapterDateFns from '@mui/lab/AdapterDateFns'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + label: string; + dateFormat: string; + fullWidth?: boolean; + minDate?: any; + maxDate?: any; + disabled?: boolean; +} + +export default function RHFDatePicker({ name, label, dateFormat, minDate, maxDate, disabled, ...other }: IProps) { + const { control } = useFormContext(); + + const { fullWidth } = other; + + return ( + + ( + <> + field.onChange(date)} + inputFormat={dateFormat} + value={field.value} + mask={''} + minDate={(minDate) ? new Date(minDate) : null} + maxDate={(maxDate) ? new Date(maxDate) : null} + renderInput={(params) => } + /> + {!!error && ( + + {error.message} + + )} + + )} + /> + + ); +} \ No newline at end of file diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFDateTimePicker.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFDateTimePicker.tsx new file mode 100644 index 00000000..6dbc7eb2 --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFDateTimePicker.tsx @@ -0,0 +1,53 @@ +// form +import { useFormContext, Controller, useForm } from 'react-hook-form'; +// @mui +import { FormHelperText, TextField } from '@mui/material'; +import { DesktopDateTimePicker, LocalizationProvider } from '@mui/lab'; +import AdapterDateFns from '@mui/lab/AdapterDateFns'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + label: string; + dateFormat: string; + fullWidth?: boolean; + onChange?: (date: any) => void; + disabled?: boolean; +} + +export default function RHFDateTimePicker({ name, label, dateFormat, ...other }: IProps) { + const { control } = useFormContext(); + + const { fullWidth, onChange, disabled } = other; + + return ( + + ( + <> + { + field.onChange(date); + onChange && onChange(date); + }} + inputFormat={dateFormat} + value={field.value} + mask={''} + disabled={disabled} + renderInput={(params) => } + /> + {!!error && ( + + {error.message} + + )} + + )} + /> + + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFEditor.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFEditor.tsx new file mode 100644 index 00000000..84cf1c27 --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFEditor.tsx @@ -0,0 +1,37 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText } from '@mui/material'; +// +import Editor, { Props as EditorProps } from '../../editor'; + +// ---------------------------------------------------------------------- + +interface Props extends EditorProps { + name: string; +} + +export default function RHFEditor({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + ( + + {error?.message} + + } + {...other} + /> + )} + /> + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFRadioGroup.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFRadioGroup.tsx new file mode 100644 index 00000000..a2776f13 --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFRadioGroup.tsx @@ -0,0 +1,61 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { + Radio, + RadioGroup, + FormHelperText, + RadioGroupProps, + FormControlLabel, +} from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + options: string[]; + getOptionLabel?: string[]; + fullWidth?: boolean; + disabled?: boolean; +} + +export default function RHFRadioGroup({ + name, + options, + getOptionLabel, + fullWidth, + disabled, + ...other +}: IProps & RadioGroupProps) { + const { control } = useFormContext(); + + return ( + ( +
+ + {options.map((option, index) => ( + } + label={getOptionLabel?.length ? getOptionLabel[index] : option} + disabled={disabled} + /> + + ))} + + + {!!error && ( + + {error.message} + + )} +
+ )} + /> + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFSelect.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFSelect.tsx new file mode 100644 index 00000000..4f6c02b8 --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFSelect.tsx @@ -0,0 +1,35 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { TextField, TextFieldProps } from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + children: any; +} + +export default function RHFSelect({ name, children, ...other }: IProps & TextFieldProps) { + const { control } = useFormContext(); + + return ( + ( + + {children} + + )} + /> + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFSelectV2.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFSelectV2.tsx new file mode 100644 index 00000000..9337605f --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFSelectV2.tsx @@ -0,0 +1,32 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormControl, FormHelperText, InputLabel, Select, SelectProps } from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + id: string; + children: any; +} + +export default function RHFSelectV2({ name, id, children, ...other }: IProps & SelectProps) { + const { control } = useFormContext(); + + return ( + ( + + {other.label} + + {error && {error.message}} + + )} + /> + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFSwitch.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFSwitch.tsx new file mode 100644 index 00000000..a56c548a --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFSwitch.tsx @@ -0,0 +1,29 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { Switch, FormControlLabel, FormControlLabelProps } from '@mui/material'; + +// ---------------------------------------------------------------------- + +type IProps = Omit; + +interface Props extends IProps { + name: string; +} + +export default function RHFSwitch({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + } + /> + } + {...other} + /> + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFTextField.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFTextField.tsx new file mode 100644 index 00000000..6ffc8208 --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFTextField.tsx @@ -0,0 +1,29 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { TextField, TextFieldProps, Typography } from '@mui/material'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; +} + +export default function RHFTextField({ name, ...other }: IProps & TextFieldProps) { + const { control } = useFormContext(); + + return ( + ( + <> + {/* + * + */} + + + )} + /> + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFTextFieldMoney.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFTextFieldMoney.tsx new file mode 100644 index 00000000..078c39b2 --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFTextFieldMoney.tsx @@ -0,0 +1,42 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { InputAdornment, TextField, TextFieldProps, Typography } from '@mui/material'; +import MoneyFormat from '../../numeric_format/MoneyFormat'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; +} + +export default function RHFTextFieldMoney({ name, ...other }: IProps & TextFieldProps) { + const { control, watch, setValue } = useFormContext(); + const values = watch(); + + return ( + ( + <> + { (values[name] === '0') && setValue(name, '') }} + onBlur={() => { (values[name] === '') && setValue(name, '0') }} + {...other} + inputProps={{ min: 0, max: 5, style: { textAlign: 'right' } }} + InputProps={{ + startAdornment: Rp, + inputComponent: MoneyFormat as any, + }} + /> + + )} + /> + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFTextFieldNumber.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFTextFieldNumber.tsx new file mode 100644 index 00000000..1646f7a8 --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFTextFieldNumber.tsx @@ -0,0 +1,61 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { InputAdornment, TextField, TextFieldProps, Typography } from '@mui/material'; +import MoneyFormat from '../../numeric_format/MoneyFormat'; +// import AutoNumeric from "autonumeric" +// import { useEffect, useRef } from 'react'; +// import React from 'react'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + endAdornment?: React.ReactNode; +} + +export default function RHFTextFieldNumber({ name, endAdornment, ...other }: IProps & TextFieldProps) { + const { control, watch, setValue } = useFormContext(); + const values = watch(); + + // const ref = React.createRef(); + // const mountedRef = useRef(false); + // useEffect(() => { + // mountedRef.current = true; + // new AutoNumeric(ref.current as HTMLElement) + + // return () => { + // mountedRef.current = false; + // } + // }, []) + + + + return ( + ( + <> + { (watch(name) == '0') && setValue(name, '') }} + onBlur={() => { (watch(name) == '') && setValue(name, '0') }} + {...other} + inputProps={{ min: 0, max: 5, style: { textAlign: 'right' } }} + InputProps={{ + inputComponent: MoneyFormat as any, + endAdornment: endAdornment, + + }} + + /> + + )} + /> + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFTextFieldPercentage.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFTextFieldPercentage.tsx new file mode 100644 index 00000000..f09eba7f --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFTextFieldPercentage.tsx @@ -0,0 +1,39 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { InputAdornment, TextField, TextFieldProps, Typography } from '@mui/material'; +import MoneyFormat from '../../numeric_format/MoneyFormat'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; +} + +export default function RHFTextFieldPercentage({ name, ...other }: IProps & TextFieldProps) { + const { control } = useFormContext(); + + return ( + ( + <> + %, + inputComponent: MoneyFormat as any, + }} + /> + + )} + /> + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFTimePicker.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFTimePicker.tsx new file mode 100644 index 00000000..70e9d8fe --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFTimePicker.tsx @@ -0,0 +1,46 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText, TextField } from '@mui/material'; +import { DesktopDatePicker, LocalizationProvider, TimePicker } from '@mui/lab'; +import AdapterDateFns from '@mui/lab/AdapterDateFns'; + +// ---------------------------------------------------------------------- + +interface IProps { + name: string; + label: string; + fullWidth?: boolean; + disabled?: boolean; +} + +export default function RHFTimePicker({ name, label, disabled, ...other }: IProps) { + const { control } = useFormContext(); + + const { fullWidth } = other; + + return ( + + ( + <> + field.onChange(date)} + value={field.value} + renderInput={(params) => } + /> + {!!error && ( + + {error.message} + + )} + + )} + /> + + ); +} \ No newline at end of file diff --git a/frontend/hospital-portal/src/components/hook-form/v2/RHFUpload.tsx b/frontend/hospital-portal/src/components/hook-form/v2/RHFUpload.tsx new file mode 100644 index 00000000..fddf48d6 --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/RHFUpload.tsx @@ -0,0 +1,112 @@ +// form +import { useFormContext, Controller } from 'react-hook-form'; +// @mui +import { FormHelperText } from '@mui/material'; +// type +import { + UploadAvatar, + UploadMultiFile, + UploadSingleFile, + UploadProps, + UploadMultiFileProps, +} from '../../upload'; +import { Accept } from 'react-dropzone'; + +// ---------------------------------------------------------------------- + +interface Props extends Omit { + name: string; +} + +export function RHFUploadAvatar({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + { + const checkError = !!error && !field.value; + + return ( +
+ + {checkError && ( + + {error.message} + + )} +
+ ); + }} + /> + ); +} + +// ---------------------------------------------------------------------- + +export function RHFUploadSingleFile({ name, ...other }: Props) { + const { control } = useFormContext(); + + return ( + { + const checkError = !!error && !field.value; + + return ( + + {error.message} + + ) + } + {...other} + /> + ); + }} + /> + ); +} + +// ---------------------------------------------------------------------- + +interface RHFUploadMultiFileProps extends Omit { + name: string; +} + +export function RHFUploadMultiFile({ name, ...other }: RHFUploadMultiFileProps) { + const { control } = useFormContext(); + + return ( + { + const checkError = !!error && field.value?.length === 0; + + return ( + + {error?.message} + + ) + } + {...other} + /> + ); + }} + /> + ); +} diff --git a/frontend/hospital-portal/src/components/hook-form/v2/index.ts b/frontend/hospital-portal/src/components/hook-form/v2/index.ts new file mode 100644 index 00000000..f58a39a7 --- /dev/null +++ b/frontend/hospital-portal/src/components/hook-form/v2/index.ts @@ -0,0 +1,12 @@ +export * from './RHFCheckbox'; +export * from './RHFUpload'; + +export { default as FormProvider } from './FormProvider'; + +export { default as RHFSwitch } from './RHFSwitch'; +export { default as RHFAutocomplete } from './RHFAutocomplete'; +export { default as RHFSelect } from './RHFSelect'; +export { default as RHFEditor } from './RHFEditor'; +export { default as RHFTextField } from './RHFTextField'; +export { default as RHFRadioGroup } from './RHFRadioGroup'; +export { default as RHFSelectV2 } from './RHFSelectV2'; diff --git a/frontend/hospital-portal/src/components/numeric_format/DiscountPctFormat.tsx b/frontend/hospital-portal/src/components/numeric_format/DiscountPctFormat.tsx new file mode 100644 index 00000000..78c3ec6e --- /dev/null +++ b/frontend/hospital-portal/src/components/numeric_format/DiscountPctFormat.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { InputAttributes, NumericFormat, NumericFormatProps } from "react-number-format"; + +interface CustomProps { + onChange: (event: { target: { name: string; value: string } }) => void; + name: string; +} + +const DiscountPctFormat = React.forwardRef< + NumericFormatProps, + CustomProps +>(function DiscountPctFormat(props, ref) { + const { onChange, ...other } = props; + + return ( + { + onChange({ + target: { + name: props.name, + value: values.value, + }, + }); + }} + thousandSeparator + valueIsNumericString + allowLeadingZeros={false} + /> + ); +}); + +export default DiscountPctFormat; \ No newline at end of file diff --git a/frontend/hospital-portal/src/components/numeric_format/MoneyFormat.tsx b/frontend/hospital-portal/src/components/numeric_format/MoneyFormat.tsx new file mode 100644 index 00000000..6d564143 --- /dev/null +++ b/frontend/hospital-portal/src/components/numeric_format/MoneyFormat.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { InputAttributes, NumericFormat, NumericFormatProps } from "react-number-format"; + +interface CustomProps { + onChange: (event: { target: { name: string; value: string } }) => void; + name: string; +} + +const MoneyFormat = React.forwardRef< + NumericFormatProps, + CustomProps +>(function MoneyFormat(props, ref) { + const { onChange, ...other } = props; + + return ( + { + onChange({ + target: { + name: props.name, + value: values.value, + }, + }); + }} + thousandSeparator + valueIsNumericString + allowLeadingZeros={false} + /> + ); +}); + +export default MoneyFormat; \ No newline at end of file