diff --git a/src/pages/AdminPanel.tsx b/src/pages/AdminPanel.tsx index a7fad73..58aa047 100644 --- a/src/pages/AdminPanel.tsx +++ b/src/pages/AdminPanel.tsx @@ -81,6 +81,12 @@ const PaymentsIcon = ({ className = "w-8 h-8" }: { className?: string }) => ( ) +const PromoOffersIcon = ({ className = "w-8 h-8" }: { className?: string }) => ( + + + +) + const ChevronRightIcon = () => ( @@ -230,6 +236,16 @@ export default function AdminPanel() { bgColor: 'bg-violet-500/20', textColor: 'text-violet-400' }, + { + to: '/admin/promo-offers', + icon: , + mobileIcon: , + title: t('admin.nav.promoOffers', 'Промопредложения'), + description: t('admin.panel.promoOffersDesc', 'Персональные скидки и предложения'), + color: 'orange', + bgColor: 'bg-orange-500/20', + textColor: 'text-orange-400' + }, { to: '/admin/campaigns', icon: , diff --git a/src/pages/AdminPromoOffers.tsx b/src/pages/AdminPromoOffers.tsx new file mode 100644 index 0000000..3e265b4 --- /dev/null +++ b/src/pages/AdminPromoOffers.tsx @@ -0,0 +1,786 @@ +import { useState } from 'react' +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { + promoOffersApi, + PromoOfferTemplate, + PromoOfferTemplateUpdateRequest, + PromoOfferLog, + TARGET_SEGMENTS, + TargetSegment, + OFFER_TYPE_CONFIG, + OfferType, +} from '../api/promoOffers' + +// Icons +const GiftIcon = () => ( + + + +) + +const EditIcon = () => ( + + + +) + +const XIcon = () => ( + + + +) + +const SendIcon = () => ( + + + +) + +const ClockIcon = () => ( + + + +) + +const UserIcon = () => ( + + + +) + +const CheckIcon = () => ( + + + +) + +const UsersIcon = () => ( + + + +) + +// Helper functions +const formatDateTime = (date: string | null): string => { + if (!date) return '-' + return new Date(date).toLocaleString('ru-RU', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit', + }) +} + +const getActionLabel = (action: string): string => { + const labels: Record = { + created: 'Создано', + claimed: 'Активировано', + consumed: 'Использовано', + disabled: 'Деактивировано', + } + return labels[action] || action +} + +const getActionColor = (action: string): string => { + const colors: Record = { + created: 'bg-blue-500/20 text-blue-400', + claimed: 'bg-emerald-500/20 text-emerald-400', + consumed: 'bg-purple-500/20 text-purple-400', + disabled: 'bg-dark-600 text-dark-400', + } + return colors[action] || 'bg-dark-600 text-dark-400' +} + +const getOfferTypeIcon = (offerType: string): string => { + return OFFER_TYPE_CONFIG[offerType as OfferType]?.icon || '🎁' +} + +const getOfferTypeLabel = (offerType: string): string => { + return OFFER_TYPE_CONFIG[offerType as OfferType]?.label || offerType +} + +// Template Edit Modal +interface TemplateEditModalProps { + template: PromoOfferTemplate + onSave: (data: PromoOfferTemplateUpdateRequest) => void + onClose: () => void + isLoading?: boolean +} + +function TemplateEditModal({ template, onSave, onClose, isLoading }: TemplateEditModalProps) { + const [name, setName] = useState(template.name) + const [messageText, setMessageText] = useState(template.message_text) + const [buttonText, setButtonText] = useState(template.button_text) + const [validHours, setValidHours] = useState(template.valid_hours) + const [discountPercent, setDiscountPercent] = useState(template.discount_percent) + const [activeDiscountHours, setActiveDiscountHours] = useState(template.active_discount_hours || 0) + const [testDurationHours, setTestDurationHours] = useState(template.test_duration_hours || 0) + const [isActive, setIsActive] = useState(template.is_active) + + const isTestAccess = template.offer_type === 'test_access' + + const handleSubmit = () => { + const data: PromoOfferTemplateUpdateRequest = { + name, + message_text: messageText, + button_text: buttonText, + valid_hours: validHours, + discount_percent: discountPercent, + is_active: isActive, + } + if (isTestAccess) { + data.test_duration_hours = testDurationHours > 0 ? testDurationHours : undefined + } else { + data.active_discount_hours = activeDiscountHours > 0 ? activeDiscountHours : undefined + } + onSave(data) + } + + return ( +
+
+ {/* Header */} +
+
+ {getOfferTypeIcon(template.offer_type)} +

+ Редактирование шаблона +

+
+ +
+ + {/* Content */} +
+
+ + setName(e.target.value)} + className="w-full px-3 py-2 bg-dark-700 border border-dark-600 rounded-lg text-dark-100 focus:outline-none focus:border-accent-500" + /> +
+ +
+ +