efril #6

Merged
aefril merged 43 commits from efril into main 2025-09-11 18:58:35 +00:00
2 changed files with 511 additions and 7 deletions
Showing only changes of commit b3a41fe0e0 - Show all commits

View File

@ -0,0 +1,454 @@
'use client'
// React Imports
import { useEffect, useState } from 'react'
// MUI Imports
import Button from '@mui/material/Button'
import Drawer from '@mui/material/Drawer'
import IconButton from '@mui/material/IconButton'
import MenuItem from '@mui/material/MenuItem'
import Typography from '@mui/material/Typography'
import Grid from '@mui/material/Grid2'
import Box from '@mui/material/Box'
import Radio from '@mui/material/Radio'
import RadioGroup from '@mui/material/RadioGroup'
import FormControlLabel from '@mui/material/FormControlLabel'
import FormControl from '@mui/material/FormControl'
// Third-party Imports
import { useForm, Controller } from 'react-hook-form'
// Component Imports
import CustomTextField from '@core/components/mui/TextField'
type Props = {
open: boolean
handleClose: () => void
setData?: (data: any) => void
}
type StockAdjustmentType = {
tipeStok: 'perhitungan' | 'stokMasukKeluar'
gudang: string
tanggal: string
akun: string
nomor: string
referensi: string
tag: string
qtyTercatat: number
satuan: string
qtyAktual: number
selisih: number
hargaRataRata: number
}
type FormValidateType = StockAdjustmentType
// Vars
const initialData: StockAdjustmentType = {
tipeStok: 'perhitungan',
gudang: '',
tanggal: '11/09/2025',
akun: '8-80100 Penyesuaian Persediaan',
nomor: 'SA/00007',
referensi: '',
tag: '',
qtyTercatat: 0,
satuan: 'Pcs',
qtyAktual: 0,
selisih: 0,
hargaRataRata: 0
}
const IngredientDetailStockAdjustmentDrawer = (props: Props) => {
// Props
const { open, handleClose, setData } = props
// States
const [formData, setFormData] = useState<StockAdjustmentType>(initialData)
// Hooks
const {
control,
reset: resetForm,
handleSubmit,
watch,
setValue,
formState: { errors }
} = useForm<FormValidateType>({
defaultValues: initialData
})
// Watch values untuk kalkulasi otomatis
const qtyTercatat = watch('qtyTercatat')
const qtyAktual = watch('qtyAktual')
// Kalkulasi selisih otomatis
useEffect(() => {
const selisih = qtyAktual - qtyTercatat
setValue('selisih', selisih)
}, [qtyTercatat, qtyAktual, setValue])
const onSubmit = (data: FormValidateType) => {
console.log('Stock adjustment data:', data)
if (setData) {
setData(data)
}
handleClose()
}
const handleReset = () => {
handleClose()
resetForm(initialData)
setFormData(initialData)
}
const formatNumber = (value: number) => {
return new Intl.NumberFormat('id-ID').format(value)
}
const parseNumber = (value: string) => {
return parseInt(value.replace(/\./g, '')) || 0
}
return (
<Drawer
open={open}
anchor='right'
variant='temporary'
onClose={handleReset}
ModalProps={{ keepMounted: true }}
sx={{
'& .MuiDrawer-paper': {
width: { xs: 400, sm: 600 },
display: 'flex',
flexDirection: 'column',
height: '100%'
}
}}
>
{/* Sticky Header */}
<Box
sx={{
position: 'sticky',
top: 0,
zIndex: 10,
backgroundColor: 'background.paper',
borderBottom: 1,
borderColor: 'divider'
}}
>
<div className='flex items-center justify-between plb-5 pli-6'>
<Typography variant='h5'>Penyesuaian Stok</Typography>
<IconButton size='small' onClick={handleReset}>
<i className='tabler-x text-2xl text-textPrimary' />
</IconButton>
</div>
</Box>
{/* Scrollable Content */}
<Box sx={{ flex: 1, overflowY: 'auto' }}>
<form id='stock-adjustment-form' onSubmit={handleSubmit(data => onSubmit(data))}>
<div className='flex flex-col gap-6 p-6'>
{/* Tipe Penyesuaian Stok */}
<div>
<Typography variant='body2' className='mb-3' sx={{ color: 'error.main' }}>
* Tipe penyesuaian stok
</Typography>
<Controller
name='tipeStok'
control={control}
rules={{ required: true }}
render={({ field }) => (
<FormControl component='fieldset'>
<RadioGroup {...field} row sx={{ gap: 4 }}>
<FormControlLabel
value='perhitungan'
control={<Radio />}
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography>Perhitungan Stok</Typography>
<IconButton size='small' sx={{ color: 'text.secondary' }}>
<i className='tabler-help-circle' style={{ fontSize: '16px' }} />
</IconButton>
</Box>
}
/>
<FormControlLabel
value='stokMasukKeluar'
control={<Radio />}
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Typography>Stok Masuk / Keluar</Typography>
<IconButton size='small' sx={{ color: 'text.secondary' }}>
<i className='tabler-help-circle' style={{ fontSize: '16px' }} />
</IconButton>
</Box>
}
/>
</RadioGroup>
</FormControl>
)}
/>
</div>
{/* Gudang dan Tanggal */}
<Grid container spacing={4}>
<Grid size={6}>
<Typography variant='body2' className='mb-2' sx={{ color: 'error.main' }}>
* Gudang
</Typography>
<Controller
name='gudang'
control={control}
rules={{ required: true }}
render={({ field }) => (
<CustomTextField
{...field}
select
fullWidth
placeholder='Pilih gudang'
{...(errors.gudang && { error: true, helperText: 'Field ini wajib diisi.' })}
>
<MenuItem value=''>Pilih gudang</MenuItem>
<MenuItem value='Gudang Utama'>Gudang Utama</MenuItem>
<MenuItem value='Gudang Cabang'>Gudang Cabang</MenuItem>
<MenuItem value='Gudang Produksi'>Gudang Produksi</MenuItem>
</CustomTextField>
)}
/>
</Grid>
<Grid size={6}>
<Typography variant='body2' className='mb-2' sx={{ color: 'error.main' }}>
* Tanggal
</Typography>
<Controller
name='tanggal'
control={control}
rules={{ required: true }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
type='date'
{...(errors.tanggal && { error: true, helperText: 'Field ini wajib diisi.' })}
/>
)}
/>
</Grid>
</Grid>
{/* Akun dan Nomor */}
<Grid container spacing={4}>
<Grid size={6}>
<Typography variant='body2' className='mb-2' sx={{ color: 'error.main' }}>
* Akun
<IconButton size='small' sx={{ color: 'text.secondary', ml: 1 }}>
<i className='tabler-help-circle' style={{ fontSize: '16px' }} />
</IconButton>
</Typography>
<Controller
name='akun'
control={control}
rules={{ required: true }}
render={({ field }) => (
<CustomTextField
{...field}
select
fullWidth
{...(errors.akun && { error: true, helperText: 'Field ini wajib diisi.' })}
>
<MenuItem value='8-80100 Penyesuaian Persediaan'>8-80100 Penyesuaian Persediaan</MenuItem>
<MenuItem value='8-80200 Penyesuaian Stok Rusak'>8-80200 Penyesuaian Stok Rusak</MenuItem>
<MenuItem value='8-80300 Penyesuaian Stok Hilang'>8-80300 Penyesuaian Stok Hilang</MenuItem>
</CustomTextField>
)}
/>
</Grid>
<Grid size={6}>
<Typography variant='body2' className='mb-2'>
Nomor
<IconButton size='small' sx={{ color: 'text.secondary', ml: 1 }}>
<i className='tabler-help-circle' style={{ fontSize: '16px' }} />
</IconButton>
</Typography>
<Controller
name='nomor'
control={control}
render={({ field }) => <CustomTextField {...field} fullWidth placeholder='SA/00007' />}
/>
</Grid>
</Grid>
{/* Referensi dan Tag */}
<Grid container spacing={4}>
<Grid size={6}>
<Typography variant='body2' className='mb-2'>
Referensi
<IconButton size='small' sx={{ color: 'text.secondary', ml: 1 }}>
<i className='tabler-help-circle' style={{ fontSize: '16px' }} />
</IconButton>
</Typography>
<Controller
name='referensi'
control={control}
render={({ field }) => <CustomTextField {...field} fullWidth placeholder='Referensi' />}
/>
</Grid>
<Grid size={6}>
<Typography variant='body2' className='mb-2'>
Tag
<IconButton size='small' sx={{ color: 'text.secondary', ml: 1 }}>
<i className='tabler-help-circle' style={{ fontSize: '16px' }} />
</IconButton>
</Typography>
<Controller
name='tag'
control={control}
render={({ field }) => (
<CustomTextField {...field} select fullWidth placeholder='Pilih Tag'>
<MenuItem value=''>Pilih Tag</MenuItem>
<MenuItem value='Urgent'>Urgent</MenuItem>
<MenuItem value='Regular'>Regular</MenuItem>
<MenuItem value='Maintenance'>Maintenance</MenuItem>
</CustomTextField>
)}
/>
</Grid>
</Grid>
{/* Stock Details Section */}
<Box sx={{ mt: 4 }}>
<Grid container spacing={2} alignItems='end'>
{/* Qty Tercatat */}
<Grid size={2}>
<Typography variant='body2' className='mb-2 font-medium'>
Qty Tercatat
</Typography>
<Controller
name='qtyTercatat'
control={control}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
type='number'
onChange={e => field.onChange(parseInt(e.target.value) || 0)}
/>
)}
/>
</Grid>
{/* Satuan */}
<Grid size={2}>
<Typography variant='body2' className='mb-2 font-medium'>
Satuan
</Typography>
<Controller
name='satuan'
control={control}
render={({ field }) => (
<CustomTextField {...field} select fullWidth>
<MenuItem value='Pcs'>Pcs</MenuItem>
<MenuItem value='Kg'>Kg</MenuItem>
<MenuItem value='Liter'>Liter</MenuItem>
<MenuItem value='Box'>Box</MenuItem>
</CustomTextField>
)}
/>
</Grid>
{/* Qty Aktual */}
<Grid size={2}>
<Typography variant='body2' className='mb-2 font-medium'>
Qty Aktual
</Typography>
<Controller
name='qtyAktual'
control={control}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
type='number'
onChange={e => field.onChange(parseInt(e.target.value) || 0)}
/>
)}
/>
</Grid>
{/* Selisih */}
<Grid size={2}>
<Typography variant='body2' className='mb-2 font-medium'>
Selisih
</Typography>
<Controller
name='selisih'
control={control}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
type='number'
disabled
sx={{
'& .MuiInputBase-input.Mui-disabled': {
WebkitTextFillColor: 'text.primary',
backgroundColor: 'grey.100'
}
}}
/>
)}
/>
</Grid>
{/* Harga Rata-rata */}
<Grid size={4}>
<Typography variant='body2' className='mb-2 font-medium'>
Harga Rata-rata
</Typography>
<Controller
name='hargaRataRata'
control={control}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
value={formatNumber(field.value)}
onChange={e => field.onChange(parseNumber(e.target.value))}
/>
)}
/>
</Grid>
</Grid>
</Box>
</div>
</form>
</Box>
{/* Sticky Footer */}
<Box
sx={{
position: 'sticky',
bottom: 0,
zIndex: 10,
backgroundColor: 'background.paper',
borderTop: 1,
borderColor: 'divider',
p: 3
}}
>
<div className='flex items-center gap-4'>
<Button variant='contained' type='submit' form='stock-adjustment-form'>
Simpan
</Button>
<Button variant='tonal' color='error' onClick={() => handleReset()}>
Batal
</Button>
</div>
</Box>
</Drawer>
)
}
export default IngredientDetailStockAdjustmentDrawer

