502 lines
16 KiB
TypeScript
Raw Normal View History

2025-09-17 20:21:28 +07:00
// React Imports
import { useState, useEffect } 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 Divider from '@mui/material/Divider'
import Grid from '@mui/material/Grid2'
import Box from '@mui/material/Box'
import Switch from '@mui/material/Switch'
import FormControlLabel from '@mui/material/FormControlLabel'
import Chip from '@mui/material/Chip'
import InputAdornment from '@mui/material/InputAdornment'
2025-09-17 20:54:54 +07:00
import Select from '@mui/material/Select'
import FormControl from '@mui/material/FormControl'
import InputLabel from '@mui/material/InputLabel'
2025-09-17 20:21:28 +07:00
// Third-party Imports
2025-09-17 20:54:54 +07:00
import { useForm, Controller } from 'react-hook-form'
2025-09-17 20:21:28 +07:00
// Component Imports
import CustomTextField from '@core/components/mui/TextField'
2025-09-17 20:54:54 +07:00
import { Tier, TierRequest } from '@/types/services/tier'
import { useTiersMutation } from '@/services/mutations/tier'
2025-09-17 20:21:28 +07:00
type Props = {
open: boolean
handleClose: () => void
2025-09-17 20:54:54 +07:00
data?: Tier // Data tier untuk edit (jika ada)
}
// Benefit item type
type BenefitItem = {
key: string
value: any
type: 'boolean' | 'number' | 'string'
2025-09-17 20:21:28 +07:00
}
type FormValidateType = {
name: string
min_points: number
2025-09-17 20:54:54 +07:00
benefits: BenefitItem[]
newBenefitKey: string
newBenefitValue: string
newBenefitType: 'boolean' | 'number' | 'string'
2025-09-17 20:21:28 +07:00
}
// Initial form data
const initialData: FormValidateType = {
name: '',
min_points: 0,
benefits: [],
2025-09-17 20:54:54 +07:00
newBenefitKey: '',
newBenefitValue: '',
newBenefitType: 'boolean'
2025-09-17 20:21:28 +07:00
}
const AddEditTierDrawer = (props: Props) => {
// Props
const { open, handleClose, data } = props
// States
const [showMore, setShowMore] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
2025-09-17 20:54:54 +07:00
const { createTier, updateTier } = useTiersMutation()
2025-09-17 20:21:28 +07:00
// Determine if this is edit mode
const isEditMode = Boolean(data?.id)
// Hooks
const {
control,
reset: resetForm,
handleSubmit,
watch,
setValue,
formState: { errors }
} = useForm<FormValidateType>({
defaultValues: initialData
})
const watchedBenefits = watch('benefits')
2025-09-17 20:54:54 +07:00
const watchedNewBenefitKey = watch('newBenefitKey')
const watchedNewBenefitValue = watch('newBenefitValue')
const watchedNewBenefitType = watch('newBenefitType')
2025-09-17 20:21:28 +07:00
2025-09-17 20:54:54 +07:00
// Helper function to convert benefits object to BenefitItem array
const convertBenefitsToArray = (benefits: Record<string, any>): BenefitItem[] => {
2025-09-17 20:21:28 +07:00
if (!benefits) return []
2025-09-17 20:54:54 +07:00
return Object.entries(benefits).map(([key, value]) => ({
key,
value,
type: typeof value === 'boolean' ? 'boolean' : typeof value === 'number' ? 'number' : 'string'
}))
2025-09-17 20:21:28 +07:00
}
2025-09-17 20:54:54 +07:00
// Helper function to convert BenefitItem array to benefits object
const convertBenefitsToObject = (benefits: BenefitItem[]): Record<string, any> => {
2025-09-17 20:21:28 +07:00
const benefitsObj: Record<string, any> = {}
benefits.forEach(benefit => {
2025-09-17 20:54:54 +07:00
let value = benefit.value
// Convert string values to appropriate types
if (benefit.type === 'boolean') {
value = value === true || value === 'true' || value === 'yes'
} else if (benefit.type === 'number') {
value = Number(value)
}
benefitsObj[benefit.key] = value
2025-09-17 20:21:28 +07:00
})
return benefitsObj
}
2025-09-17 20:54:54 +07:00
// Helper function to format benefit display
const formatBenefitDisplay = (item: BenefitItem): string => {
const readableKey = item.key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())
if (item.type === 'boolean') {
return `${readableKey}: ${item.value ? 'Ya' : 'Tidak'}`
} else if (item.type === 'number') {
if (item.key.includes('multiplier')) {
return `${readableKey}: ${item.value}x`
} else if (item.key.includes('discount') || item.key.includes('bonus')) {
return `${readableKey}: ${item.value}%`
}
return `${readableKey}: ${item.value}`
}
return `${readableKey}: ${item.value}`
}
2025-09-17 20:21:28 +07:00
// Effect to populate form when editing
useEffect(() => {
if (isEditMode && data) {
// Convert benefits object to array for form handling
const benefitsArray = convertBenefitsToArray(data.benefits)
// Populate form with existing data
const formData: FormValidateType = {
name: data.name || '',
min_points: data.min_points || 0,
benefits: benefitsArray,
2025-09-17 20:54:54 +07:00
newBenefitKey: '',
newBenefitValue: '',
newBenefitType: 'boolean'
2025-09-17 20:21:28 +07:00
}
resetForm(formData)
setShowMore(true) // Always show more for edit mode
} else {
// Reset to initial data for add mode
resetForm(initialData)
setShowMore(false)
}
}, [data, isEditMode, resetForm])
const handleAddBenefit = () => {
2025-09-17 20:54:54 +07:00
const key = watchedNewBenefitKey.trim()
const value = watchedNewBenefitValue.trim()
const type = watchedNewBenefitType
if (key && value) {
// Check if key already exists
const existingKeys = watchedBenefits.map(b => b.key)
if (existingKeys.includes(key)) {
alert('Key benefit sudah ada!')
return
}
let processedValue: any = value
if (type === 'boolean') {
processedValue = value === 'true' || value === 'yes' || value === '1'
} else if (type === 'number') {
processedValue = Number(value)
if (isNaN(processedValue)) {
alert('Nilai harus berupa angka!')
return
}
}
const newBenefit: BenefitItem = {
key,
value: processedValue,
type
}
2025-09-17 20:21:28 +07:00
const currentBenefits = watchedBenefits || []
2025-09-17 20:54:54 +07:00
setValue('benefits', [...currentBenefits, newBenefit])
setValue('newBenefitKey', '')
setValue('newBenefitValue', '')
setValue('newBenefitType', 'boolean')
2025-09-17 20:21:28 +07:00
}
}
const handleRemoveBenefit = (index: number) => {
const currentBenefits = watchedBenefits || []
const newBenefits = currentBenefits.filter((_, i) => i !== index)
setValue('benefits', newBenefits)
}
const handleKeyPress = (event: React.KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault()
handleAddBenefit()
}
}
const handleFormSubmit = async (formData: FormValidateType) => {
try {
setIsSubmitting(true)
// Convert benefits array back to object format
const benefitsObj = convertBenefitsToObject(formData.benefits)
// Create TierRequest object
const tierRequest: TierRequest = {
name: formData.name,
min_points: formData.min_points,
benefits: benefitsObj
}
2025-09-17 20:54:54 +07:00
console.log('Submitting tier data:', tierRequest)
2025-09-17 20:21:28 +07:00
if (isEditMode && data?.id) {
// Update existing tier
updateTier.mutate(
{ id: data.id, payload: tierRequest },
{
onSuccess: () => {
handleReset()
handleClose()
}
}
)
} else {
// Create new tier
createTier.mutate(tierRequest, {
onSuccess: () => {
handleReset()
handleClose()
}
})
}
} catch (error) {
console.error('Error submitting tier:', error)
// Handle error (show toast, etc.)
} finally {
setIsSubmitting(false)
}
}
const handleReset = () => {
handleClose()
resetForm(initialData)
setShowMore(false)
}
const formatNumber = (value: number) => {
return new Intl.NumberFormat('id-ID').format(value)
}
return (
<Drawer
open={open}
anchor='right'
variant='temporary'
onClose={handleReset}
ModalProps={{ keepMounted: true }}
sx={{
'& .MuiDrawer-paper': {
width: { xs: 300, sm: 400 },
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'>{isEditMode ? 'Edit Tier' : 'Tambah Tier Baru'}</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='tier-form' onSubmit={handleSubmit(handleFormSubmit)}>
<div className='flex flex-col gap-6 p-6'>
{/* Nama Tier */}
<div>
<Typography variant='body2' className='mb-2'>
Nama Tier <span className='text-red-500'>*</span>
</Typography>
<Controller
name='name'
control={control}
rules={{ required: 'Nama tier wajib diisi' }}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder='Masukkan nama tier (contoh: Bronze, Silver, Gold)'
error={!!errors.name}
helperText={errors.name?.message}
/>
)}
/>
</div>
{/* Minimum Points */}
<div>
<Typography variant='body2' className='mb-2'>
Minimum Poin <span className='text-red-500'>*</span>
</Typography>
<Controller
name='min_points'
control={control}
rules={{
required: 'Minimum poin wajib diisi',
min: {
value: 0,
message: 'Minimum poin tidak boleh negatif'
}
}}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
type='number'
placeholder='0'
error={!!errors.min_points}
helperText={
errors.min_points?.message || (field.value > 0 ? `${formatNumber(field.value)} poin` : '')
}
InputProps={{
endAdornment: <InputAdornment position='end'>poin</InputAdornment>
}}
onChange={e => field.onChange(Number(e.target.value))}
/>
)}
/>
</div>
{/* Benefits */}
<div>
<Typography variant='body2' className='mb-2'>
Manfaat Tier <span className='text-red-500'>*</span>
</Typography>
{/* Display current benefits */}
{watchedBenefits && watchedBenefits.length > 0 && (
2025-09-17 20:54:54 +07:00
<div className='flex flex-col gap-2 mb-3'>
2025-09-17 20:21:28 +07:00
{watchedBenefits.map((benefit, index) => (
<Chip
key={index}
2025-09-17 20:54:54 +07:00
label={formatBenefitDisplay(benefit)}
2025-09-17 20:21:28 +07:00
onDelete={() => handleRemoveBenefit(index)}
color='primary'
variant='outlined'
size='small'
2025-09-17 20:54:54 +07:00
sx={{
justifyContent: 'space-between',
'& .MuiChip-label': {
overflow: 'visible',
textOverflow: 'unset',
whiteSpace: 'normal'
}
}}
2025-09-17 20:21:28 +07:00
/>
))}
</div>
)}
2025-09-17 20:54:54 +07:00
{/* Add new benefit - Key */}
<div className='mb-3'>
<Controller
name='newBenefitKey'
control={control}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder='Key benefit (contoh: birthday_bonus, point_multiplier)'
label='Key Benefit'
size='small'
/>
)}
/>
</div>
{/* Type selector */}
<div className='mb-3'>
<Controller
name='newBenefitType'
control={control}
render={({ field }) => (
<FormControl fullWidth size='small'>
<InputLabel>Tipe Value</InputLabel>
<Select {...field} label='Tipe Value'>
<MenuItem value='boolean'>Boolean (Ya/Tidak)</MenuItem>
<MenuItem value='number'>Number (Angka)</MenuItem>
<MenuItem value='string'>String (Teks)</MenuItem>
</Select>
</FormControl>
)}
/>
</div>
{/* Add new benefit - Value */}
<div className='mb-3'>
<Controller
name='newBenefitValue'
control={control}
render={({ field }) => (
<CustomTextField
{...field}
fullWidth
placeholder={
watchedNewBenefitType === 'boolean'
? 'true/false, yes/no, 1/0'
: watchedNewBenefitType === 'number'
? 'Contoh: 1.5, 10, 5'
: 'Contoh: Premium access, VIP status'
}
label='Value Benefit'
size='small'
onKeyPress={handleKeyPress}
InputProps={{
endAdornment: (
<InputAdornment position='end'>
<Button
size='small'
onClick={handleAddBenefit}
disabled={!watchedNewBenefitKey?.trim() || !watchedNewBenefitValue?.trim()}
>
Tambah
</Button>
</InputAdornment>
)
}}
/>
)}
/>
</div>
2025-09-17 20:21:28 +07:00
{(!watchedBenefits || watchedBenefits.length === 0) && (
<Typography variant='caption' color='error'>
Minimal satu manfaat harus ditambahkan
</Typography>
)}
</div>
</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='tier-form'
disabled={isSubmitting || !watchedBenefits || watchedBenefits.length === 0}
2025-09-17 20:54:54 +07:00
startIcon={isSubmitting ? <i className='tabler-loader animate-spin' /> : null}
2025-09-17 20:21:28 +07:00
>
{isSubmitting ? (isEditMode ? 'Mengupdate...' : 'Menyimpan...') : isEditMode ? 'Update' : 'Simpan'}
</Button>
<Button variant='outlined' color='error' onClick={handleReset} disabled={isSubmitting}>
Batal
</Button>
</div>
</Box>
</Drawer>
)
}
export default AddEditTierDrawer