From 3d1b6eee39280af4965c3ef1ca81b2d4466f4e43 Mon Sep 17 00:00:00 2001 From: efrilm Date: Wed, 17 Sep 2025 01:45:05 +0700 Subject: [PATCH] Campaign --- .../apps/marketing/campaign/page.tsx | 7 + .../layout/vertical/VerticalMenu.tsx | 1 + src/data/dictionaries/en.json | 3 +- src/data/dictionaries/id.json | 3 +- src/types/services/campaign.ts | 13 + .../campaign/AddEditCampaignDrawer.tsx | 576 +++++++++++++++ .../marketing/campaign/CampaignListTable.tsx | 668 ++++++++++++++++++ src/views/apps/marketing/campaign/index.tsx | 17 + 8 files changed, 1286 insertions(+), 2 deletions(-) create mode 100644 src/app/[lang]/(dashboard)/(private)/apps/marketing/campaign/page.tsx create mode 100644 src/types/services/campaign.ts create mode 100644 src/views/apps/marketing/campaign/AddEditCampaignDrawer.tsx create mode 100644 src/views/apps/marketing/campaign/CampaignListTable.tsx create mode 100644 src/views/apps/marketing/campaign/index.tsx diff --git a/src/app/[lang]/(dashboard)/(private)/apps/marketing/campaign/page.tsx b/src/app/[lang]/(dashboard)/(private)/apps/marketing/campaign/page.tsx new file mode 100644 index 0000000..9e66987 --- /dev/null +++ b/src/app/[lang]/(dashboard)/(private)/apps/marketing/campaign/page.tsx @@ -0,0 +1,7 @@ +import CampaignListTable from '@/views/apps/marketing/campaign/CampaignListTable' + +const CampaignPage = () => { + return +} + +export default CampaignPage diff --git a/src/components/layout/vertical/VerticalMenu.tsx b/src/components/layout/vertical/VerticalMenu.tsx index 5a33103..fbc2db4 100644 --- a/src/components/layout/vertical/VerticalMenu.tsx +++ b/src/components/layout/vertical/VerticalMenu.tsx @@ -161,6 +161,7 @@ const VerticalMenu = ({ dictionary, scrollMenu }: Props) => { {dictionary['navigation'].wheel_spin} + {dictionary['navigation'].campaign} }> diff --git a/src/data/dictionaries/en.json b/src/data/dictionaries/en.json index 9ef9e1e..51b36a7 100644 --- a/src/data/dictionaries/en.json +++ b/src/data/dictionaries/en.json @@ -131,6 +131,7 @@ "loyalty": "Loyalty", "reward": "Reward", "gamification": "Gamification", - "wheel_spin": "Wheel Spin" + "wheel_spin": "Wheel Spin", + "campaign": "Campaign" } } diff --git a/src/data/dictionaries/id.json b/src/data/dictionaries/id.json index 03fdd66..0b32e4d 100644 --- a/src/data/dictionaries/id.json +++ b/src/data/dictionaries/id.json @@ -131,6 +131,7 @@ "loyalty": "Loyalti", "reward": "Reward", "gamification": "Gamifikasi", - "wheel_spin": "Wheel Spin" + "wheel_spin": "Wheel Spin", + "campaign": "Kampanye" } } diff --git a/src/types/services/campaign.ts b/src/types/services/campaign.ts new file mode 100644 index 0000000..440cc49 --- /dev/null +++ b/src/types/services/campaign.ts @@ -0,0 +1,13 @@ +export interface Campaign { + id: string + name: string + description?: string + minimumPurchase: number + rewardType: 'point' | 'voucher' | 'discount' + rewardValue: number + startDate: Date + endDate: Date + isActive: boolean + createdAt: Date + updatedAt: Date +} diff --git a/src/views/apps/marketing/campaign/AddEditCampaignDrawer.tsx b/src/views/apps/marketing/campaign/AddEditCampaignDrawer.tsx new file mode 100644 index 0000000..207bffd --- /dev/null +++ b/src/views/apps/marketing/campaign/AddEditCampaignDrawer.tsx @@ -0,0 +1,576 @@ +// 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' + +// Third-party Imports +import { useForm, Controller, useFieldArray } from 'react-hook-form' + +// Component Imports +import CustomTextField from '@core/components/mui/TextField' + +// Types +export interface Campaign { + id: string + name: string + description?: string + minimumPurchase: number + rewardType: 'point' | 'voucher' | 'discount' + rewardValue: number + startDate: Date + endDate: Date + isActive: boolean + createdAt: Date + updatedAt: Date +} + +export interface CampaignRequest { + name: string + description?: string + minimumPurchase: number + rewardType: 'point' | 'voucher' | 'discount' + rewardValue: number + startDate: Date + endDate: Date + isActive: boolean +} + +type Props = { + open: boolean + handleClose: () => void + data?: Campaign // Data campaign untuk edit (jika ada) +} + +type FormValidateType = { + name: string + description: string + minimumPurchase: number + rewardType: 'point' | 'voucher' | 'discount' + rewardValue: number + startDate: string + endDate: string + isActive: boolean +} + +// Initial form data +const initialData: FormValidateType = { + name: '', + description: '', + minimumPurchase: 0, + rewardType: 'point', + rewardValue: 0, + startDate: '', + endDate: '', + isActive: true +} + +// Mock mutation hooks (replace with actual hooks) +const useCampaignMutation = () => { + const createCampaign = { + mutate: (data: CampaignRequest, options?: { onSuccess?: () => void }) => { + console.log('Creating campaign:', data) + setTimeout(() => options?.onSuccess?.(), 1000) + } + } + + const updateCampaign = { + mutate: (data: { id: string; payload: CampaignRequest }, options?: { onSuccess?: () => void }) => { + console.log('Updating campaign:', data) + setTimeout(() => options?.onSuccess?.(), 1000) + } + } + + return { createCampaign, updateCampaign } +} + +const AddEditCampaignDrawer = (props: Props) => { + // Props + const { open, handleClose, data } = props + + // States + const [showMore, setShowMore] = useState(false) + const [isSubmitting, setIsSubmitting] = useState(false) + + const { createCampaign, updateCampaign } = useCampaignMutation() + + // Determine if this is edit mode + const isEditMode = Boolean(data?.id) + + // Hooks + const { + control, + reset: resetForm, + handleSubmit, + watch, + setValue, + formState: { errors } + } = useForm({ + defaultValues: initialData + }) + + const watchedRewardType = watch('rewardType') + const watchedStartDate = watch('startDate') + const watchedEndDate = watch('endDate') + + // Effect to populate form when editing + useEffect(() => { + if (isEditMode && data) { + // Populate form with existing data + const formData: FormValidateType = { + name: data.name || '', + description: data.description || '', + minimumPurchase: data.minimumPurchase || 0, + rewardType: data.rewardType || 'point', + rewardValue: data.rewardValue || 0, + startDate: data.startDate ? new Date(data.startDate).toISOString().split('T')[0] : '', + endDate: data.endDate ? new Date(data.endDate).toISOString().split('T')[0] : '', + isActive: data.isActive ?? true + } + + 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) + + // Create CampaignRequest object + const campaignRequest: CampaignRequest = { + name: formData.name, + description: formData.description || undefined, + minimumPurchase: formData.minimumPurchase, + rewardType: formData.rewardType, + rewardValue: formData.rewardValue, + startDate: new Date(formData.startDate), + endDate: new Date(formData.endDate), + isActive: formData.isActive + } + + if (isEditMode && data?.id) { + // Update existing campaign + updateCampaign.mutate( + { id: data.id, payload: campaignRequest }, + { + onSuccess: () => { + handleReset() + handleClose() + } + } + ) + } else { + // Create new campaign + createCampaign.mutate(campaignRequest, { + onSuccess: () => { + handleReset() + handleClose() + } + }) + } + } catch (error) { + console.error('Error submitting campaign:', error) + // Handle error (show toast, etc.) + } finally { + setIsSubmitting(false) + } + } + + const handleReset = () => { + handleClose() + resetForm(initialData) + setShowMore(false) + } + + const formatCurrency = (value: number) => { + return new Intl.NumberFormat('id-ID', { + style: 'currency', + currency: 'IDR', + minimumFractionDigits: 0 + }).format(value) + } + + const getRewardTypeLabel = (type: 'point' | 'voucher' | 'discount') => { + switch (type) { + case 'point': + return 'Poin' + case 'voucher': + return 'Voucher' + case 'discount': + return 'Diskon' + default: + return type + } + } + + const getRewardValuePlaceholder = (type: 'point' | 'voucher' | 'discount') => { + switch (type) { + case 'point': + return 'Jumlah poin yang diberikan' + case 'voucher': + return 'Nilai voucher dalam Rupiah' + case 'discount': + return 'Persentase diskon (1-100)' + default: + return 'Nilai reward' + } + } + + const getRewardValueRules = (type: 'point' | 'voucher' | 'discount') => { + const baseRules = { + required: 'Nilai reward wajib diisi', + min: { + value: 1, + message: 'Nilai reward minimal 1' + } + } + + if (type === 'discount') { + return { + ...baseRules, + max: { + value: 100, + message: 'Persentase diskon maksimal 100%' + } + } + } + + return baseRules + } + + return ( + + {/* Sticky Header */} + +
+ {isEditMode ? 'Edit Kampanye' : 'Tambah Kampanye Baru'} + + + +
+
+ + {/* Scrollable Content */} + +
+
+ {/* Nama Kampanye */} +
+ + Nama Kampanye * + + ( + + )} + /> +
+ + {/* Minimum Purchase */} +
+ + Minimum Pembelian * + + ( + 0 ? formatCurrency(field.value) : '')} + InputProps={{ + startAdornment: Rp + }} + onChange={e => field.onChange(Number(e.target.value))} + /> + )} + /> +
+ + {/* Jenis Reward */} +
+ + Jenis Reward * + + ( + + +
+ + Poin +
+
+ +
+ + Voucher +
+
+ +
+ + Diskon +
+
+
+ )} + /> +
+ + {/* Nilai Reward */} +
+ + Nilai {getRewardTypeLabel(watchedRewardType)} * + + ( + Rp + ) : undefined, + endAdornment: + watchedRewardType === 'discount' ? ( + % + ) : watchedRewardType === 'point' ? ( + Poin + ) : undefined + }} + onChange={e => field.onChange(Number(e.target.value))} + /> + )} + /> +
+ + {/* Tanggal Mulai */} +
+ + Tanggal Mulai * + + ( + + )} + /> +
+ + {/* Tanggal Berakhir */} +
+ + Tanggal Berakhir * + + { + if (watchedStartDate && value) { + return ( + new Date(value) >= new Date(watchedStartDate) || 'Tanggal berakhir harus setelah tanggal mulai' + ) + } + return true + } + }} + render={({ field }) => ( + + )} + /> +
+ + {/* Status Aktif */} +
+ ( + } + label='Kampanye Aktif' + /> + )} + /> +
+ + {/* Tampilkan selengkapnya */} + {!showMore && ( + + )} + + {/* Konten tambahan */} + {showMore && ( + <> + {/* Description */} +
+ + Deskripsi Kampanye + + ( + + )} + /> +
+ + {/* Sembunyikan */} + + + )} +
+ +
+ + {/* Sticky Footer */} + +
+ + +
+
+
+ ) +} + +export default AddEditCampaignDrawer diff --git a/src/views/apps/marketing/campaign/CampaignListTable.tsx b/src/views/apps/marketing/campaign/CampaignListTable.tsx new file mode 100644 index 0000000..f48ef23 --- /dev/null +++ b/src/views/apps/marketing/campaign/CampaignListTable.tsx @@ -0,0 +1,668 @@ +'use client' + +// React Imports +import { useEffect, useState, useMemo, useCallback } from 'react' + +// Next Imports +import Link from 'next/link' +import { useParams } from 'next/navigation' + +// MUI Imports +import Card from '@mui/material/Card' +import CardHeader from '@mui/material/CardHeader' +import Button from '@mui/material/Button' +import Typography from '@mui/material/Typography' +import Chip from '@mui/material/Chip' +import Checkbox from '@mui/material/Checkbox' +import IconButton from '@mui/material/IconButton' +import { styled } from '@mui/material/styles' +import TablePagination from '@mui/material/TablePagination' +import type { TextFieldProps } from '@mui/material/TextField' +import MenuItem from '@mui/material/MenuItem' + +// Third-party Imports +import classnames from 'classnames' +import { rankItem } from '@tanstack/match-sorter-utils' +import { + createColumnHelper, + flexRender, + getCoreRowModel, + useReactTable, + getFilteredRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFacetedMinMaxValues, + getPaginationRowModel, + getSortedRowModel +} from '@tanstack/react-table' +import type { ColumnDef, FilterFn } from '@tanstack/react-table' +import type { RankingInfo } from '@tanstack/match-sorter-utils' + +// Type Imports +import type { ThemeColor } from '@core/types' +import type { Locale } from '@configs/i18n' + +// Component Imports +import OptionMenu from '@core/components/option-menu' +import TablePaginationComponent from '@components/TablePaginationComponent' +import CustomTextField from '@core/components/mui/TextField' +import CustomAvatar from '@core/components/mui/Avatar' + +// Util Imports +import { getInitials } from '@/utils/getInitials' +import { getLocalizedUrl } from '@/utils/i18n' +import { formatCurrency } from '@/utils/transform' + +// Style Imports +import tableStyles from '@core/styles/table.module.css' +import Loading from '@/components/layout/shared/Loading' +import AddEditCampaignDrawer from './AddEditCampaignDrawer' + +// Campaign Type Interface +export interface Campaign { + id: string + name: string + description?: string + minimumPurchase: number + rewardType: 'point' | 'voucher' | 'discount' + rewardValue: number + startDate: Date + endDate: Date + isActive: boolean + createdAt: Date + updatedAt: Date +} + +declare module '@tanstack/table-core' { + interface FilterFns { + fuzzy: FilterFn + } + interface FilterMeta { + itemRank: RankingInfo + } +} + +type CampaignWithAction = Campaign & { + action?: string +} + +// Styled Components +const Icon = styled('i')({}) + +const fuzzyFilter: FilterFn = (row, columnId, value, addMeta) => { + // Rank the item + const itemRank = rankItem(row.getValue(columnId), value) + + // Store the itemRank info + addMeta({ + itemRank + }) + + // Return if the item should be filtered in/out + return itemRank.passed +} + +const DebouncedInput = ({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number + onChange: (value: string | number) => void + debounce?: number +} & Omit) => { + // States + const [value, setValue] = useState(initialValue) + + useEffect(() => { + setValue(initialValue) + }, [initialValue]) + + useEffect(() => { + const timeout = setTimeout(() => { + onChange(value) + }, debounce) + + return () => clearTimeout(timeout) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [value]) + + return setValue(e.target.value)} /> +} + +// Dummy data for campaigns +const DUMMY_CAMPAIGN_DATA: Campaign[] = [ + { + id: '1', + name: 'Summer Sale Campaign', + description: 'Get extra points during summer season', + minimumPurchase: 500000, + rewardType: 'point', + rewardValue: 100, + startDate: new Date('2024-06-01'), + endDate: new Date('2024-08-31'), + isActive: true, + createdAt: new Date('2024-05-15'), + updatedAt: new Date('2024-06-01') + }, + { + id: '2', + name: 'Welcome Bonus', + description: 'Special discount for new customers', + minimumPurchase: 200000, + rewardType: 'discount', + rewardValue: 15, + startDate: new Date('2024-01-01'), + endDate: new Date('2024-12-31'), + isActive: true, + createdAt: new Date('2024-01-01'), + updatedAt: new Date('2024-03-15') + }, + { + id: '3', + name: 'Flash Sale Weekend', + description: 'Weekend special voucher campaign', + minimumPurchase: 1000000, + rewardType: 'voucher', + rewardValue: 50000, + startDate: new Date('2024-07-06'), + endDate: new Date('2024-07-07'), + isActive: false, + createdAt: new Date('2024-07-01'), + updatedAt: new Date('2024-07-08') + }, + { + id: '4', + name: 'Loyalty Rewards', + description: 'Extra points for loyal customers', + minimumPurchase: 2000000, + rewardType: 'point', + rewardValue: 300, + startDate: new Date('2024-03-01'), + endDate: new Date('2024-09-30'), + isActive: true, + createdAt: new Date('2024-02-25'), + updatedAt: new Date('2024-05-10') + }, + { + id: '5', + name: 'Black Friday Special', + description: 'Biggest discount of the year', + minimumPurchase: 800000, + rewardType: 'discount', + rewardValue: 25, + startDate: new Date('2024-11-29'), + endDate: new Date('2024-11-29'), + isActive: false, + createdAt: new Date('2024-11-01'), + updatedAt: new Date('2024-11-30') + }, + { + id: '6', + name: 'Student Promo', + description: 'Special voucher for students', + minimumPurchase: 300000, + rewardType: 'voucher', + rewardValue: 25000, + startDate: new Date('2024-09-01'), + endDate: new Date('2024-12-20'), + isActive: true, + createdAt: new Date('2024-08-25'), + updatedAt: new Date('2024-09-01') + }, + { + id: '7', + name: 'Holiday Celebration', + description: 'Special points during holidays', + minimumPurchase: 1500000, + rewardType: 'point', + rewardValue: 200, + startDate: new Date('2024-12-15'), + endDate: new Date('2025-01-15'), + isActive: true, + createdAt: new Date('2024-12-01'), + updatedAt: new Date('2024-12-15') + }, + { + id: '8', + name: 'Mid Year Sale', + description: 'Mid year discount campaign', + minimumPurchase: 600000, + rewardType: 'discount', + rewardValue: 20, + startDate: new Date('2024-06-15'), + endDate: new Date('2024-07-15'), + isActive: false, + createdAt: new Date('2024-06-01'), + updatedAt: new Date('2024-07-16') + } +] + +// Mock data hook with dummy data +const useCampaigns = ({ page, limit, search }: { page: number; limit: number; search: string }) => { + const [isLoading, setIsLoading] = useState(false) + + // Simulate loading + useEffect(() => { + setIsLoading(true) + const timer = setTimeout(() => setIsLoading(false), 500) + return () => clearTimeout(timer) + }, [page, limit, search]) + + // Filter data based on search + const filteredData = useMemo(() => { + if (!search) return DUMMY_CAMPAIGN_DATA + + return DUMMY_CAMPAIGN_DATA.filter( + campaign => + campaign.name.toLowerCase().includes(search.toLowerCase()) || + campaign.description?.toLowerCase().includes(search.toLowerCase()) || + campaign.rewardType.toLowerCase().includes(search.toLowerCase()) + ) + }, [search]) + + // Paginate data + const paginatedData = useMemo(() => { + const startIndex = (page - 1) * limit + const endIndex = startIndex + limit + return filteredData.slice(startIndex, endIndex) + }, [filteredData, page, limit]) + + return { + data: { + campaigns: paginatedData, + total_count: filteredData.length + }, + isLoading, + error: null, + isFetching: isLoading + } +} + +// Utility functions +const getRewardTypeColor = (rewardType: Campaign['rewardType']): ThemeColor => { + switch (rewardType) { + case 'point': + return 'primary' + case 'voucher': + return 'success' + case 'discount': + return 'warning' + default: + return 'info' + } +} + +const getRewardTypeIcon = (rewardType: Campaign['rewardType']): string => { + switch (rewardType) { + case 'point': + return 'tabler-coins' + case 'voucher': + return 'tabler-ticket' + case 'discount': + return 'tabler-percentage' + default: + return 'tabler-gift' + } +} + +const formatRewardValue = (rewardType: Campaign['rewardType'], rewardValue: number): string => { + switch (rewardType) { + case 'point': + return `${rewardValue} Poin` + case 'voucher': + return formatCurrency(rewardValue) + case 'discount': + return `${rewardValue}%` + default: + return rewardValue.toString() + } +} + +// Column Definitions +const columnHelper = createColumnHelper() + +const CampaignListTable = () => { + // States + const [addCampaignOpen, setAddCampaignOpen] = useState(false) + const [editCampaignData, setEditCampaignData] = useState(undefined) + const [rowSelection, setRowSelection] = useState({}) + const [globalFilter, setGlobalFilter] = useState('') + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [search, setSearch] = useState('') + + const { data, isLoading, error, isFetching } = useCampaigns({ + page: currentPage, + limit: pageSize, + search + }) + + const campaigns = data?.campaigns ?? [] + const totalCount = data?.total_count ?? 0 + + // Hooks + const { lang: locale } = useParams() + + const handlePageChange = useCallback((event: unknown, newPage: number) => { + setCurrentPage(newPage) + }, []) + + const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { + const newPageSize = parseInt(event.target.value, 10) + setPageSize(newPageSize) + setCurrentPage(1) // Reset to first page + }, []) + + const handleEditCampaign = (campaign: Campaign) => { + setEditCampaignData(campaign) + setAddCampaignOpen(true) + } + + const handleDeleteCampaign = (campaignId: string) => { + if (confirm('Apakah Anda yakin ingin menghapus kampanye ini?')) { + console.log('Deleting campaign:', campaignId) + // Add your delete logic here + // deleteCampaign.mutate(campaignId) + } + } + + const handleCloseCampaignDrawer = () => { + setAddCampaignOpen(false) + setEditCampaignData(undefined) + } + + const columns = useMemo[]>( + () => [ + { + id: 'select', + header: ({ table }) => ( + + ), + cell: ({ row }) => ( + + ) + }, + columnHelper.accessor('name', { + header: 'Nama Kampanye', + cell: ({ row }) => ( +
+
+ + + {row.original.name} + + + {row.original.description && ( + + {row.original.description} + + )} +
+
+ ) + }), + columnHelper.accessor('minimumPurchase', { + header: 'Minimum Pembelian', + cell: ({ row }) => ( +
+ + {formatCurrency(row.original.minimumPurchase)} +
+ ) + }), + columnHelper.accessor('rewardType', { + header: 'Jenis Reward', + cell: ({ row }) => ( +
+ + +
+ ) + }), + columnHelper.accessor('startDate', { + header: 'Periode Kampanye', + cell: ({ row }) => ( +
+ + {new Date(row.original.startDate).toLocaleDateString('id-ID', { + year: 'numeric', + month: 'short', + day: 'numeric' + })} + + + s/d{' '} + {new Date(row.original.endDate).toLocaleDateString('id-ID', { + year: 'numeric', + month: 'short', + day: 'numeric' + })} + +
+ ) + }), + columnHelper.accessor('isActive', { + header: 'Status', + cell: ({ row }) => ( + + ) + }), + columnHelper.accessor('createdAt', { + header: 'Tanggal Dibuat', + cell: ({ row }) => ( + + {new Date(row.original.createdAt).toLocaleDateString('id-ID', { + year: 'numeric', + month: 'short', + day: 'numeric' + })} + + ) + }), + { + id: 'actions', + header: 'Aksi', + cell: ({ row }) => ( +
+ handleEditCampaign(row.original) + } + }, + { + text: 'Hapus', + icon: 'tabler-trash text-[22px]', + menuItemProps: { + className: 'flex items-center gap-2 text-textSecondary', + onClick: () => handleDeleteCampaign(row.original.id) + } + } + ]} + /> +
+ ), + enableSorting: false + } + ], + // eslint-disable-next-line react-hooks/exhaustive-deps + [locale, handleEditCampaign, handleDeleteCampaign] + ) + + const table = useReactTable({ + data: campaigns as Campaign[], + columns, + filterFns: { + fuzzy: fuzzyFilter + }, + state: { + rowSelection, + globalFilter, + pagination: { + pageIndex: currentPage, + pageSize + } + }, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + manualPagination: true, + pageCount: Math.ceil(totalCount / pageSize) + }) + + return ( + <> + +
+ table.setPageSize(Number(e.target.value))} + className='max-sm:is-full sm:is-[70px]' + > + 10 + 25 + 50 + +
+ setSearch(value as string)} + placeholder='Cari Kampanye' + className='max-sm:is-full' + /> + + +
+
+
+ {isLoading ? ( + + ) : ( + + + {table.getHeaderGroups().map(headerGroup => ( + + {headerGroup.headers.map(header => ( + + ))} + + ))} + + {table.getFilteredRowModel().rows.length === 0 ? ( + + + + + + ) : ( + + {table + .getRowModel() + .rows.slice(0, table.getState().pagination.pageSize) + .map(row => { + return ( + + {row.getVisibleCells().map(cell => ( + + ))} + + ) + })} + + )} +
+ {header.isPlaceholder ? null : ( + <> +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: , + desc: + }[header.column.getIsSorted() as 'asc' | 'desc'] ?? null} +
+ + )} +
+ Tidak ada data tersedia +
{flexRender(cell.column.columnDef.cell, cell.getContext())}
+ )} +
+ ( + + )} + count={totalCount} + rowsPerPage={pageSize} + page={currentPage} + onPageChange={handlePageChange} + onRowsPerPageChange={handlePageSizeChange} + rowsPerPageOptions={[10, 25, 50]} + disabled={isLoading} + /> +
+ + + ) +} + +export default CampaignListTable diff --git a/src/views/apps/marketing/campaign/index.tsx b/src/views/apps/marketing/campaign/index.tsx new file mode 100644 index 0000000..852c444 --- /dev/null +++ b/src/views/apps/marketing/campaign/index.tsx @@ -0,0 +1,17 @@ +// MUI Imports +import Grid from '@mui/material/Grid2' +import LoyaltyListTable from './CampaignListTable' + +// Type Imports + +const LoyaltyList = () => { + return ( + + + + + + ) +} + +export default LoyaltyList