View File

@ -1,17 +1,67 @@
'use client'
import { useState } from 'react'
import Grid from '@mui/material/Grid2'
import IngredientDetailInfo from './IngredientDetailInfo'
import IngredientDetailUnit from './IngredientDetailUnit'
import IngredientDetailStockAdjustmentDrawer from './IngredientDetailStockAdjustmentDrawer' // Sesuaikan dengan path file Anda
import { Button } from '@mui/material'
const IngredientDetail = () => {
// State untuk mengontrol stock adjustment drawer
const [openStockAdjustmentDrawer, setOpenStockAdjustmentDrawer] = useState(false)
// Function untuk membuka stock adjustment drawer
const handleOpenStockAdjustmentDrawer = () => {
setOpenStockAdjustmentDrawer(true)
}
// Function untuk menutup stock adjustment drawer
const handleCloseStockAdjustmentDrawer = () => {
setOpenStockAdjustmentDrawer(false)
}
// Function untuk handle data dari stock adjustment drawer
const handleSetStockAdjustmentData = (data: any) => {
console.log('Stock adjustment data received:', data)
// Anda bisa menambahkan logic untuk mengupdate state atau API call di sini
// Misalnya: update stock data, refresh ingredient data, dll.
}
return (
<Grid container spacing={6}>
<Grid size={{ xs: 12, lg: 8, md: 7 }}>
<IngredientDetailInfo />
<>
<Grid container spacing={6}>
<Grid size={{ xs: 12, lg: 8, md: 7 }}>
<IngredientDetailInfo />
</Grid>
<Grid size={{ xs: 12, lg: 4, md: 5 }}>
<Button
variant='contained'
fullWidth
startIcon={<span style={{ fontSize: '18px' }}>+</span>}
onClick={handleOpenStockAdjustmentDrawer}
className='mb-4'
sx={{
py: 1.5,
borderRadius: 2,
textTransform: 'none',
fontSize: '16px',
mb: 3 // Menambahkan margin bottom untuk spacing
}}
>
Penyesuaian Stok
</Button>
<IngredientDetailUnit />
</Grid>
</Grid>
<Grid size={{ xs: 12, lg: 4, md: 5 }}>
<IngredientDetailUnit />
</Grid>
</Grid>
{/* Stock Adjustment Drawer */}
<IngredientDetailStockAdjustmentDrawer
open={openStockAdjustmentDrawer}
handleClose={handleCloseStockAdjustmentDrawer}
setData={handleSetStockAdjustmentData}
/>
</>
)
}