2025-09-18 01:21:51 +07:00

508 lines
17 KiB
TypeScript

// 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'
import Select from '@mui/material/Select'
import FormControl from '@mui/material/FormControl'
import InputLabel from '@mui/material/InputLabel'
// Third-party Imports
import { useForm, Controller } from 'react-hook-form'
// Component Imports
import CustomTextField from '@core/components/mui/TextField'
import { Tier, TierRequest } from '@/types/services/tier'
import { useTiersMutation } from '@/services/mutations/tier'
type Props = {
open: boolean
handleClose: () => void
data?: Tier // Data tier untuk edit (jika ada)
}
// Static benefit keys with their configurations
const STATIC_BENEFIT_KEYS = {
birthday_bonus: {
label: 'Birthday Bonus',
type: 'boolean' as const,
description: 'Bonus ulang tahun khusus member'
},
exclusive_discounts: {
label: 'Exclusive Discounts',
type: 'boolean' as const,
description: 'Akses diskon eksklusif'
},
point_multiplier: {
label: 'Point Multiplier',
type: 'number' as const,
description: 'Pengali poin (contoh: 1.1 = +10%)',
suffix: 'x'
},
priority_support: {
label: 'Priority Support',
type: 'boolean' as const,
description: 'Dukungan pelanggan prioritas'
},
special_discount: {
label: 'Special Discount',
type: 'number' as const,
description: 'Diskon khusus dalam persen',
suffix: '%'
}
} as const
// Benefit item type
type BenefitItem = {
key: keyof typeof STATIC_BENEFIT_KEYS
value: any
type: 'boolean' | 'number' | 'string'
}
type FormValidateType = {
name: string
min_points: number
benefits: BenefitItem[]
}
// Initial form data
const initialData: FormValidateType = {
name: '',
min_points: 0,
benefits: []
}
const AddEditTierDrawer = (props: Props) => {
// Props
const { open, handleClose, data } = props
// States
const [showMore, setShowMore] = useState(false)
const [isSubmitting, setIsSubmitting] = useState(false)
const { createTier, updateTier } = useTiersMutation()
// 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')
// Helper function to convert benefits object to BenefitItem array
const convertBenefitsToArray = (benefits: Record<string, any>): BenefitItem[] => {
if (!benefits) return []
return Object.entries(benefits)
.filter(([key]) => key in STATIC_BENEFIT_KEYS)
.map(([key, value]) => ({
key: key as keyof typeof STATIC_BENEFIT_KEYS,
value,
type: STATIC_BENEFIT_KEYS[key as keyof typeof STATIC_BENEFIT_KEYS].type
}))
}
// Helper function to convert BenefitItem array to benefits object
const convertBenefitsToObject = (benefits: BenefitItem[]): Record<string, any> => {
const benefitsObj: Record<string, any> = {}
benefits.forEach(benefit => {
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
})
return benefitsObj
}
// Helper function to format benefit display
const formatBenefitDisplay = (item: BenefitItem): string => {
const config = STATIC_BENEFIT_KEYS[item.key]
if (item.type === 'boolean') {
return `${config.label}: ${item.value ? 'Ya' : 'Tidak'}`
} else if (item.type === 'number') {
const suffix = config.suffix || ''
return `${config.label}: ${item.value}${suffix}`
}
return `${config.label}: ${item.value}`
}
// Get available benefit keys (not already added)
const getAvailableBenefitKeys = () => {
const usedKeys = watchedBenefits?.map(b => b.key) || []
return Object.entries(STATIC_BENEFIT_KEYS)
.filter(([key]) => !usedKeys.includes(key as keyof typeof STATIC_BENEFIT_KEYS))
.map(([key, config]) => ({
key: key as keyof typeof STATIC_BENEFIT_KEYS,
...config
}))
}
// 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
}
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 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
}
console.log('Submitting tier data:', tierRequest)
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)
}
// Get placeholder and validation info based on selected benefit key
const getBenefitInputInfo = () => {
return { placeholder: 'Tidak diperlukan lagi', type: 'text' }
}
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-3'>
Manfaat Tier <span className='text-red-500'>*</span>
</Typography>
{/* All Benefits in Horizontal Layout */}
<div className='space-y-4'>
{Object.entries(STATIC_BENEFIT_KEYS).map(([key, config]) => {
const benefitKey = key as keyof typeof STATIC_BENEFIT_KEYS
const existingBenefit = watchedBenefits?.find(b => b.key === benefitKey)
const isActive = Boolean(existingBenefit)
return (
<div key={benefitKey} className='border rounded-lg p-3'>
<div className='flex items-center justify-between mb-2'>
<div className='flex-1'>
<Typography variant='body2' className='font-medium'>
{config.label}
</Typography>
<Typography variant='caption' color='text.secondary'>
{config.description}
</Typography>
</div>
<FormControlLabel
control={
<Switch
checked={isActive}
onChange={e => {
if (e.target.checked) {
// Add default benefit
const defaultValue =
config.type === 'boolean'
? true
: config.type === 'number'
? benefitKey === 'point_multiplier'
? 1.1
: benefitKey === 'special_discount'
? 5
: 1
: ''
const newBenefit: BenefitItem = {
key: benefitKey,
value: defaultValue,
type: config.type
}
const currentBenefits = watchedBenefits || []
setValue('benefits', [...currentBenefits, newBenefit])
} else {
// Remove benefit
const currentBenefits = watchedBenefits || []
const newBenefits = currentBenefits.filter(b => b.key !== benefitKey)
setValue('benefits', newBenefits)
}
}}
size='small'
/>
}
label=''
sx={{ margin: 0 }}
/>
</div>
{/* Value Input - Only show when active */}
{isActive && (
<div className='mt-2'>
{config.type === 'boolean' ? (
<FormControl size='small' fullWidth>
<Select
value={existingBenefit?.value ? 'true' : 'false'}
onChange={e => {
const currentBenefits = watchedBenefits || []
const updatedBenefits = currentBenefits.map(b =>
b.key === benefitKey ? { ...b, value: e.target.value === 'true' } : b
)
setValue('benefits', updatedBenefits)
}}
>
<MenuItem value='true'>Ya</MenuItem>
<MenuItem value='false'>Tidak</MenuItem>
</Select>
</FormControl>
) : (
<CustomTextField
fullWidth
size='small'
type='number'
value={existingBenefit?.value || ''}
onChange={e => {
const newValue = Number(e.target.value)
if (!isNaN(newValue)) {
const currentBenefits = watchedBenefits || []
const updatedBenefits = currentBenefits.map(b =>
b.key === benefitKey ? { ...b, value: newValue } : b
)
setValue('benefits', updatedBenefits)
}
}}
placeholder={
benefitKey === 'point_multiplier'
? 'Contoh: 1.1, 1.5, 2.0'
: benefitKey === 'special_discount'
? 'Contoh: 5, 10, 15'
: 'Masukkan angka'
}
InputProps={{
endAdornment: config.suffix && (
<InputAdornment position='end'>{config.suffix}</InputAdornment>
),
inputProps: {
step: benefitKey === 'point_multiplier' ? '0.1' : '1',
min: benefitKey === 'point_multiplier' ? '0.1' : '0',
max: benefitKey === 'special_discount' ? '100' : undefined
}
}}
/>
)}
</div>
)}
</div>
)
})}
</div>
{(!watchedBenefits || watchedBenefits.length === 0) && (
<Typography variant='caption' color='error' className='mt-2 block'>
Minimal satu manfaat harus diaktifkan
</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}
startIcon={isSubmitting ? <i className='tabler-loader animate-spin' /> : null}
>
{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