feat: integrate react-number-format for price inputs in product create and edit forms

This commit is contained in:
Yoga Pangestu 2026-04-16 13:16:05 +07:00
parent 4b5d5af316
commit bfee01153c
5 changed files with 1932 additions and 62 deletions

1799
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -82,6 +82,7 @@
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-hotkeys-hook": "^5.2.4",
"react-number-format": "^5.4.5",
"shadcn": "^4.2.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",

View File

@ -21,6 +21,7 @@ import {
ComboboxValue,
useComboboxAnchor,
} from "@/components/ui/combobox"
import { NumericFormat } from 'react-number-format';
export default function ProductCreate({ categories }: { categories: Category[] }) {
const { data, setData, post, processing, errors } = useForm({
@ -122,65 +123,100 @@ export default function ProductCreate({ categories }: { categories: Category[] }
<Field>
<Label htmlFor="price_purchase">Harga Beli</Label>
<Input
<NumericFormat
id="price_purchase"
type="number"
min="0"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.prices.purchase}
onChange={e => setData('prices', { ...data.prices, purchase: e.target.value })}
placeholder='0'
onValueChange={(values) => {
setData('prices', {
...data.prices,
purchase: values.value
})
}}
placeholder="Rp 0"
/>
{errors['prices.purchase'] && <p className="text-xs text-red-500">{errors['prices.purchase']}</p>}
</Field>
<Field>
<Label htmlFor="price_distributor">Harga Distributor</Label>
<Input
<NumericFormat
id="price_distributor"
type="number"
min="0"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.prices.distributor}
onChange={e => setData('prices', { ...data.prices, distributor: e.target.value })}
placeholder='0'
onValueChange={(values) => {
setData('prices', {
...data.prices,
distributor: values.value
})
}}
placeholder="Rp 0"
/>
{errors['prices.distributor'] && <p className="text-xs text-red-500">{errors['prices.distributor']}</p>}
</Field>
<Field>
<Label htmlFor="price_agent">Harga Agen</Label>
<Input
<NumericFormat
id="price_agent"
type="number"
min="0"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.prices.agent}
onChange={e => setData('prices', { ...data.prices, agent: e.target.value })}
placeholder='0'
onValueChange={(values) => {
setData('prices', {
...data.prices,
agent: values.value
})
}}
placeholder="Rp 0"
/>
{errors['prices.agent'] && <p className="text-xs text-red-500">{errors['prices.agent']}</p>}
</Field>
<Field>
<Label htmlFor="price_reseller">Harga Reseller</Label>
<Input
<NumericFormat
id="price_reseller"
type="number"
min="0"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.prices.reseller}
onChange={e => setData('prices', { ...data.prices, reseller: e.target.value })}
placeholder='0'
onValueChange={(values) => {
setData('prices', {
...data.prices,
reseller: values.value
})
}}
placeholder="Rp 0"
/>
{errors['prices.reseller'] && <p className="text-xs text-red-500">{errors['prices.reseller']}</p>}
</Field>
<Field>
<Label htmlFor="price_retail">Harga Retail</Label>
<Input
<NumericFormat
id="price_retail"
type="number"
min="0"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.prices.retail}
onChange={e => setData('prices', { ...data.prices, retail: e.target.value })}
placeholder='0'
onValueChange={(values) => {
setData('prices', {
...data.prices,
retail: values.value
})
}}
placeholder="Rp 0"
/>
{errors['prices.retail'] && <p className="text-xs text-red-500">{errors['prices.retail']}</p>}
</Field>

View File

@ -22,6 +22,7 @@ import {
ComboboxValue,
useComboboxAnchor,
} from "@/components/ui/combobox"
import { NumericFormat } from 'react-number-format';
export default function ProductEdit({ product, categories }: { product: Product, categories: Category[] }) {
const getPrice = (type: string) => {
@ -130,65 +131,100 @@ export default function ProductEdit({ product, categories }: { product: Product,
<Field>
<Label htmlFor="price_purchase">Harga Beli</Label>
<Input
<NumericFormat
id="price_purchase"
type="number"
min="0"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.prices.purchase}
onChange={e => setData('prices', { ...data.prices, purchase: e.target.value })}
placeholder='0'
onValueChange={(values) => {
setData('prices', {
...data.prices,
purchase: values.value
})
}}
placeholder="Rp 0"
/>
{errors['prices.purchase'] && <p className="text-xs text-red-500">{errors['prices.purchase']}</p>}
</Field>
<Field>
<Label htmlFor="price_distributor">Harga Distributor</Label>
<Input
<NumericFormat
id="price_distributor"
type="number"
min="0"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.prices.distributor}
onChange={e => setData('prices', { ...data.prices, distributor: e.target.value })}
placeholder='0'
onValueChange={(values) => {
setData('prices', {
...data.prices,
distributor: values.value
})
}}
placeholder="Rp 0"
/>
{errors['prices.distributor'] && <p className="text-xs text-red-500">{errors['prices.distributor']}</p>}
</Field>
<Field>
<Label htmlFor="price_agent">Harga Agen</Label>
<Input
<NumericFormat
id="price_agent"
type="number"
min="0"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.prices.agent}
onChange={e => setData('prices', { ...data.prices, agent: e.target.value })}
placeholder='0'
onValueChange={(values) => {
setData('prices', {
...data.prices,
agent: values.value
})
}}
placeholder="Rp 0"
/>
{errors['prices.agent'] && <p className="text-xs text-red-500">{errors['prices.agent']}</p>}
</Field>
<Field>
<Label htmlFor="price_reseller">Harga Reseller</Label>
<Input
<NumericFormat
id="price_reseller"
type="number"
min="0"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.prices.reseller}
onChange={e => setData('prices', { ...data.prices, reseller: e.target.value })}
placeholder='0'
onValueChange={(values) => {
setData('prices', {
...data.prices,
reseller: values.value
})
}}
placeholder="Rp 0"
/>
{errors['prices.reseller'] && <p className="text-xs text-red-500">{errors['prices.reseller']}</p>}
</Field>
<Field>
<Label htmlFor="price_retail">Harga Retail</Label>
<Input
<NumericFormat
id="price_retail"
type="number"
min="0"
customInput={Input}
thousandSeparator="."
decimalSeparator=","
prefix="Rp "
value={data.prices.retail}
onChange={e => setData('prices', { ...data.prices, retail: e.target.value })}
placeholder='0'
onValueChange={(values) => {
setData('prices', {
...data.prices,
retail: values.value
})
}}
placeholder="Rp 0"
/>
{errors['prices.retail'] && <p className="text-xs text-red-500">{errors['prices.retail']}</p>}
</Field>

View File

@ -4,6 +4,11 @@ import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import laravel from 'laravel-vite-plugin';
import { defineConfig } from 'vite';
import { fileURLToPath } from 'url';
import path from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default defineConfig({
plugins: [
@ -13,13 +18,24 @@ export default defineConfig({
}),
inertia(),
react({
babel: {
plugins: ['babel-plugin-react-compiler'],
},
// Babel compiler can sometimes cause issues with third-party hooks in React 19
// babel: {
// plugins: ['babel-plugin-react-compiler'],
// },
}),
tailwindcss(),
wayfinder({
formVariants: true,
}),
],
resolve: {
alias: {
'react': path.resolve(__dirname, 'node_modules/react'),
'react-dom': path.resolve(__dirname, 'node_modules/react-dom'),
'@': path.resolve(__dirname, 'resources/js'),
},
},
optimizeDeps: {
include: ['react-number-format'],
},
});