import { useState } from 'react'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { Plus, Gift, Power, Pencil, Trash2, X, Info } from 'lucide-react'
import toast from 'react-hot-toast'
import client from '../../../api/client'
import Button from '../../../ui/Button'
import Modal from '../../../ui/Modal'
import { ConfirmModal } from '../../../ui/Modal'
// ─── API ─────────────────────────────────────────────────────────────────────
const api = {
list: () => client.get('/api/pricing/deals').then(r => r.data),
create: body => client.post('/api/pricing/deals', body).then(r => r.data),
update: (id,b) => client.put(`/api/pricing/deals/${id}`, b).then(r => r.data),
toggle: id => client.patch(`/api/pricing/deals/${id}/toggle`).then(r => r.data),
remove: id => client.delete(`/api/pricing/deals/${id}`),
modifiers: () => client.get('/api/pricing/modifiers').then(r => r.data),
products: () => client.get('/api/products', { params: { all: true } }).then(r => r.data),
categories:() => client.get('/api/products/categories').then(r => r.data),
}
const COLORS = [
'#6366f1','#8b5cf6','#ec4899','#f43f5e','#f97316',
'#eab308','#22c55e','#14b8a6','#0ea5e9','#64748b',
]
const CONDITION_TYPES = [
{ value: 'min_item_quantity', label: 'Ελάχιστη ποσότητα αντικειμένου' },
{ value: 'min_order_value', label: 'Ελάχιστη αξία παραγγελίας' },
{ value: 'time_range', label: 'Ώρα ημέρας' },
{ value: 'day_of_week', label: 'Ημέρα εβδομάδας' },
{ value: 'date_range', label: 'Εύρος ημερομηνιών' },
{ value: 'order_channel', label: 'Κανάλι παραγγελίας' },
{ value: 'price_group_active',label: 'Ομάδα τιμής ενεργή' },
]
const DAYS = ['Δευτέρα','Τρίτη','Τετάρτη','Πέμπτη','Παρασκευή','Σάββατο','Κυριακή']
const CHANNELS = ['pos','online','qr','takeaway']
const ACTION_TYPES = [
{ value: 'apply_modifier', label: 'Εφαρμογή τροποποιητή' },
{ value: 'set_price', label: 'Ορισμός τιμής' },
{ value: 'add_amount', label: '± Ποσό €' },
{ value: 'add_percent', label: '± Ποσοστό %' },
{ value: 'free_item', label: 'Δωρεάν προϊόν' },
{ value: 'free_choice', label: 'Επιλογή δωρεάν προϊόντος' },
]
// ─── Reuse condition editor (simplified for deals) ────────────────────────────
function ConditionRow({ cond, onChange, onRemove, groups }) {
const set = (k, v) => onChange({ ...cond, params: { ...cond.params, [k]: v } })
const setType = t => onChange({ condition_type: t, params: {} })
return (
setType(e.target.value)}>
{CONDITION_TYPES.map(ct => {ct.label} )}
{cond.condition_type === 'min_item_quantity' && (
Ελάχιστη ποσότητα
set('min', Number(e.target.value))} />
)}
{cond.condition_type === 'min_order_value' && (
Ελάχιστη αξία (€)
set('min', Number(e.target.value))} />
)}
{cond.condition_type === 'time_range' && (
)}
{cond.condition_type === 'day_of_week' && (
{DAYS.map((d, i) => (
{ const days = cond.params.days ?? []; set('days', days.includes(i) ? days.filter(x => x !== i) : [...days, i]) }}
className={`px-2.5 py-1 rounded text-[11px] font-medium transition-colors ${
(cond.params.days ?? []).includes(i) ? 'bg-sky-100 text-sky-700' : 'bg-white border border-slate-200 text-slate-500 hover:bg-slate-50'
}`}>{d.slice(0, 3)}
))}
)}
{cond.condition_type === 'date_range' && (
)}
{cond.condition_type === 'order_channel' && (
{CHANNELS.map(ch => (
{ const channels = cond.params.channels ?? []; set('channels', channels.includes(ch) ? channels.filter(x => x !== ch) : [...channels, ch]) }}
className={`px-2.5 py-1 rounded text-[11px] font-medium transition-colors ${
(cond.params.channels ?? []).includes(ch) ? 'bg-sky-100 text-sky-700' : 'bg-white border border-slate-200 text-slate-500 hover:bg-slate-50'
}`}>{ch}
))}
)}
{cond.condition_type === 'price_group_active' && (
set('price_group_id', Number(e.target.value))}>
— Επιλογή ομάδας —
{(groups ?? []).map(g => {g.name} )}
)}
)
}
// ─── Shared multi-picker (same logic as ModifiersTab) ─────────────────────────
function MultiPicker({ items, selected, onToggle, labelKey = 'name', valueKey = 'id' }) {
return (
{items.length === 0 && (
Δεν βρέθηκαν αποτελέσματα
)}
{items.map(item => {
const val = item[valueKey]
const checked = selected.includes(val)
return (
onToggle(val)}
className={`flex items-center gap-2.5 px-3 py-1.5 cursor-pointer transition-colors text-[12px] border-b border-slate-100 last:border-0 ${
checked ? 'bg-sky-50 text-sky-700' : 'hover:bg-slate-50 text-slate-700'
}`}>
{item[labelKey]}
)
})}
)
}
function TagPicker({ selected, onToggle, onAdd, allTags }) {
const [draft, setDraft] = useState('')
const handleAdd = () => {
const tag = draft.trim()
if (tag && !selected.includes(tag)) onAdd(tag)
setDraft('')
}
return (
{allTags.length > 0 && (
{allTags.map(tag => {
const active = selected.includes(tag)
return (
onToggle(tag)}
className={`px-2 py-0.5 rounded-full text-[11px] font-medium border transition-colors ${
active ? 'bg-sky-500 border-sky-500 text-white' : 'border-slate-300 text-slate-600 hover:border-sky-400 hover:text-sky-600'
}`}>{tag}
)
})}
)}
{selected.length > 0 && (
{selected.map(tag => (
{tag}
onToggle(tag)} className="hover:text-sky-900">
))}
)}
setDraft(e.target.value)}
onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleAdd() } }} />
+
)
}
// ─── Deal target row ──────────────────────────────────────────────────────────
function DealTargetRow({ target, onChange, onRemove, products, categories, allTags }) {
const ids = target.target_ids ?? []
const tags = target.target_tags ?? []
const setIds = v => onChange({ ...target, target_ids: v, target_id: v[0] ?? null })
const setTags = v => onChange({ ...target, target_tags: v, target_tag: v[0] ?? null })
const toggleId = id => setIds(ids.includes(id) ? ids.filter(x => x !== id) : [...ids, id])
const toggleTag = tag => setTags(tags.includes(tag) ? tags.filter(x => x !== tag) : [...tags, tag])
return (
onChange({ target_type: e.target.value, target_id: null, target_tag: null, target_ids: [], target_tags: [] })}>
Οποιοδήποτε προϊόν
Προϊόντα
Κατηγορίες
Ετικέτες (tags)
{target.target_type === 'item' && (
)}
{target.target_type === 'category' && (
)}
{target.target_type === 'tag' && (
setTags([...tags, tag])} allTags={allTags} />
)}
)
}
// ─── Deal form ────────────────────────────────────────────────────────────────
const DEAL_TABS = [
{ key: 'info', label: 'Πληροφορίες' },
{ key: 'trigger', label: 'Trigger & Συνθήκες' },
{ key: 'action', label: 'Ενέργεια' },
]
function DealForm({ initial, onSave, onCancel, saving, modifiers, groups, activeTab, onTabChange }) {
const { data: products = [] } = useQuery({
queryKey: ['products-all-pricing'],
queryFn: () => client.get('/api/products/', { params: { all: true } }).then(r => r.data),
})
const { data: categories = [] } = useQuery({
queryKey: ['categories'],
queryFn: () => client.get('/api/products/categories').then(r => r.data),
staleTime: 60_000,
})
const { data: allTags = [] } = useQuery({
queryKey: ['product-tags'],
queryFn: () => client.get('/api/products/tags').then(r => r.data),
staleTime: 60_000,
})
const normalizeTarget = t => ({
...t,
target_ids: t.target_ids ?? (t.target_id != null ? [t.target_id] : []),
target_tags: t.target_tags ?? (t.target_tag ? [t.target_tag] : []),
})
const blank = {
name: '', description: '', color: COLORS[6], is_active: true, sort_order: 0,
action_type: 'free_item',
action_modifier_id: null, action_value: null,
action_free_item_id: null,
action_free_target_type: 'item', action_free_target_ids: [],
action_free_quantity: 1,
conditions: [], targets: [{ target_type: 'any', target_id: null, target_tag: null, target_ids: [], target_tags: [] }],
}
const [f, setF] = useState(initial ? {
...blank, ...initial,
conditions: initial.conditions ?? [],
targets: (initial.targets ?? [{ target_type: 'any', target_id: null, target_tag: null, target_ids: [], target_tags: [] }]).map(normalizeTarget),
action_free_target_ids: initial.action_free_target_ids ?? [],
} : blank)
const set = (k, v) => setF(p => ({ ...p, [k]: v }))
const addCond = () => set('conditions', [...f.conditions, { condition_type: 'min_item_quantity', params: { min: 1 } }])
const updCond = (i, c) => set('conditions', f.conditions.map((x, j) => j === i ? c : x))
const remCond = i => set('conditions', f.conditions.filter((_, j) => j !== i))
const addTarget = () => set('targets', [...f.targets, { target_type: 'any', target_id: null, target_tag: null, target_ids: [], target_tags: [] }])
const updTarget = (i, t) => set('targets', f.targets.map((x, j) => j === i ? t : x))
const remTarget = i => set('targets', f.targets.filter((_, j) => j !== i))
const toggleFreeId = id => {
const ids = f.action_free_target_ids ?? []
set('action_free_target_ids', ids.includes(id) ? ids.filter(x => x !== id) : [...ids, id])
}
function submit(e) {
e.preventDefault()
if (!f.name.trim()) return toast.error('Απαιτείται όνομα')
onSave({
...f,
action_free_target_ids: f.action_free_target_ids?.length ? f.action_free_target_ids : null,
})
}
return (
)
}
// ─── Deal card ────────────────────────────────────────────────────────────────
function DealCard({ deal, onEdit, onDelete }) {
const qc = useQueryClient()
const toggle = useMutation({
mutationFn: () => api.toggle(deal.id),
onSuccess: () => qc.invalidateQueries({ queryKey: ['pricing-deals'] }),
onError: () => toast.error('Σφάλμα'),
})
const actionLabel = ACTION_TYPES.find(a => a.value === deal.action_type)?.label ?? deal.action_type
return (
{deal.name}
{deal.is_active ? 'Ενεργή' : 'Ανενεργή'}
{actionLabel}
{deal.description &&
{deal.description}
}
{deal.conditions?.length ?? 0} συνθήκες · {deal.targets?.length ?? 0} targets
toggle.mutate()}
className={`p-1.5 rounded-lg transition-colors ${
deal.is_active ? 'text-emerald-600 hover:bg-emerald-50' : 'text-slate-400 hover:bg-slate-100'
}`}>
onEdit(deal)}
className="p-1.5 rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition-colors">
onDelete(deal)}
className="p-1.5 rounded-lg text-slate-400 hover:bg-rose-50 hover:text-rose-500 transition-colors">
)
}
// ─── Main tab ─────────────────────────────────────────────────────────────────
export default function DealsTab() {
const qc = useQueryClient()
const [showForm, setShowForm] = useState(false)
const [editing, setEditing] = useState(null)
const [deleting, setDeleting] = useState(null)
const [formTab, setFormTab] = useState('info')
const { data: deals = [], isLoading } = useQuery({ queryKey: ['pricing-deals'], queryFn: api.list })
const { data: modifiers = [] } = useQuery({ queryKey: ['pricing-modifiers'], queryFn: api.modifiers })
const { data: groups = [] } = useQuery({ queryKey: ['pricing-groups'], queryFn: () => client.get('/api/pricing/groups').then(r => r.data) })
const create = useMutation({
mutationFn: api.create,
onSuccess: () => { qc.invalidateQueries({ queryKey: ['pricing-deals'] }); setShowForm(false); toast.success('Δημιουργήθηκε') },
onError: () => toast.error('Σφάλμα'),
})
const update = useMutation({
mutationFn: ({ id, body }) => api.update(id, body),
onSuccess: () => { qc.invalidateQueries({ queryKey: ['pricing-deals'] }); setEditing(null); toast.success('Αποθηκεύτηκε') },
onError: () => toast.error('Σφάλμα'),
})
const remove = useMutation({
mutationFn: api.remove,
onSuccess: () => { qc.invalidateQueries({ queryKey: ['pricing-deals'] }); setDeleting(null); toast.success('Διαγράφηκε') },
onError: () => toast.error('Σφάλμα'),
})
const formProps = { modifiers, groups }
return (
Προσφορές
Αυτόματες δράσεις που ενεργοποιούνται όταν ο σερβιτόρος προσθέτει συγκεκριμένα προϊόντα στην παραγγελία.
setShowForm(true)}>
Νέα Προσφορά
Όταν μια προσφορά ενεργοποιηθεί, ο σερβιτόρος λαμβάνει prompt στην εφαρμογή και επιλέγει αν θα την εφαρμόσει.
Τίποτα δεν εφαρμόζεται αυτόματα χωρίς επιβεβαίωση.
{isLoading ? (
) : deals.length === 0 ? (
Δεν υπάρχουν προσφορές
π.χ. «Αγόρασε 3 μπύρες, πάρε 1 δωρεάν»
) : (
{deals.map(d => (
{ setEditing(d); setFormTab('info') }} onDelete={setDeleting} />
))}
)}
{showForm && (
{ setShowForm(false); setFormTab('info') }}
maxWidth="max-w-xl" tabs={DEAL_TABS} activeTab={formTab} onTabChange={setFormTab}
footer={<>
{ setShowForm(false); setFormTab('info') }}>Ακύρωση
{create.isPending ? 'Αποθήκευση...' : 'Αποθήκευση'}
>}>
create.mutate(body)} onCancel={() => { setShowForm(false); setFormTab('info') }}
saving={create.isPending} {...formProps} activeTab={formTab} onTabChange={setFormTab} />
)}
{editing && (
{ setEditing(null); setFormTab('info') }}
maxWidth="max-w-xl" tabs={DEAL_TABS} activeTab={formTab} onTabChange={setFormTab}
footer={<>
{ setEditing(null); setFormTab('info') }}>Ακύρωση
{update.isPending ? 'Αποθήκευση...' : 'Αποθήκευση'}
>}>
update.mutate({ id: editing.id, body })}
onCancel={() => { setEditing(null); setFormTab('info') }} saving={update.isPending}
{...formProps} activeTab={formTab} onTabChange={setFormTab} />
)}
{deleting && (
remove.mutate(deleting.id)}
onCancel={() => setDeleting(null)} />
)}
)
}