proje / apps /web /src /lib /acente-feedback.ts
rodopsin
feat: add Acente Portal with Performans, Sözleşmelerim, Duyurular, Geri Bildirim
3c841ba
Raw
History Blame Contribute Delete
6.5 kB
'use client';
export const ACENTE_FEEDBACK_KEY = 'ajet360.acente.feedback.v1';
export const ACENTE_FEEDBACK_EVENT = 'ajet360:acente-feedback';
export type FeedbackCategory = 'Şikayet' | 'Öneri' | 'Teşekkür' | 'Teknik Sorun' | 'Sözleşme/Komisyon';
export type FeedbackStatus = 'Gönderildi' | 'AJet İncelemede' | 'Yanıtlandı' | 'Kapatıldı';
export type FeedbackReply = {
at: string;
from: 'AJet' | 'Acente';
authorName: string;
body: string;
};
export type Feedback = {
id: string;
agencyCode: string;
category: FeedbackCategory;
subject: string;
body: string;
status: FeedbackStatus;
createdAt: string;
replies: FeedbackReply[];
};
const SEED_FEEDBACKS: Feedback[] = [
{
id: 'fb-demo-001',
agencyCode: 'AJ-IST-001',
category: 'Sözleşme/Komisyon',
subject: 'Sözleşme komisyon yapısı hakkında',
body: 'Yeni dönem sözleşmesinde Q3 için komisyon dilimlerinin yeniden değerlendirilmesini talep ediyorum. Mevcut yapıda %12 komisyon hedef hacmin altında kalıyor; %14\'e çıkarılması mümkün mü?',
status: 'Yanıtlandı',
createdAt: '2026-04-18T09:30:00.000Z',
replies: [
{
at: '2026-04-19T11:00:00.000Z',
from: 'AJet',
authorName: 'Mehmet Yılmaz · Satış Müdürü',
body: 'Talebinizi inceledik. Q3 itibarıyla %14 komisyon dilimine geçişiniz için gerekli değerlendirme süreci başlatıldı. 7 iş günü içinde yeni sözleşme taslağı tarafınıza iletilecektir.',
},
{
at: '2026-04-19T14:20:00.000Z',
from: 'Acente',
authorName: 'Istanbul Jet Travel',
body: 'Hızlı dönüş için teşekkürler, taslağı bekliyoruz.',
},
],
},
{
id: 'fb-demo-002',
agencyCode: 'AJ-IST-001',
category: 'Teknik Sorun',
subject: 'Crane Pax bakiye yükleme yavaş',
body: 'Son 1 haftadır Crane Pax üzerinden bakiye yüklemesi 5–7 dakika sürüyor. Önceden anında yansıyordu. İncelenmesi mümkün mü?',
status: 'AJet İncelemede',
createdAt: '2026-04-23T08:45:00.000Z',
replies: [
{
at: '2026-04-23T10:15:00.000Z',
from: 'AJet',
authorName: 'Sistem Destek',
body: 'Geri bildiriminiz alındı. Crane Pax entegrasyon ekibimizle paylaşıldı, 24 saat içinde dönüş yapılacaktır.',
},
],
},
{
id: 'fb-demo-003',
agencyCode: 'AJ-ANK-001',
category: 'Teşekkür',
subject: 'Yeni eğitim için teşekkürler',
body: 'Geçen hafta düzenlenen PNR v2 eğitimi çok faydalıydı, ekipçe katıldık. Bu tip eğitimlerin devamını rica ediyoruz.',
status: 'Yanıtlandı',
createdAt: '2026-04-15T16:00:00.000Z',
replies: [
{
at: '2026-04-16T09:00:00.000Z',
from: 'AJet',
authorName: 'Eğitim Koordinatörü',
body: 'Geri dönüşünüz için teşekkür ederiz. Mayıs ayında Crane Pax kullanımı eğitimi planlıyoruz, davetiye e-postanız gelecek.',
},
],
},
];
function canUseStorage() {
return typeof window !== 'undefined' && Boolean(window.localStorage);
}
function readAll(): Feedback[] {
if (!canUseStorage()) return SEED_FEEDBACKS;
const raw = window.localStorage.getItem(ACENTE_FEEDBACK_KEY);
if (!raw) {
return SEED_FEEDBACKS;
}
try {
const parsed = JSON.parse(raw) as Feedback[];
if (!Array.isArray(parsed)) return SEED_FEEDBACKS;
const seedIds = new Set(SEED_FEEDBACKS.map((s) => s.id));
const userOnes = parsed.filter((f) => !seedIds.has(f.id));
return [...userOnes, ...SEED_FEEDBACKS];
} catch {
return SEED_FEEDBACKS;
}
}
function writeAll(items: Feedback[]) {
if (!canUseStorage()) return;
const seedIds = new Set(SEED_FEEDBACKS.map((s) => s.id));
const persisted = items.filter((f) => !seedIds.has(f.id));
const seedOverrides = items.filter(
(f) => seedIds.has(f.id) && hasNewReplies(f),
);
window.localStorage.setItem(
ACENTE_FEEDBACK_KEY,
JSON.stringify([...persisted, ...seedOverrides]),
);
window.dispatchEvent(new Event(ACENTE_FEEDBACK_EVENT));
}
function hasNewReplies(f: Feedback): boolean {
const seed = SEED_FEEDBACKS.find((s) => s.id === f.id);
if (!seed) return false;
return f.replies.length > seed.replies.length || f.status !== seed.status;
}
export function listFeedbacks(agencyCode: string): Feedback[] {
return readAll()
.filter((f) => f.agencyCode === agencyCode)
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
}
export function getFeedbackById(id: string): Feedback | undefined {
return readAll().find((f) => f.id === id);
}
export type CreateFeedbackInput = {
agencyCode: string;
category: FeedbackCategory;
subject: string;
body: string;
};
export function createFeedback(input: CreateFeedbackInput): Feedback {
const now = new Date().toISOString();
const ts = now.replace(/\D/g, '').slice(0, 14);
const fb: Feedback = {
id: `fb-${ts}-${Math.random().toString(36).slice(2, 6)}`,
agencyCode: input.agencyCode,
category: input.category,
subject: input.subject,
body: input.body,
status: 'Gönderildi',
createdAt: now,
replies: [],
};
writeAll([fb, ...readAll()]);
return fb;
}
export function appendReply(id: string, reply: Omit<FeedbackReply, 'at'> & { at?: string }): Feedback | undefined {
const all = readAll();
const idx = all.findIndex((f) => f.id === id);
if (idx < 0) return undefined;
const at = reply.at ?? new Date().toISOString();
const next = { ...all[idx]!, replies: [...all[idx]!.replies, { ...reply, at }] };
if (reply.from === 'AJet') {
next.status = 'Yanıtlandı';
}
const arr = [...all];
arr[idx] = next;
writeAll(arr);
return next;
}
export function transitionStatus(id: string, status: FeedbackStatus): Feedback | undefined {
const all = readAll();
const idx = all.findIndex((f) => f.id === id);
if (idx < 0) return undefined;
const next = { ...all[idx]!, status };
const arr = [...all];
arr[idx] = next;
writeAll(arr);
return next;
}
export function simulateAjetAutoReply(id: string) {
setTimeout(() => {
appendReply(id, {
from: 'AJet',
authorName: 'AJet Acente İlişkileri',
body: 'Geri bildiriminiz alındı. Konuyla ilgili 24 saat içinde dönüş yapılacaktır. Acil durumlar için temsilcinizle iletişime geçebilirsiniz.',
});
}, 8000);
setTimeout(() => {
transitionStatus(id, 'AJet İncelemede');
}, 1500);
}