- Add src/fields/richText.ts: standardEditor (all defaultFeatures) and blogEditor (defaultFeatures + UploadFeature for media + EXPERIMENTAL_TableFeature) - Apply standardEditor to ThankYouPage, CheckoutPage, GroupVisitsPage, Tariffs, Locations - Apply blogEditor to BlogPosts — editors can now insert images inline via Upload button Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
178 lines
6.8 KiB
TypeScript
178 lines
6.8 KiB
TypeScript
import type { CollectionConfig, FieldAccess } from 'payload'
|
||
import { standardEditor } from '@/fields/richText'
|
||
import { isAdmin } from '@/access/isAdmin'
|
||
import { revalidateAfterChange } from '@/hooks/revalidatePath'
|
||
import { getTariffs } from '@/lib/ezy'
|
||
|
||
const adminFieldAccess: FieldAccess = ({ req: { user } }) => user?.role === 'admin'
|
||
|
||
export const Tariffs: CollectionConfig = {
|
||
slug: 'tariffs',
|
||
labels: { singular: 'Квиток', plural: 'Квитки (тарифи)' },
|
||
admin: {
|
||
useAsTitle: 'last_synced_name',
|
||
defaultColumns: [
|
||
'display_name',
|
||
'last_synced_name',
|
||
'category_tag',
|
||
'last_synced_price',
|
||
'visible',
|
||
],
|
||
group: 'Каталог',
|
||
description:
|
||
'Керування квитками: показ/приховування, значки, описи, фото. Зніміть «Показувати на сайті», щоб приховати квиток (напр. якщо його ще немає в ezy).',
|
||
},
|
||
hooks: {
|
||
afterChange: [revalidateAfterChange],
|
||
beforeChange: [
|
||
async ({ data, originalDoc, req }) => {
|
||
const newEzyId = data.ezy_id as number | null | undefined
|
||
const oldEzyId = (originalDoc as Record<string, unknown> | undefined)?.ezy_id as
|
||
| number
|
||
| null
|
||
| undefined
|
||
// Only validate when admin user manually changes ezy_id (skip automated sync)
|
||
if (newEzyId && newEzyId !== oldEzyId && req.user) {
|
||
const all = await getTariffs()
|
||
const match = all.find((t) => t.id === newEzyId)
|
||
if (!match) throw new Error(`ezy_id ${newEzyId} не знайдено в ezy API`)
|
||
data.last_synced_name = match.name
|
||
data.last_synced_price = match.price
|
||
data.last_synced_at = new Date().toISOString()
|
||
}
|
||
return data
|
||
},
|
||
],
|
||
},
|
||
access: {
|
||
read: () => true,
|
||
create: isAdmin,
|
||
update: isAdmin,
|
||
delete: isAdmin,
|
||
},
|
||
fields: [
|
||
{
|
||
name: 'ezy_id',
|
||
type: 'number',
|
||
unique: true,
|
||
index: true,
|
||
admin: {
|
||
description:
|
||
'Встановлюється автоматично при синхронізації з ezy API. Порожнє — для квитків, доданих вручну.',
|
||
},
|
||
access: { update: adminFieldAccess },
|
||
},
|
||
{ name: 'display_name', type: 'text', label: 'Назва для сайту (перевизначення)' },
|
||
{
|
||
name: 'description',
|
||
type: 'richText',
|
||
editor: standardEditor,
|
||
label: 'Опис квитка',
|
||
},
|
||
{ name: 'image', type: 'upload', relationTo: 'media', label: 'Фото квитка' },
|
||
{ name: 'icon', type: 'text', label: 'Emoji або назва іконки' },
|
||
{
|
||
name: 'category_tag',
|
||
type: 'select',
|
||
required: true,
|
||
label: 'Категорія (вкладка каталогу)',
|
||
options: [
|
||
{ label: 'Основні зони', value: 'zone' },
|
||
{ label: 'Атракціони', value: 'attraction' },
|
||
{ label: 'Програми та екскурсії', value: 'program' },
|
||
{ label: 'Комбо', value: 'combo' },
|
||
// legacy-значення з ранніх синхронізацій (мапляться у вкладки в каталозі)
|
||
{ label: 'Dyno (legacy)', value: 'dyno' },
|
||
{ label: 'Dyvolis (legacy)', value: 'dyvolis' },
|
||
{ label: 'Maze (legacy)', value: 'maze' },
|
||
{ label: 'Family (legacy)', value: 'family' },
|
||
],
|
||
},
|
||
{
|
||
name: 'badgeLabel',
|
||
type: 'text',
|
||
label: 'Назва бейджа (перевизначення)',
|
||
admin: {
|
||
description: 'Якщо порожнє — береться з назви категорії (Зона / Атракціон / Екскурсія).',
|
||
},
|
||
},
|
||
{
|
||
name: 'infoChips',
|
||
type: 'array',
|
||
label: 'Значки (характеристики квитка)',
|
||
labels: { singular: 'Значок', plural: 'Значки' },
|
||
admin: {
|
||
description:
|
||
'Маленькі значки під назвою: тривалість, вартість за людину, вік тощо. Іконка + текст.',
|
||
},
|
||
fields: [
|
||
{
|
||
name: 'icon',
|
||
type: 'select',
|
||
required: true,
|
||
label: 'Іконка',
|
||
defaultValue: 'clock',
|
||
options: [
|
||
{ label: '⏱ Тривалість (годинник)', value: 'clock' },
|
||
{ label: '👤 Вартість за людину', value: 'person' },
|
||
{ label: '👨👩👧 Група / для родини', value: 'group' },
|
||
{ label: '♿ Вік / доступність', value: 'age' },
|
||
{ label: '∞ Необмежено', value: 'infinity' },
|
||
{ label: '🎯 Влучність / постріли', value: 'target' },
|
||
{ label: '🎟 Сеанс / квиток', value: 'session' },
|
||
{ label: '📏 Зріст / розмір', value: 'height' },
|
||
{ label: '🔧 Інтерактив', value: 'wrench' },
|
||
{ label: '🎓 Навчальний', value: 'graduation' },
|
||
{ label: '🌈 Спокійно', value: 'rainbow' },
|
||
{ label: '🚀 Драйв', value: 'send' },
|
||
{ label: '🥽 VR досвід', value: 'vr' },
|
||
],
|
||
},
|
||
{ name: 'label', type: 'text', required: true, label: 'Текст' },
|
||
],
|
||
},
|
||
{ name: 'sort', type: 'number', defaultValue: 0, admin: { position: 'sidebar' } },
|
||
{
|
||
name: 'visible',
|
||
type: 'checkbox',
|
||
label: 'Показувати на сайті',
|
||
defaultValue: true,
|
||
admin: {
|
||
position: 'sidebar',
|
||
description:
|
||
'Зніміть галочку, щоб приховати квиток із сайту (напр. якщо його ще немає в ezy).',
|
||
},
|
||
},
|
||
{
|
||
name: 'sync_ezy_ui',
|
||
type: 'ui',
|
||
label: 'Ручна синхронізація',
|
||
admin: {
|
||
components: {
|
||
Field: '/src/components/admin/SyncEzyButton',
|
||
},
|
||
},
|
||
},
|
||
{
|
||
name: 'last_synced_name',
|
||
type: 'text',
|
||
label: 'Назва з ezy API',
|
||
admin: { readOnly: true, description: 'Оновлюється автоматично при синхронізації' },
|
||
access: { update: adminFieldAccess },
|
||
},
|
||
{
|
||
name: 'last_synced_price',
|
||
type: 'number',
|
||
label: 'Ціна з ezy API (грн)',
|
||
admin: { readOnly: true },
|
||
access: { update: adminFieldAccess },
|
||
},
|
||
{
|
||
name: 'last_synced_at',
|
||
type: 'date',
|
||
label: 'Дата синхронізації',
|
||
admin: { readOnly: true, date: { pickerAppearance: 'dayAndTime' } },
|
||
access: { update: adminFieldAccess },
|
||
},
|
||
],
|
||
}
|