/* global appLocalizer */ import React, { useEffect, useState } from 'react'; import axios from 'axios'; import { __ } from '@wordpress/i18n'; import { getApiLink } from '@zyra/core'; import { TextAreaInput, TextInput, ButtonInput, ToggleInput, SelectInput, } from '@zyra/inputs'; import { ContainerComponent, ColumnComponent, FormGroupWrapperComponent, FormGroupComponent, PopupComponent, NavigatorHeaderComponent, } from '@zyra/components'; import { TableCard, TableRow, QueryProps, CategoryCount } from '@zyra/table'; import Popup from '../../../src/components/Popup/Popup'; import { formatLocalDate } from '../../../src/services/commonFunction'; type AnnouncementForm = { title: string; url: string; content: string; stores: number[]; status: 'draft' | 'pending' | 'publish'; }; interface Store { id: number; store_name: string; } interface StoreOption { value: number; label: string; } export const Announcements: React.FC = () => { const [rows, setRows] = useState([]); const [isLoading, setIsLoading] = useState(false); const [totalRows, setTotalRows] = useState(0); const [submitting, setSubmitting] = useState(false); const [addAnnouncements, setAddAnnouncements] = useState(false); const [rowIds, setRowIds] = useState([]); const [error, setError] = useState(null); const [editId, setEditId] = useState(null); // Form state const [formData, setFormData] = useState({ title: '', url: '', content: '', stores: [], status: 'publish', }); const fetchStoreOptions = () => { axios .get(getApiLink(appLocalizer, 'stores'), { headers: { 'X-WP-Nonce': appLocalizer.nonce }, params: { options: true }, }) .then((response) => { const stores = response.data || []; const options: StoreOption[] = [ { value: 0, label: __('All Stores', 'multivendorx') }, ...stores.map((store: Store) => ({ value: store.id, label: store.store_name, })), ]; setStoreOptions(options); }) .catch(() => { setError(__('Failed to load stores', 'multivendorx')); }); }; const [categoryCounts, setCategoryCounts] = useState< CategoryCount[] | null >(null); const [storeOptions, setStoreOptions] = useState([]); const [validationErrors, setValidationErrors] = useState<{ [key: string]: string; }>({}); const [selectedAn, setSelectedAn] = useState<{ id: number; } | null>(null); const [confirmOpen, setConfirmOpen] = useState(false); useEffect(() => { fetchStoreOptions(); }, []); const handleConfirmDelete = () => { if (!selectedAn) { return; } axios({ method: 'DELETE', url: getApiLink(appLocalizer, `announcements/${selectedAn.id}`), headers: { 'X-WP-Nonce': appLocalizer.nonce, }, }) .then(() => { doRefreshTableData({}); }) .finally(() => { setConfirmOpen(false); setSelectedAn(null); }); }; const validateForm = () => { const errors: { [key: string]: string } = {}; if (!formData.title.trim()) { errors.title = __('Title is required', 'multivendorx'); } if (!formData.content.trim()) { errors.content = __('Content is required', 'multivendorx'); } if (!formData.stores || formData.stores.length === 0) { errors.stores = __( 'Please select at least one store', 'multivendorx' ); } setValidationErrors(errors); return Object.keys(errors).length === 0; }; const handleCloseForm = () => { setAddAnnouncements(false); setFormData({ title: '', url: '', content: '', stores: [], // Reset to empty array status: 'draft', }); setEditId(null); setError(null); setValidationErrors({}); }; // Handle form input change const handleChange = (name: string, value: string | number[]) => { setFormData((prev) => ({ ...prev, [name]: value })); if (validationErrors[name]) { setValidationErrors((prev) => { const updated = { ...prev }; delete updated[name]; return updated; }); } }; const handleBulkAction = (action: string, selectedIds: number[]) => { if (!selectedIds.length) { return; } if (!action) { return; } axios({ method: 'POST', url: getApiLink(appLocalizer, 'announcements'), headers: { 'X-WP-Nonce': appLocalizer.nonce }, data: { bulk: true, action, ids: selectedIds }, }) .then(() => { doRefreshTableData({}); }) .catch((err) => { console.error('Bulk action failed:', err); setError(__('Failed to perform bulk action.', 'multivendorx')); }); }; const handleEdit = (id: number) => { axios .get(getApiLink(appLocalizer, `announcements/${id}`), { headers: { 'X-WP-Nonce': appLocalizer.nonce }, }) .then((response) => { setFormData({ title: response.data.title || '', url: response.data.url || '', content: response.data.content || '', stores: response.data.stores ?? [], status: response.data.status || 'draft', }); setEditId(id); setAddAnnouncements(true); }) .catch((err) => { console.error('Failed to load announcement for editing', err); setError(__('Failed to load announcement for editing', 'multivendorx')); }); }; const handleSubmit = () => { if (submitting) { return; } if (!validateForm()) { return; } setSubmitting(true); const endpoint = editId ? getApiLink(appLocalizer, `announcements/${editId}`) : getApiLink(appLocalizer, 'announcements'); const payload = { ...formData, }; axios({ method: 'POST', url: endpoint, headers: { 'X-WP-Nonce': appLocalizer.nonce }, data: payload, }) .then((response) => { if (response.data?.success) { setAddAnnouncements(false); setFormData({ title: '', url: '', content: '', stores: [], status: 'draft', }); setEditId(null); doRefreshTableData({}); } else { setError(__('Failed to save announcement', 'multivendorx')); } // cleanup on success setSubmitting(false); }) .catch((err) => { console.error('Failed to save announcement', err); setError(__('Failed to save announcement', 'multivendorx')); // cleanup on error setSubmitting(false); }); }; const bulkActions = [ { label: __('Published', 'multivendorx'), value: 'publish' }, { label: __('Pending', 'multivendorx'), value: 'pending' }, { label: __('Delete', 'multivendorx'), value: 'delete' }, ]; const headers = { title: { label: __('Title', 'multivendorx') }, content: { label: __('Content', 'multivendorx'), type: 'content' }, status_label: { label: __('Status', 'multivendorx'), type: 'status', statusClass: (row) => `${row.status}` }, store_name: { label: __('Recipients', 'multivendorx') }, date_created: { label: __('Date', 'multivendorx'), type: 'date' }, action: { type: 'action', label: __('Action', 'multivendorx'), actions: [ { label: __('Edit', 'multivendorx'), icon: 'edit', onClick: (row) => handleEdit(row.id), }, { label: __('Delete', 'multivendorx'), icon: 'delete', onClick: (row) => { setSelectedAn({ id: row.id }); setConfirmOpen(true); }, className: 'danger', }, ], }, }; const doRefreshTableData = (query: QueryProps) => { setIsLoading(true); axios .get(getApiLink(appLocalizer, 'announcements'), { headers: { 'X-WP-Nonce': appLocalizer.nonce, withCredentials: true, }, params: { page: query.paged || 1, row: query.per_page || 10, status: query.categoryFilter || '', search_value: query.searchValue || '', start_date: query.filter?.created_at?.startDate ? formatLocalDate(query.filter.created_at.startDate) : '', end_date: query.filter?.created_at?.endDate ? formatLocalDate(query.filter.created_at.endDate) : '', store_id: query.filter?.store_id || '', }, }) .then((response) => { const items = response.data || []; const ids = items .filter((ann) => ann?.id != null) .map((ann) => ann.id); setRowIds(ids); setRows(items); setCategoryCounts([ { value: 'all', label: __('All', 'multivendorx'), count: Number(response.headers['x-wp-total']) || 0, }, { value: 'publish', label: __('Published', 'multivendorx'), count: Number(response.headers['x-wp-status-publish']) || 0, }, { value: 'pending', label: __('Pending', 'multivendorx'), count: Number(response.headers['x-wp-status-pending']) || 0, }, { value: 'draft', label: __('Draft', 'multivendorx'), count: Number(response.headers['x-wp-status-draft']) || 0, }, ]); setTotalRows(Number(response.headers['x-wp-total']) || 0); setIsLoading(false); }) .catch((error) => { console.error('Failed to fetch announcements', error); setError(__('Failed to load announcements', 'multivendorx')); setRows([]); setTotalRows(0); setIsLoading(false); }); }; const filters = [ { key: 'store_id', label: __('Select Stores', 'multivendorx'), type: 'select', options: storeOptions, }, { key: 'created_at', label: __('Created Date', 'multivendorx'), type: 'date', }, ]; return ( <> setConfirmOpen(false)} width={31.25} height="auto" > { setConfirmOpen(false); setSelectedAn(null); }} /> { setValidationErrors({}); setAddAnnouncements(true); }, }, ]} /> handleSubmit(), }, ]} /> } > handleChange('title', val as string) } msg={{ type: 'error', message: validationErrors.title, }} /> handleChange('content', val as string) } usePlainText={false} tinymceApiKey={ appLocalizer.admin_settings[ 'overview' ]['tinymce_api_section'] ?? '' } msg={{ type: 'error', message: validationErrors.content, }} /> { if (!Array.isArray(newValue)) { return; } const selectedIds = newValue.map((opt) => Number(opt) ); const prevStores = formData.stores; let nextStores = selectedIds; if ( !prevStores.includes(0) && selectedIds.includes(0) ) { nextStores = [0]; } else if ( prevStores.includes(0) && selectedIds.length > 1 ) { nextStores = selectedIds.filter( (id) => id !== 0 ); } setValidationErrors((prev) => { const updated = { ...prev }; delete updated.stores; return updated; }); setFormData((prev) => ({ ...prev, stores: nextStores, })); }} msg={{ type: 'error', message: validationErrors.stores, }} /> handleChange('status', val) } /> { handleBulkAction(action, selectedIds); }} format={appLocalizer.date_format} /> ); }; export default Announcements;