Files
aura-crm/production/web/src/clients/ClientLoyaltySection.jsx
T

124 lines
5.6 KiB
React

import { useState } from 'react'
import * as api from '../lib/api'
import { useAuth } from '../auth/AuthContext'
import { useClientLoyalty } from './useClientLoyalty'
import { inputStyle } from '../orders/formStyles'
const TYPE_LABELS = { accrual: 'Начисление', redemption: 'Списание', adjustment: 'Корректировка' }
const TYPE_COLORS = { accrual: 'var(--accent-green)', redemption: 'var(--accent-red)', adjustment: 'var(--text-muted)' }
function RedeemForm({ clientId, balance, onDone }) {
const [points, setPoints] = useState('')
const [note, setNote] = useState('')
const [busy, setBusy] = useState(false)
const [error, setError] = useState('')
const submit = async () => {
const n = Number(points)
if (!n || n <= 0) { setError('Укажите количество баллов'); return }
setBusy(true)
setError('')
try {
await api.loyalty.redeem(clientId, { points: n, note })
setPoints('')
setNote('')
onDone()
} catch (err) {
setError(err.message)
} finally {
setBusy(false)
}
}
return (
<div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap', alignItems: 'center', marginBottom: '6px' }}>
<input type="number" min="1" max={balance} value={points} onChange={(e) => setPoints(e.target.value)} placeholder="Баллов" style={{ ...inputStyle, width: '90px', padding: '6px 8px' }} />
<input value={note} onChange={(e) => setNote(e.target.value)} placeholder="Заметка (необязательно)" style={{ ...inputStyle, flex: 1, minWidth: '140px', padding: '6px 8px' }} />
<button type="button" onClick={submit} disabled={busy} className="btn-outline" style={{ padding: '6px 12px', fontSize: '0.78rem' }}>Списать</button>
{error && <span style={{ fontSize: '0.76rem', color: 'var(--accent-red)', width: '100%' }}>{error}</span>}
</div>
)
}
function AdjustForm({ clientId, onDone }) {
const [points, setPoints] = useState('')
const [note, setNote] = useState('')
const [busy, setBusy] = useState(false)
const [error, setError] = useState('')
const submit = async () => {
const n = Number(points)
if (!n) { setError('Укажите количество баллов (можно отрицательное)'); return }
if (!note.trim()) { setError('Заметка обязательна для корректировки'); return }
setBusy(true)
setError('')
try {
await api.loyalty.adjust(clientId, { points: n, note })
setPoints('')
setNote('')
onDone()
} catch (err) {
setError(err.message)
} finally {
setBusy(false)
}
}
return (
<div style={{ display: 'flex', gap: '6px', flexWrap: 'wrap', alignItems: 'center', marginBottom: '10px' }}>
<input type="number" value={points} onChange={(e) => setPoints(e.target.value)} placeholder="±Баллов" style={{ ...inputStyle, width: '90px', padding: '6px 8px' }} />
<input value={note} onChange={(e) => setNote(e.target.value)} placeholder="Причина (обязательно)" style={{ ...inputStyle, flex: 1, minWidth: '140px', padding: '6px 8px' }} />
<button type="button" onClick={submit} disabled={busy} className="btn-outline" style={{ padding: '6px 12px', fontSize: '0.78rem' }}>Скорректировать</button>
{error && <span style={{ fontSize: '0.76rem', color: 'var(--accent-red)', width: '100%' }}>{error}</span>}
</div>
)
}
// Embedded in ClientsPage's ClientDetail modal, right after
// ClientNotificationsSection — balance, manual redeem/adjust (owner/manager
// only, matching the backend's cashOnly gate), and the ledger history.
const ClientLoyaltySection = ({ clientId }) => {
const { staff } = useAuth()
const canManage = staff.role === 'owner' || staff.role === 'manager'
const { balance, history, loading, reload } = useClientLoyalty(clientId)
return (
<div style={{ marginTop: '18px', paddingTop: '18px', borderTop: '1px solid var(--border-light)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '10px' }}>
<h3 style={{ fontSize: '0.9rem' }}>Бонусные баллы</h3>
<span style={{ fontSize: '1.1rem', fontWeight: 590, color: 'var(--accent-orange)' }}>{balance}</span>
</div>
{canManage && (
<>
<RedeemForm clientId={clientId} balance={balance} onDone={reload} />
<AdjustForm clientId={clientId} onDone={reload} />
</>
)}
<h4 style={{ fontSize: '0.82rem', color: 'var(--text-muted)', marginBottom: '6px' }}>История</h4>
{loading ? (
<p style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>Загрузка...</p>
) : history.length === 0 ? (
<p style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>Пока нет операций</p>
) : (
history.map((t) => (
<div key={t.id} style={{ padding: '6px 0', borderBottom: '1px solid var(--border-light)', fontSize: '0.78rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{TYPE_LABELS[t.type] || t.type}{t.note ? `${t.note}` : ''}</span>
<span style={{ fontWeight: 590, color: TYPE_COLORS[t.type], flexShrink: 0 }}>
{t.points > 0 ? '+' : ''}{t.points}
</span>
</div>
<p style={{ color: 'var(--text-muted)', marginTop: '2px' }}>
{new Date(t.created_at).toLocaleString('ru-RU')} · {t.staff_name}
</p>
</div>
))
)}
</div>
)
}
export default ClientLoyaltySection