Files
guilan-ace-frontend/src/lib/event-ui.ts

168 lines
5.8 KiB
TypeScript

export type EventAvailabilityInput = {
status?: string | null;
registration_start_date?: string | null;
registration_end_date?: string | null;
start_time?: string | null;
end_time?: string | null;
capacity?: number | null;
registration_count?: number | null;
};
export function getEventPath(event: { id: number; slug?: string | null }) {
const segment = event.slug?.trim() || String(event.id);
return `/events/${encodeURIComponent(segment)}`;
}
export type EventRegistrationState =
| "open"
| "not_started"
| "closed"
| "full"
| "unavailable";
function timestamp(value?: string | null) {
if (!value) return null;
const parsed = new Date(value).getTime();
return Number.isFinite(parsed) ? parsed : null;
}
export function getEventRegistrationState(
event: EventAvailabilityInput,
now = Date.now(),
): { state: EventRegistrationState; remaining: number | null } {
if (event.status !== "published") {
return { state: "unavailable", remaining: null };
}
const endsAt = timestamp(event.registration_end_date);
if (endsAt !== null && now > endsAt) {
return { state: "closed", remaining: getRemainingCapacity(event) };
}
const startsAt = timestamp(event.registration_start_date);
if (startsAt !== null && now < startsAt) {
return { state: "not_started", remaining: getRemainingCapacity(event) };
}
const remaining = getRemainingCapacity(event);
if (remaining !== null && remaining <= 0) {
return { state: "full", remaining: 0 };
}
return { state: "open", remaining };
}
function getRemainingCapacity(event: EventAvailabilityInput) {
if (event.capacity == null || !Number.isFinite(Number(event.capacity))) {
return null;
}
const used = Number.isFinite(Number(event.registration_count))
? Number(event.registration_count)
: 0;
return Math.max(0, Number(event.capacity) - used);
}
export function getEventCardState(
event: EventAvailabilityInput,
now = Date.now(),
): "past" | "full" | "upcoming" {
const endsAt = timestamp(event.end_time);
if (event.status === "completed" || (endsAt !== null && endsAt < now)) {
return "past";
}
if (getEventRegistrationState(event, now).state === "full") {
return "full";
}
return "upcoming";
}
export function getEventBrowseLabel(event: EventAvailabilityInput, now = Date.now()) {
if (getEventCardState(event, now) === "past") return "برگزار شده";
if (getEventRegistrationState(event, now).state === "open") return "درحال برگزاری";
const startsAt = timestamp(event.start_time);
if (startsAt !== null && startsAt <= now) return "درحال برگزاری";
return "به‌زودی";
}
export function getEventRegistrationMessage(event: EventAvailabilityInput, now = Date.now()) {
if (getEventCardState(event, now) === "past") return "این رویداد برگزار شده است";
const registration = getEventRegistrationState(event, now);
if (registration.state === "not_started") return "ثبت‌نام هنوز آغاز نشده است";
if (registration.state === "closed") return "زمان ثبت‌نام به پایان رسیده است";
if (registration.state === "full") return "ظرفیت ثبت‌نام تکمیل شده است";
if (registration.state === "unavailable") return "امکان ثبت‌نام وجود ندارد";
if (registration.remaining === null) return "ثبت‌نام در حال انجام است";
return `${registration.remaining.toLocaleString("fa-IR")} نفر ظرفیت باقی‌مانده`;
}
export type RegistrationErrorKind =
| "already_registered"
| "not_started"
| "closed"
| "full"
| "unknown";
function normalizeApiMessage(message: unknown) {
return String(message ?? "")
.replace(/[يى]/g, "ی")
.replace(/\u200c/g, " ")
.replace(/\s+/g, " ")
.trim()
.toLocaleLowerCase();
}
export function classifyRegistrationError(message: unknown): RegistrationErrorKind {
const normalized = normalizeApiMessage(message);
if (normalized.includes("already registered") || normalized.includes("قبلا در این ایونت ثبت نام کرده اید")) {
return "already_registered";
}
if (normalized.includes("زمان ثبت نام هنوز آغاز نشده") || normalized.includes("registration has not started")) {
return "not_started";
}
if (normalized.includes("مهلت ثبت نام به پایان رسیده") || normalized.includes("registration period has ended")) {
return "closed";
}
if (normalized.includes("ظرفیت شرکت کنندگان تکمیل است") || normalized.includes("event is full")) {
return "full";
}
return "unknown";
}
export type PaymentOutcome = "paid" | "pending" | "failed" | "canceled" | "unknown";
export function getPaymentOutcome(status: unknown): PaymentOutcome {
const normalized = normalizeApiMessage(status);
if (["success", "paid", "ok", "پرداخت شده", "تایید شده"].includes(normalized)) {
return "paid";
}
if (["pending", "initiated", "init", "در انتظار", "در حال پردازش"].includes(normalized)) {
return "pending";
}
if (["canceled", "cancelled", "لغو شده"].includes(normalized)) {
return "canceled";
}
if (["failed", "failure", "ناموفق"].includes(normalized)) {
return "failed";
}
return "unknown";
}
export type RegistrationOutcome = "confirmed" | "pending" | "canceled" | "unknown";
export function getRegistrationOutcome(status: unknown): RegistrationOutcome {
const normalized = normalizeApiMessage(status);
if (["confirmed", "attended", "approved", "تایید شده"].includes(normalized)) {
return "confirmed";
}
if (["pending", "در انتظار"].includes(normalized)) {
return "pending";
}
if (["canceled", "cancelled", "لغو شده"].includes(normalized)) {
return "canceled";
}
return "unknown";
}