diff --git a/src/services/api.ts b/src/services/api.ts index 5783ecf..b0fb547 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -5,8 +5,9 @@ const getToken = () => { return localStorage.getItem('authToken') } +// baseURL: 'https://api-pos.apskel.id/api/v1', export const api = axios.create({ - baseURL: 'https://api-pos.apskel.id/api/v1', + baseURL: 'http://127.0.0.1:4000/api/v1', headers: { 'Content-Type': 'application/json' }, diff --git a/src/types/services/campaign.ts b/src/types/services/campaign.ts index e5657b0..8d9481c 100644 --- a/src/types/services/campaign.ts +++ b/src/types/services/campaign.ts @@ -14,8 +14,9 @@ export interface Campaign { is_active: boolean show_on_app: boolean position: number - metadata?: Record - rules?: CampaignRule[] + metadata?: { + banner_url?: string + } created_at: string // ISO string updated_at: string // ISO string } @@ -43,8 +44,9 @@ export interface CampaignRequest { is_active: boolean show_on_app: boolean position: number - metadata?: Record - rules: CampaignRuleRequest[] + metadata?: { + banner_url?: string + } } export interface CampaignRuleRequest { diff --git a/src/utils/transform.ts b/src/utils/transform.ts index 9f1e131..aee93f4 100644 --- a/src/utils/transform.ts +++ b/src/utils/transform.ts @@ -43,6 +43,15 @@ export const formatDateDDMMYYYY = (dateString: Date | string) => { return `${day}-${month}-${year}` } +export const formatDateYYYYMMDD = (dateString: Date | string) => { + const date = new Date(dateString) + date.setHours(0, 0, 0, 0) + const day = String(date.getDate()).padStart(2, '0') + const month = String(date.getMonth() + 1).padStart(2, '0') + const year = date.getFullYear() + return `${year}-${month}-${day}` +} + export const formatForInputDate = (dateString: Date | string) => { const date = new Date(dateString) const day = String(date.getDate()).padStart(2, '0') @@ -51,7 +60,6 @@ export const formatForInputDate = (dateString: Date | string) => { return `${year}-${month}-${day}` } - export const formatDatetime = (dateString: string | number | Date) => { const date = new Date(dateString) diff --git a/src/views/apps/marketing/campaign/AddEditCampaignDrawer.tsx b/src/views/apps/marketing/campaign/AddEditCampaignDrawer.tsx index 477ba94..f385a00 100644 --- a/src/views/apps/marketing/campaign/AddEditCampaignDrawer.tsx +++ b/src/views/apps/marketing/campaign/AddEditCampaignDrawer.tsx @@ -7,49 +7,22 @@ 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' // Third-party Imports -import { useForm, Controller, useFieldArray } from 'react-hook-form' +import { useForm, Controller } from 'react-hook-form' // Component Imports import CustomTextField from '@core/components/mui/TextField' +import ImageUpload from '@/components/ImageUpload' // Types -import { Campaign } from '@/types/services/campaign' +import { Campaign, CampaignRequest, CampaignType } from '@/types/services/campaign' import { useCampaignsMutation } from '@/services/mutations/campaign' - -// Updated Type Definitions -export type CampaignType = 'REWARD' | 'POINTS' | 'TOKENS' | 'MIXED' -export type RuleType = 'TIER' | 'SPEND' | 'PRODUCT' | 'CATEGORY' | 'DAY' | 'LOCATION' -export type RewardType = 'POINTS' | 'TOKENS' | 'REWARD' - -export interface CampaignRequest { - name: string - description?: string - type: CampaignType - start_date: string // ISO string - end_date: string // ISO string - is_active: boolean - show_on_app: boolean - position: number - metadata?: Record - rules: CampaignRuleRequest[] -} - -export interface CampaignRuleRequest { - rule_type: RuleType - condition_value?: string - reward_type: RewardType - reward_value?: number - reward_subtype?: string -} +import { useFilesMutation } from '@/services/mutations/files' +import { formatDateYYYYMMDD } from '@/utils/transform' type Props = { open: boolean @@ -66,14 +39,7 @@ type FormValidateType = { is_active: boolean show_on_app: boolean position: number - // Rules array - rules: { - rule_type: RuleType - condition_value: string - reward_type: RewardType - reward_value: number - reward_subtype: string - }[] + banner_url: string } // Initial form data @@ -86,16 +52,7 @@ const initialData: FormValidateType = { is_active: true, show_on_app: true, position: 1, - // Initial rule - rules: [ - { - rule_type: 'SPEND', - condition_value: '', - reward_type: 'POINTS', - reward_value: 0, - reward_subtype: '' - } - ] + banner_url: '' } const AddEditCampaignDrawer = (props: Props) => { @@ -105,8 +62,10 @@ const AddEditCampaignDrawer = (props: Props) => { // States const [showMore, setShowMore] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) + const [bannerUrl, setBannerUrl] = useState('') const { createCampaign, updateCampaign } = useCampaignsMutation() + const { mutate: uploadFile, isPending: isUploadPending } = useFilesMutation().uploadFile // Determine if this is edit mode const isEditMode = Boolean(data?.id) @@ -123,12 +82,6 @@ const AddEditCampaignDrawer = (props: Props) => { defaultValues: initialData }) - // Field array for rules - const { fields, append, remove } = useFieldArray({ - control, - name: 'rules' - }) - const watchedStartDate = watch('start_date') const watchedEndDate = watch('end_date') @@ -144,29 +97,16 @@ const AddEditCampaignDrawer = (props: Props) => { is_active: data.is_active ?? true, show_on_app: data.show_on_app ?? true, position: data.position || 1, - // Map existing rules - rules: data.rules?.map(rule => ({ - rule_type: rule.rule_type, - condition_value: rule.condition_value || '', - reward_type: rule.reward_type, - reward_value: rule.reward_value || 0, - reward_subtype: rule.reward_subtype || '' - })) || [ - { - rule_type: 'SPEND', - condition_value: '', - reward_type: 'POINTS', - reward_value: 0, - reward_subtype: '' - } - ] + banner_url: data.metadata?.banner_url || '' } resetForm(formData) + setBannerUrl(data.metadata?.banner_url || '') setShowMore(true) // Always show more for edit mode } else { // Reset to initial data for add mode resetForm(initialData) + setBannerUrl('') setShowMore(false) } }, [data, isEditMode, resetForm]) @@ -175,20 +115,10 @@ const AddEditCampaignDrawer = (props: Props) => { try { setIsSubmitting(true) - // Create rules array - const rulesRequest: CampaignRuleRequest[] = formData.rules.map(rule => ({ - rule_type: rule.rule_type, - condition_value: rule.condition_value || undefined, - reward_type: rule.reward_type, - reward_value: rule.reward_value || undefined, - reward_subtype: rule.reward_subtype || undefined - })) - - // Create metadata from rules if needed - const metadata: Record = {} - const spendRule = formData.rules.find(rule => rule.rule_type === 'SPEND') - if (spendRule?.condition_value) { - metadata.minPurchase = parseInt(spendRule.condition_value) + // Create metadata object + const metadata: { banner_url?: string } = {} + if (bannerUrl.trim()) { + metadata.banner_url = bannerUrl.trim() } // Create CampaignRequest object @@ -196,13 +126,12 @@ const AddEditCampaignDrawer = (props: Props) => { name: formData.name, description: formData.description || undefined, type: formData.type, - start_date: new Date(formData.start_date).toISOString(), - end_date: new Date(formData.end_date).toISOString(), + start_date: formatDateYYYYMMDD(formData.start_date), + end_date: formatDateYYYYMMDD(formData.end_date), is_active: formData.is_active, show_on_app: formData.show_on_app, position: formData.position, - metadata: Object.keys(metadata).length > 0 ? metadata : undefined, - rules: rulesRequest + metadata: Object.keys(metadata).length > 0 ? metadata : undefined } if (isEditMode && data?.id) { @@ -236,70 +165,29 @@ const AddEditCampaignDrawer = (props: Props) => { const handleReset = () => { handleClose() resetForm(initialData) + setBannerUrl('') setShowMore(false) } - const formatCurrency = (value: number) => { - return new Intl.NumberFormat('id-ID', { - style: 'currency', - currency: 'IDR', - minimumFractionDigits: 0 - }).format(value) - } + // Handle file upload + const handleBannerUpload = async (file: File): Promise => { + return new Promise((resolve, reject) => { + const formData = new FormData() + formData.append('file', file) + formData.append('file_type', 'image') + formData.append('description', 'campaign banner upload') - const getRewardTypeLabel = (type: RewardType) => { - switch (type) { - case 'POINTS': - return 'Poin' - case 'TOKENS': - return 'Token' - case 'REWARD': - return 'Reward' - default: - return type - } - } - - const getRewardValuePlaceholder = (type: RewardType) => { - switch (type) { - case 'POINTS': - return 'Jumlah poin yang diberikan' - case 'TOKENS': - return 'Jumlah token yang diberikan' - case 'REWARD': - return 'Nilai reward' - default: - return 'Nilai reward' - } - } - - const getConditionValuePlaceholder = (ruleType: RuleType) => { - switch (ruleType) { - case 'SPEND': - return 'Minimum pembelian (Rupiah)' - case 'TIER': - return 'Tier pelanggan (misal: GOLD, SILVER)' - case 'PRODUCT': - return 'ID atau nama produk' - case 'CATEGORY': - return 'Kategori produk' - case 'DAY': - return 'Hari dalam seminggu (misal: MONDAY)' - case 'LOCATION': - return 'Lokasi atau kota' - default: - return 'Nilai kondisi' - } - } - - const getConditionValueInputProps = (ruleType: RuleType) => { - if (ruleType === 'SPEND') { - return { - startAdornment: Rp, - type: 'number' as const - } - } - return { type: 'text' as const } + uploadFile(formData, { + onSuccess: r => { + setBannerUrl(r.file_url) + setValue('banner_url', r.file_url) // Update form value + resolve(r.id) + }, + onError: er => { + reject(er) + } + }) + }) } return ( @@ -402,202 +290,6 @@ const AddEditCampaignDrawer = (props: Props) => { /> - {/* Rules Section */} -
- - Aturan Kampanye - - - - {fields.map((field, index) => ( - - - - Aturan {index + 1} - - {fields.length > 1 && ( - remove(index)}> - - - )} - - -
- {/* Rule Type */} -
- - Tipe Aturan * - - ( - - Minimum Pembelian - Tier Pelanggan - Produk Tertentu - Kategori Produk - Hari Tertentu - Lokasi Tertentu - - )} - /> -
- - {/* Condition Value */} -
- - Nilai Kondisi * - - { - const ruleType = watch(`rules.${index}.rule_type`) - return ( - - ) - }} - /> -
- - {/* Reward Type */} -
- - Jenis Reward * - - ( - - -
- - Points -
-
- -
- - Tokens -
-
- -
- - Reward -
-
-
- )} - /> -
- - {/* Reward Value */} -
- - Nilai Reward * - - { - const rewardType = watch(`rules.${index}.reward_type`) - return ( - Poin - ) : rewardType === 'TOKENS' ? ( - Token - ) : undefined - }} - onChange={e => field.onChange(Number(e.target.value))} - /> - ) - }} - /> -
- - {/* Reward Subtype (jika reward type adalah REWARD) */} - {watch(`rules.${index}.reward_type`) === 'REWARD' && ( -
- - Sub-tipe Reward - - ( - - Diskon Persentase - Diskon Nominal - Cashback - Gratis Ongkir - - )} - /> -
- )} -
-
- ))} -
- {/* Tanggal Mulai */}
@@ -723,6 +415,23 @@ const AddEditCampaignDrawer = (props: Props) => { />
+ {/* Banner Upload */} +
+ + Banner Kampanye + + + + Format: JPG, PNG. Ukuran maksimal: 1MB + +
+ {/* Position */}