admin/features/events/edit/mapEventToEditWizardData.ts
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

146 lines
5.5 KiB
TypeScript

import type { EventWizardFormData } from '@/components/events/create/consumer/types'
import { createClientId } from '@/lib/createClientId'
import type { EventAgeRestriction, EventGenderRestriction } from '@/lib/eventAudience'
import type { CreatedEvent, EventRevisionPayload } from '@/services/events'
import type { EventFaq, EventMedia } from '@/services/eventDetail'
function splitIsoToDateAndMinutes(iso: string): { date: string; minutes: number } {
const d = new Date(iso)
return {
date: d.toISOString(),
minutes: d.getHours() * 60 + d.getMinutes(),
}
}
function mapMediaToStaged(media: EventMedia[]): EventWizardFormData['media'] {
return media
.filter((item) => item.mediaType === 'image')
.map((item, index) => ({
// نگه داشتن id سرور برای sync create/update/delete در ویرایش non-live
id: item.id,
url: item.url,
isPoster: item.isPoster,
isSquarePoster: Boolean(item.isSquarePoster),
sortOrder: item.sortOrder ?? index,
}))
}
function mapFaqsToStaged(faqs: EventFaq[]): EventWizardFormData['faqs'] {
return faqs.map((faq) => ({
id: faq.id,
question: faq.question,
answer: faq.answer,
}))
}
/**
* Maps an existing event (plus media/FAQs) into edit-wizard form values.
* Unlike clone, keeps the real title (no clone suffix).
*/
export function mapEventToEditWizardData(event: CreatedEvent, media: EventMedia[], faqs: EventFaq[]): EventWizardFormData {
const start = splitIsoToDateAndMinutes(String(event.startsAt))
const end = splitIsoToDateAndMinutes(String(event.endsAt))
return {
title: event.title,
slug: event.slug,
categoryId: String(event.categoryId),
shortDescription: event.shortDescription ?? '',
description: event.description ?? '',
media: mapMediaToStaged(media),
startDate: start.date,
startTime: start.minutes,
endDate: end.date,
endTime: end.minutes,
provinceId: String(event.provinceId),
cityId: String(event.cityId),
address: event.address ?? '',
lat: event.lat ?? 35.6892,
lng: event.lng ?? 51.389,
isFree: event.isFree,
price: event.price,
capacity: event.capacity,
reservedCapacity: event.reservedCapacity ?? 0,
genderRestriction: event.genderRestriction ?? 'open',
ageRestriction: event.ageRestriction ?? 'open',
cancellationFeePercent: event.cancellationFeePercent,
isDiscoverable: event.settings.isDiscoverable,
autoCreateGroup: event.settings.autoCreateGroup,
addressVisibility: event.settings.addressVisibility,
generalArea: event.settings.generalArea ?? '',
waitlistAutoOffer: event.settings.waitlistAutoOffer,
sendReviewRequestSms: event.settings.sendReviewRequestSms ?? true,
faqs: mapFaqsToStaged(faqs),
}
}
/**
* Prefer a pending revision snapshot when hydrating the edit wizard.
* Falls back to live media/FAQs when the revision omits them.
*/
export function mapRevisionPayloadToEditWizardData(
event: CreatedEvent,
payload: EventRevisionPayload,
fallbackMedia: EventMedia[],
fallbackFaqs: EventFaq[]
): EventWizardFormData {
const base = mapEventToEditWizardData(event, fallbackMedia, fallbackFaqs)
const startsAt = payload.startsAt ? String(payload.startsAt) : String(event.startsAt)
const endsAt = payload.endsAt ? String(payload.endsAt) : String(event.endsAt)
const start = splitIsoToDateAndMinutes(startsAt)
const end = splitIsoToDateAndMinutes(endsAt)
const mediaFromPayload = Array.isArray(payload.media)
? payload.media.map((item, index) => ({
id: createClientId(),
url: item.url,
isPoster: Boolean(item.isPoster),
isSquarePoster: Boolean(item.isSquarePoster),
sortOrder: item.sortOrder ?? index,
}))
: base.media
const faqsFromPayload = Array.isArray(payload.faqs)
? payload.faqs.map((faq) => ({
id: createClientId(),
question: faq.question,
answer: faq.answer,
}))
: base.faqs
const settings = payload.settings ?? {}
return {
...base,
title: payload.title ?? base.title,
slug: payload.slug ?? base.slug,
categoryId: payload.categoryId != null ? String(payload.categoryId) : base.categoryId,
shortDescription: payload.shortDescription ?? base.shortDescription,
description: payload.description ?? base.description,
media: mediaFromPayload,
startDate: start.date,
startTime: start.minutes,
endDate: end.date,
endTime: end.minutes,
provinceId: payload.provinceId != null ? String(payload.provinceId) : base.provinceId,
cityId: payload.cityId != null ? String(payload.cityId) : base.cityId,
address: payload.address ?? base.address,
lat: payload.lat ?? base.lat,
lng: payload.lng ?? base.lng,
isFree: payload.isFree ?? base.isFree,
price: payload.price ?? base.price,
capacity: payload.capacity ?? base.capacity,
genderRestriction: (payload.genderRestriction as EventGenderRestriction | undefined) ?? base.genderRestriction,
ageRestriction: (payload.ageRestriction as EventAgeRestriction | undefined) ?? base.ageRestriction,
cancellationFeePercent: payload.cancellationFeePercent ?? base.cancellationFeePercent,
isDiscoverable: settings.isDiscoverable ?? base.isDiscoverable,
autoCreateGroup: settings.autoCreateGroup ?? base.autoCreateGroup,
addressVisibility: settings.addressVisibility ?? base.addressVisibility,
generalArea: settings.generalArea ?? base.generalArea,
waitlistAutoOffer: settings.waitlistAutoOffer ?? base.waitlistAutoOffer,
sendReviewRequestSms: settings.sendReviewRequestSms ?? base.sendReviewRequestSms,
faqs: faqsFromPayload,
}
}