Files
MotionWebStudio/pages/Admin.tsx
2025-12-22 17:59:43 +01:00

757 lines
40 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { useAuth } from '../context/AuthContext';
import { useNavigate } from 'react-router-dom';
import {
ChevronLeft, RefreshCw, BarChart2, Users, ShoppingCart, Package,
FileText, Globe, Sparkles, MessageSquare, AlertCircle, CheckCircle,
XCircle, Clock, Activity, Save, Mail, Rocket, Edit3, Bell,
AlertTriangle, Archive, Send, Layout, History
} from 'lucide-react';
import { Button } from '../components/Button';
import { supabase, isSupabaseConfigured } from '../lib/supabaseClient';
import { ProductPackage } from '../types';
import { defaultPlans } from '../lib/defaultPlans';
import { generateOrderPrompts, PromptResult } from '../lib/promptGenerator';
interface AdminUser {
id: string;
email: string;
first_name?: string;
last_name?: string;
created_at: string;
role: string;
}
interface OrderHistoryEntry {
id: string;
status: string;
changed_at: string;
}
interface EmailLogEntry {
id: string;
email_type: string;
sent_at: string;
body?: string;
}
interface AdminOrder {
id: string;
user_id: string;
displayId: string;
customer: string;
email: string;
package: string;
status: string;
date: string;
amount: string;
details?: any;
history?: OrderHistoryEntry[];
emailLogs?: EmailLogEntry[];
}
const getEmailTemplate = (type: string, data: { customer: string, package: string, demoUrl?: string }) => {
const baseStyle = "font-family: 'Inter', Helvetica, Arial, sans-serif; line-height: 1.6; color: #1a1a1a; max-width: 600px; margin: 0 auto; padding: 40px 20px; border: 1px solid #f0f0f0; border-radius: 24px; background-color: #ffffff;";
const headerStyle = "color: #7c3aed; font-size: 28px; font-weight: 800; margin-bottom: 24px; letter-spacing: -0.02em; border-bottom: 1px solid #f0f0f0; padding-bottom: 20px;";
const footerStyle = "margin-top: 40px; padding-top: 24px; border-top: 1px solid #f0f0f0; font-size: 13px; color: #94a3b8;";
const buttonStyle = "display: inline-block; background-color: #7c3aed; color: #ffffff !important; padding: 14px 28px; text-decoration: none; border-radius: 12px; font-weight: 700; margin: 20px 0; font-size: 15px;";
const templates: Record<string, { subject: string, body: string }> = {
'Fejlesztés megkezdése': {
subject: `Projekt Indítása: Megkezdtük a fejlesztést - ${data.package}`,
body: `<div style="${baseStyle}">
<div style="${headerStyle}">MotionWeb</div>
<p>Kedves <strong>${data.customer}</strong>!</p>
<p>Örömmel értesítjük, hogy a <strong>${data.package}</strong> csomagjához tartozó fejlesztési folyamat a mai napon hivatalosan is elindult.</p>
<p>Szakembereink elkezdték a weboldal vázlatának és design elemeinek kidolgozását. Amint elkészülünk az első megtekinthető demó verzióval, azonnal jelentkezünk a hozzáférési linkkel.</p>
<p>Köszönjük a bizalmát!</p>
<div style="${footerStyle}">
Üdvözlettel,<br>
<strong>Balogh Bence</strong><br>
MotionWeb Stúdió | motionweb.hu
</div>
</div>`
},
'Demó oldal elkészült': {
subject: `Elkészült a weboldal demó verziója - ${data.package}`,
body: `<div style="${baseStyle}">
<div style="${headerStyle}">MotionWeb</div>
<p>Kedves <strong>${data.customer}</strong>!</p>
<p>Jó hírünk van: elkészültünk weboldala első, interaktív bemutató verziójával!</p>
<p>A demó oldalt az alábbi biztonságos linken tudja megtekinteni:</p>
<div style="text-align: center;">
<a href="${data.demoUrl || '#'}" style="${buttonStyle}">DEMÓ MEGTEKINTÉSE</a>
</div>
<p>Kérjük, nézze át az oldalt, és a MotionWeb ügyfélkapujában a <strong>Rendeléseim</strong> menüpont alatt küldje el visszajelzését, hogy rögzíthessük az esetleges módosítási kéréseit.</p>
<div style="${footerStyle}">
Üdvözlettel,<br>
<strong>Balogh Bence</strong><br>
MotionWeb Stúdió | motionweb.hu
</div>
</div>`
},
'Módosítások fejlesztése': {
subject: `Visszajelzés rögzítve: Módosítások folyamatban`,
body: `<div style="${baseStyle}">
<div style="${headerStyle}">MotionWeb</div>
<p>Kedves <strong>${data.customer}</strong>!</p>
<p>Köszönjük részletes visszajelzését! A kért módosításokat rögzítettük és szakembereink már dolgoznak azok átvezetésén.</p>
<p>Amint a frissített verzió megtekinthető lesz, e-mailben ismét értesíteni fogjuk.</p>
<div style="${footerStyle}">
Üdvözlettel,<br>
<strong>Balogh Bence</strong><br>
MotionWeb Stúdió | motionweb.hu
</div>
</div>`
},
'1 hete nincs visszajelzés': {
subject: `Emlékeztető: Visszajelzés várható a demó oldallal kapcsolatban`,
body: `<div style="${baseStyle}">
<div style="${headerStyle}">MotionWeb</div>
<p>Kedves <strong>${data.customer}</strong>!</p>
<p>Szeretnénk emlékeztetni, hogy egy héttel ezelőtt küldtük el Önnek a weboldal demó verzióját.</p>
<p>Annak érdekében, hogy tartsuk a fejlesztési ütemtervet, kérjük, amint ideje engedi, nézze át a tervet és küldje el véleményét az ügyfélkapun keresztül.</p>
<div style="${footerStyle}">
Üdvözlettel,<br>
<strong>Balogh Bence</strong><br>
MotionWeb Stúdió | motionweb.hu
</div>
</div>`
},
'2 hete nincs visszajelzés': {
subject: `Fontos: Továbblépéshez szükséges visszajelzés hiánya`,
body: `<div style="${baseStyle}">
<div style="${headerStyle}">MotionWeb</div>
<p>Kedves <strong>${data.customer}</strong>!</p>
<p>Szeretnénk felhívni figyelmét, hogy weboldala demó verziója már két hete várakozik az Ön jóváhagyására.</p>
<p>Amennyiben elégedett a látottakkal, kérjük, jelezze azt a felületen a véglegesítés megkezdéséhez. Bármilyen kérdés vagy észrevétel esetén állunk rendelkezésére.</p>
<div style="${footerStyle}">
Üdvözlettel,<br>
<strong>Balogh Bence</strong><br>
MotionWeb Stúdió | motionweb.hu
</div>
</div>`
},
'Projekt lezárva (Nincs válasz)': {
subject: `Értesítés: Projekt adminisztratív lezárása - ${data.package}`,
body: `<div style="${baseStyle}">
<div style="${headerStyle}">MotionWeb</div>
<p>Kedves <strong>${data.customer}</strong>!</p>
<p>Sajnálattal értesítjük, hogy mivel az elmúlt időszakban többszöri megkeresésünkre sem érkezett válasz, a <strong>${data.package}</strong> projektet a mai napon adminisztratív okokból lezártnak tekintjük.</p>
<p>Amennyiben a jövőben folytatni szeretné a fejlesztést, kérjük, vegye fel velünk a kapcsolatot egy új ütemterv egyeztetése érdekében.</p>
<div style="${footerStyle}">
Üdvözlettel,<br>
<strong>Balogh Bence</strong><br>
MotionWeb Stúdió | motionweb.hu
</div>
</div>`
},
'Végleges oldal elérhető': {
subject: `Gratulálunk! Weboldala elkészült és élesítésre került`,
body: `<div style="${baseStyle}">
<div style="${headerStyle}">MotionWeb</div>
<p>Kedves <strong>${data.customer}</strong>!</p>
<p>Örömmel jelentjük, hogy a <strong>${data.package}</strong> projektünk sikeresen befejeződött!</p>
<p>A weboldalt élesítettük, így az mostantól minden látogató számára elérhető a végleges domain címen.</p>
<p>Köszönjük, hogy minket választott digitális partnerének. Kérjük, amennyiben elégedett volt a munkánkkal, ajánljon minket ismerőseinek is!</p>
<div style="${footerStyle}">
Üdvözlettel,<br>
<strong>Balogh Bence</strong><br>
MotionWeb Stúdió | motionweb.hu
</div>
</div>`
}
};
return templates[type] || { subject: 'Értesítés a MotionWeb-től', body: 'Üzenete érkezett a MotionWeb Stúdiótól.' };
};
export const Admin: React.FC = () => {
const { user, isAdmin, loading } = useAuth();
const navigate = useNavigate();
const [activeTab, setActiveTab] = useState<'overview' | 'users' | 'orders' | 'plans'>('overview');
const [users, setUsers] = useState<AdminUser[]>([]);
const [orders, setOrders] = useState<AdminOrder[]>([]);
const [plans, setPlans] = useState<ProductPackage[]>([]);
const [loadingData, setLoadingData] = useState(false);
const [visitorStats, setVisitorStats] = useState({ month: 0, week: 0 });
const [statusUpdating, setStatusUpdating] = useState<string | null>(null);
const [planSaving, setPlanSaving] = useState<string | null>(null);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const [viewOrder, setViewOrder] = useState<AdminOrder | null>(null);
const [generatedPrompts, setGeneratedPrompts] = useState<PromptResult[]>([]);
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
const [demoUrlInput, setDemoUrlInput] = useState('');
const [savingDemoUrl, setSavingDemoUrl] = useState(false);
const [emailSending, setEmailSending] = useState<string | null>(null);
useEffect(() => {
if (!loading && !isAdmin) {
navigate('/dashboard');
}
}, [isAdmin, loading, navigate]);
useEffect(() => {
if (isAdmin) fetchAdminData();
}, [isAdmin]);
const fetchAdminData = async () => {
setLoadingData(true);
setErrorMsg(null);
if (!isSupabaseConfigured) {
setPlans(defaultPlans);
setLoadingData(false);
return;
}
try {
const { data: profiles, error: pErr } = await supabase.from('profiles').select('*');
if (pErr) throw pErr;
let rolesData: any[] = [];
try {
const { data: roles } = await supabase.from('roles').select('*');
rolesData = roles || [];
} catch (e) {
console.warn("Could not fetch roles table.");
}
setUsers(profiles.map(p => ({
id: p.id,
email: p.email || 'N/A',
first_name: p.first_name,
last_name: p.last_name,
created_at: p.created_at,
role: rolesData.find(r => r.id === p.id)?.role || (p.email === 'motionstudiohq@gmail.com' ? 'admin' : 'user')
})));
const { data: oData, error: oErr } = await supabase.from('orders').select('*').order('created_at', { ascending: false });
if (oErr) throw oErr;
setOrders(oData.map(o => ({
...o,
displayId: o.id.substring(0, 8).toUpperCase(),
customer: o.customer_name,
email: o.customer_email
})));
const { data: plData } = await supabase.from('plans').select('*').order('price', { ascending: true });
setPlans(plData && plData.length > 0 ? plData : defaultPlans);
const { count: mCount } = await supabase.from('page_visits').select('*', { count: 'exact', head: true });
setVisitorStats({ month: mCount || 0, week: Math.floor((mCount || 0) / 4) });
} catch (err: any) {
setErrorMsg(err.message || "Hiba történt az adatok lekérésekor.");
} finally {
setLoadingData(false);
}
};
const fetchOrderHistory = async (orderId: string) => {
if (!isSupabaseConfigured) return [];
try {
const { data } = await supabase
.from('order_status_history')
.select('*')
.eq('order_id', orderId)
.order('changed_at', { ascending: false });
return data || [];
} catch (e) {
return [];
}
};
const fetchEmailLogs = async (orderId: string) => {
if (!isSupabaseConfigured) return [];
try {
// Megpróbáljuk lekérni, de ha hiányzik oszlop vagy tábla, hibatűrően kezeljük
const { data, error } = await supabase
.from('email_log')
.select('*')
.eq('order_id', orderId)
.order('sent_at', { ascending: false });
if (error) {
console.error("Email log fetch error:", error.message);
return [];
}
return data || [];
} catch (e) {
console.warn("Could not fetch email_log table.");
return [];
}
};
const handleStatusChange = async (orderId: string, newStatus: string) => {
if (statusUpdating) return;
setStatusUpdating(orderId);
try {
if (isSupabaseConfigured) {
const { error } = await supabase.from('orders').update({ status: newStatus }).eq('id', orderId);
if (error) throw error;
await supabase.from('order_status_history').insert({ order_id: orderId, status: newStatus });
const newHist = await fetchOrderHistory(orderId);
setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: newStatus } : o));
if (viewOrder && viewOrder.id === orderId) {
setViewOrder({ ...viewOrder, status: newStatus, history: newHist });
}
} else {
setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: newStatus } : o));
if (viewOrder && viewOrder.id === orderId) {
setViewOrder({ ...viewOrder, status: newStatus });
}
}
} catch (e: any) {
alert("Hiba: " + e.message);
} finally {
setStatusUpdating(null);
}
};
const handleUpdateDemoUrl = async () => {
if (!viewOrder || savingDemoUrl) return;
setSavingDemoUrl(true);
const orderId = viewOrder.id;
const targetStatus = 'pending_feedback';
const updatedDetails = { ...(viewOrder.details || {}), demoUrl: demoUrlInput };
try {
if (isSupabaseConfigured) {
const { error } = await supabase.from('orders').update({
details: updatedDetails,
status: targetStatus
}).eq('id', orderId);
if (error) throw error;
await supabase.from('order_status_history').insert({ order_id: orderId, status: targetStatus });
const newHist = await fetchOrderHistory(orderId);
setOrders(prev => prev.map(o => o.id === orderId ? { ...o, status: targetStatus, details: updatedDetails } : o));
setViewOrder({ ...viewOrder, status: targetStatus, details: updatedDetails, history: newHist });
if (demoUrlInput.startsWith('http')) {
handleSendEmail('Demó oldal elkészült');
}
alert(`Sikeres mentés és visszajelzés kérése!`);
} else {
setViewOrder({ ...viewOrder, details: updatedDetails });
alert("Demo URL mentve.");
}
} catch (e: any) {
alert("Hiba: " + e.message);
} finally {
setSavingDemoUrl(false);
}
};
const handleViewDetails = async (order: AdminOrder) => {
setLoadingData(true);
const history = await fetchOrderHistory(order.id);
const emailLogs = await fetchEmailLogs(order.id);
setViewOrder({ ...order, history, emailLogs });
setDemoUrlInput(order.details?.demoUrl || '');
setGeneratedPrompts([]);
setLoadingData(false);
};
const handleSendEmail = async (emailType: string) => {
if (!viewOrder || emailSending) return;
setEmailSending(emailType);
try {
const template = getEmailTemplate(emailType, {
customer: viewOrder.customer,
package: viewOrder.package,
demoUrl: demoUrlInput || viewOrder.details?.demoUrl
});
if (isSupabaseConfigured) {
// MEGHÍVJUK A FÜGGVÉNYT
const response = await supabase.functions.invoke('resend', {
body: {
to: viewOrder.email,
subject: template.subject,
html: template.body
}
});
// Supabase hiba kezelése (az invoke hiba objektumot ad vissza, ha a hívás sikertelen)
if (response.error) {
let errorDetail = response.error.message;
if (errorDetail.includes('API kulcs hiányzik')) {
throw new Error("HIÁNYZIK A BEÁLLÍTÁS: A Supabase Dashboardon az Edge Functions -> Secrets menüpontban fel kell venni a RESEND_API_KEY kulcsot!");
}
throw new Error(errorDetail);
}
// A függvény által visszaadott egyedi hiba (ha van)
if (response.data && response.data.error) {
throw new Error(response.data.error);
}
// SIKERES KÜLDÉS NAPLÓZÁSA (Hibatűrő módon)
try {
const { error: logError } = await supabase.from('email_log').insert({
order_id: viewOrder.id,
email_type: emailType,
body: template.body
});
if (logError) {
console.error("Adatbázis naplózási hiba (lehet, hogy hiányzik a 'body' oszlop):", logError.message);
// Itt nem dobunk hibát, mert az email már kiment! Csak logolunk a konzolra.
}
} catch (dbErr) {
console.error("Váratlan hiba az adatbázisba íráskor:", dbErr);
}
const newLogs = await fetchEmailLogs(viewOrder.id);
setViewOrder({ ...viewOrder, emailLogs: newLogs });
alert(`Sikeres küldés! Az értesítőt kiküldtük ${viewOrder.email} címre.`);
} else {
alert(`DEMO MÓD: Tárgy: ${template.subject}`);
}
} catch (e: any) {
console.error("Hiba az e-mail folyamatban:", e);
alert("Hiba az e-mail küldésekor:\n\n" + e.message);
} finally {
setEmailSending(null);
}
};
const StatusBadge = ({ status }: { status: string }) => {
const configs: any = {
new: { label: 'Új', color: 'bg-blue-100 text-blue-800' },
in_progress: { label: 'Folyamatban', color: 'bg-yellow-100 text-yellow-800' },
pending_feedback: { label: 'Visszajelzés', color: 'bg-purple-100 text-purple-800' },
completed: { label: 'Kész', color: 'bg-green-100 text-green-800' },
cancelled: { label: 'Törölve', color: 'bg-gray-100 text-gray-800' }
};
const c = configs[status] || configs.new;
return <span className={`text-[10px] uppercase font-black px-2 py-0.5 rounded-full ${c.color}`}>{c.label}</span>;
};
if (loading) return <div className="min-h-screen flex items-center justify-center font-bold text-primary">Admin felület...</div>;
if (!isAdmin) return null;
return (
<div className="pt-24 bg-gray-50 min-h-screen pb-20 font-sans text-gray-900">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex flex-col md:flex-row justify-between items-start md:items-end mb-8 gap-4">
<div>
<div className="flex items-center gap-2 mb-2">
<button onClick={() => navigate('/dashboard')} className="p-2 hover:bg-gray-200 rounded-full transition-colors bg-white shadow-sm border border-gray-100">
<ChevronLeft className="w-5 h-5 text-gray-500" />
</button>
<span className="text-[10px] font-black text-red-600 bg-red-50 px-3 py-1 rounded-full border border-red-100 uppercase tracking-widest">Admin Access</span>
</div>
<h1 className="text-4xl font-black tracking-tighter text-gray-900">Vezérlőpult</h1>
</div>
<Button variant="white" size="sm" onClick={fetchAdminData} disabled={loadingData} className="border-gray-200 shadow-sm font-bold uppercase text-[10px] tracking-widest">
<RefreshCw className={`w-4 h-4 mr-2 ${loadingData ? 'animate-spin' : ''}`} /> Adatok frissítése
</Button>
</div>
<div className="flex gap-2 mb-10 bg-white p-2 rounded-[24px] border border-gray-100 shadow-sm w-fit">
{[
{ id: 'overview', label: 'Statisztika', icon: BarChart2 },
{ id: 'users', label: 'Felhasználók', icon: Users },
{ id: 'orders', label: 'Rendelések', icon: ShoppingCart },
{ id: 'plans', label: 'Csomagok', icon: Package }
].map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id as any)}
className={`flex items-center gap-2 px-6 py-3 rounded-2xl text-sm font-bold transition-all ${
activeTab === tab.id ? 'bg-primary text-white shadow-lg' : 'text-gray-500 hover:bg-gray-50'
}`}
>
<tab.icon className="w-4 h-4" />
{tab.label}
</button>
))}
</div>
<div className="animate-fade-in">
{activeTab === 'overview' && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="bg-white p-10 rounded-[32px] border border-gray-100 shadow-sm">
<p className="text-xs font-bold text-gray-400 uppercase mb-3 tracking-widest">Látogatók</p>
<h3 className="text-5xl font-black text-gray-900">{visitorStats.month}</h3>
</div>
<div className="bg-white p-10 rounded-[32px] border border-gray-100 shadow-sm">
<p className="text-xs font-bold text-gray-400 uppercase mb-3 tracking-widest">Rendelések</p>
<h3 className="text-5xl font-black text-gray-900">{orders.length}</h3>
</div>
<div className="bg-white p-10 rounded-[32px] border border-gray-100 shadow-sm">
<p className="text-xs font-bold text-gray-400 uppercase mb-3 tracking-widest">Regisztrációk</p>
<h3 className="text-5xl font-black text-gray-900">{users.length}</h3>
</div>
</div>
)}
{activeTab === 'users' && (
<div className="bg-white rounded-[32px] border border-gray-100 overflow-hidden shadow-sm">
<table className="w-full text-left">
<thead className="bg-gray-50/50 border-b border-gray-100">
<tr className="text-xs font-bold text-gray-400 uppercase tracking-widest">
<th className="px-10 py-6">Felhasználó Neve</th>
<th className="px-10 py-6">E-mail</th>
<th className="px-10 py-6">Szint</th>
<th className="px-10 py-6 text-right">Regisztráció</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-50 font-medium">
{users.map(u => (
<tr key={u.id} className="hover:bg-gray-50/50 transition-colors">
<td className="px-10 py-6">
<span className="font-black text-gray-900">{u.last_name || ''} {u.first_name || ''}</span>
{(!u.last_name && !u.first_name) && <span className="text-gray-400 italic">Nincs megadva</span>}
</td>
<td className="px-10 py-6">
<span className="text-sm font-bold text-gray-900">{u.email}</span>
</td>
<td className="px-10 py-6">
<span className={`px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-widest ${u.role === 'admin' ? 'bg-red-100 text-red-700' : 'bg-gray-100 text-gray-600'}`}>{u.role}</span>
</td>
<td className="px-10 py-6 text-right text-sm text-gray-500">{new Date(u.created_at).toLocaleDateString('hu-HU')}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{activeTab === 'orders' && (
<div className="bg-white rounded-[32px] border border-gray-100 overflow-hidden shadow-sm">
<table className="w-full text-left">
<thead className="bg-gray-50/50 border-b border-gray-100 text-xs font-bold text-gray-400 uppercase tracking-widest">
<tr>
<th className="px-10 py-6">Ügyfél</th>
<th className="px-10 py-6">Csomag</th>
<th className="px-10 py-6">Státusz</th>
<th className="px-10 py-6 text-right">Művelet</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-50">
{orders.map(o => (
<tr key={o.id} className="hover:bg-gray-50/30 transition-colors">
<td className="px-10 py-6">
<p className="font-black leading-tight text-gray-900">{o.customer}</p>
<p className="text-[11px] text-gray-500 font-bold">{o.email}</p>
</td>
<td className="px-10 py-6 text-sm font-bold text-primary">{o.package}</td>
<td className="px-10 py-6"><StatusBadge status={o.status} /></td>
<td className="px-10 py-6 text-right">
<Button size="sm" variant="outline" onClick={() => handleViewDetails(o)} className="rounded-2xl border-gray-200 px-6 font-black uppercase text-[10px] tracking-widest">Kezelés</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
{activeTab === 'plans' && (
<div className="grid grid-cols-1 lg:grid-cols-3 gap-10">
{plans.map((p, i) => (
<div key={p.id} className="bg-white p-10 rounded-[32px] border border-gray-100 shadow-sm flex flex-col">
<input type="text" value={p.name} onChange={e => { const n = [...plans]; n[i].name = e.target.value; setPlans(n); }} className="text-xl font-black border-b border-gray-200 focus:border-primary outline-none w-full pb-1 text-black bg-white mb-6" />
<textarea rows={8} value={p.features.join('\n')} onChange={e => { const n = [...plans]; n[i].features = e.target.value.split('\n'); setPlans(n); }} className="text-sm p-4 bg-white rounded-2xl border border-gray-200 w-full resize-none outline-none focus:ring-4 focus:ring-primary/5 focus:border-primary font-medium text-black mb-8" />
<Button fullWidth onClick={() => { setPlanSaving(p.id); supabase.from('plans').upsert(p).then(() => { setPlanSaving(null); alert("Mentve!"); }); }} disabled={planSaving === p.id}>
{planSaving === p.id ? <RefreshCw className="w-5 h-5 animate-spin" /> : <span className="flex items-center gap-2"><Save className="w-5 h-5" /> Mentés</span>}
</Button>
</div>
))}
</div>
)}
</div>
{viewOrder && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/80 backdrop-blur-sm overflow-y-auto">
<div className="bg-white rounded-[40px] shadow-2xl w-full max-w-6xl max-h-[95vh] overflow-hidden flex flex-col animate-fade-in-up border border-white/10">
<div className="px-10 py-8 border-b border-gray-100 flex justify-between items-center bg-gray-50/50">
<div className="flex items-center gap-6">
<div className="w-14 h-14 bg-white rounded-3xl shadow-md flex items-center justify-center text-primary border border-gray-100">
<ShoppingCart className="w-7 h-7" />
</div>
<div>
<h2 className="text-3xl font-black tracking-tighter text-gray-900">{viewOrder.customer}</h2>
<p className="text-sm text-gray-400 font-bold uppercase tracking-widest">{viewOrder.email} ID: {viewOrder.displayId}</p>
</div>
</div>
<button onClick={() => setViewOrder(null)} className="p-3 hover:bg-gray-200 rounded-full transition-all bg-white border border-gray-100">
<XCircle className="w-7 h-7 text-gray-400" />
</button>
</div>
<div className="flex-grow overflow-y-auto p-10 lg:p-14 space-y-16 bg-gray-50/30">
<div className="grid md:grid-cols-12 gap-16">
<div className="md:col-span-8 space-y-12">
<section>
<h3 className="text-[11px] font-black text-gray-400 uppercase tracking-[0.3em] mb-6 flex items-center gap-2"><FileText className="w-4 h-4" /> Projekt részletei</h3>
<div className="bg-white p-8 rounded-[32px] border border-gray-100 shadow-sm text-black space-y-6">
<div className="grid grid-cols-2 gap-4">
<div><p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-1">Csomag</p><p className="font-bold text-primary">{viewOrder.package}</p></div>
<div><p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-1">Összeg</p><p className="font-bold text-gray-900">{viewOrder.amount}</p></div>
</div>
<div><p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-1">Domain</p><p className="font-bold bg-gray-50 px-3 py-1.5 rounded-lg border border-gray-100 inline-block text-gray-900">{viewOrder.details?.domainName || 'Nincs'}</p></div>
<div className="pt-6 border-t border-gray-200"><p className="text-[10px] font-black text-gray-400 uppercase tracking-widest mb-2">Leírás:</p><p className="italic leading-relaxed font-medium text-gray-800">"{viewOrder.details?.description || 'Nincs'}"</p></div>
</div>
</section>
<section>
<h3 className="text-[11px] font-black text-gray-400 uppercase tracking-[0.3em] mb-6">Státusz módosítása</h3>
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
{[
{ id: 'new', label: 'Új' },
{ id: 'in_progress', label: 'Folyamatban' },
{ id: 'pending_feedback', label: 'Visszajelzés' },
{ id: 'completed', label: 'Kész' },
{ id: 'cancelled', label: 'Törölve' }
].map(s => (
<button
key={s.id}
onClick={() => handleStatusChange(viewOrder.id, s.id)}
disabled={statusUpdating === viewOrder.id}
className={`px-4 py-3 rounded-2xl text-[10px] font-black uppercase tracking-widest transition-all border-2 flex items-center justify-between gap-2 ${viewOrder.status === s.id ? 'bg-gray-900 text-white border-gray-900 shadow-xl' : 'bg-white text-gray-500 border-gray-100 hover:border-primary/40'}`}
>
<span>{s.label}</span>
{viewOrder.status === s.id && <CheckCircle className="w-3 h-3 text-green-400" />}
</button>
))}
</div>
</section>
<section className="space-y-8">
<div>
<h3 className="text-[11px] font-black text-gray-400 uppercase tracking-[0.3em] mb-6 flex items-center gap-2"><Mail className="w-4 h-4" /> E-mail értesítők (Közvetlen sablonok)</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{[
{ label: 'Fejlesztés megkezdése', icon: Rocket },
{ label: 'Demó oldal elkészült', icon: Layout },
{ label: 'Módosítások fejlesztése', icon: Edit3 },
{ label: '1 hete nincs visszajelzés', icon: Bell },
{ label: '2 hete nincs visszajelzés', icon: AlertTriangle },
{ label: 'Projekt lezárva (Nincs válasz)', icon: Archive },
{ label: 'Végleges oldal elérhető', icon: CheckCircle }
].map((email, idx) => (
<button
key={idx}
onClick={() => handleSendEmail(email.label)}
disabled={!!emailSending}
className="flex items-center gap-4 p-4 bg-white border border-gray-100 rounded-[20px] hover:border-primary hover:shadow-md transition-all text-left group disabled:opacity-50"
>
<div className="w-10 h-10 bg-gray-50 rounded-xl flex items-center justify-center text-gray-400 group-hover:bg-primary/10 group-hover:text-primary transition-colors">
{emailSending === email.label ? <RefreshCw className="w-5 h-5 animate-spin" /> : <email.icon className="w-5 h-5" />}
</div>
<div className="flex-grow">
<p className="text-xs font-bold text-gray-900">{email.label}</p>
<p className="text-[9px] text-gray-400 uppercase font-black tracking-widest mt-0.5">
{emailSending === email.label ? 'Küldés folyamatban...' : 'E-mail küldése'}
</p>
</div>
<Send className="w-3 h-3 text-gray-300 group-hover:text-primary group-hover:translate-x-1 transition-all" />
</button>
))}
</div>
</div>
<div className="bg-purple-50/50 p-8 rounded-[32px] border border-purple-100 shadow-sm">
<h4 className="text-[11px] font-black text-purple-600 uppercase tracking-[0.3em] mb-6 flex items-center gap-2"><History className="w-4 h-4" /> Korábban kiküldött e-mailek</h4>
<div className="space-y-6 relative before:absolute before:left-[11px] before:top-2 before:bottom-2 before:w-0.5 before:bg-purple-200">
{viewOrder.emailLogs && viewOrder.emailLogs.length > 0 ? (
viewOrder.emailLogs.map((log) => (
<div key={log.id} className="relative pl-8">
<div className={`absolute left-0 top-1.5 w-6 h-6 rounded-full border-4 border-white shadow-sm z-10 bg-purple-500`} />
<div className="text-black">
<div className="flex justify-between items-start">
<p className="text-xs font-bold text-gray-900">{log.email_type}</p>
<p className="text-[10px] font-black text-purple-400 uppercase tracking-widest">{new Date(log.sent_at).toLocaleString('hu-HU')}</p>
</div>
{log.body && (
<div className="mt-2 p-3 bg-white border border-purple-100 rounded-xl text-[10px] text-gray-600 max-h-48 overflow-y-auto font-medium" dangerouslySetInnerHTML={{ __html: log.body }} />
)}
</div>
</div>
))
) : (
<p className="text-xs text-gray-400 italic pl-8">Még nem küldtünk e-mailt ehhez a rendeléshez.</p>
)}
</div>
</div>
</section>
<section className="pt-8 border-t border-gray-100">
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-10 gap-4">
<h3 className="text-2xl font-black text-gray-900 flex items-center gap-3 uppercase tracking-tighter"><Sparkles className="w-7 h-7 text-purple-600" /> AI Fejlesztői Promptok</h3>
<Button size="sm" onClick={() => setGeneratedPrompts(generateOrderPrompts(viewOrder))}>Promptok Generálása</Button>
</div>
<div className="grid gap-6">
{generatedPrompts.map((p, i) => (
<div key={i} className="border border-gray-100 rounded-[32px] overflow-hidden bg-white shadow-sm">
<div className="bg-gray-50/50 px-8 py-4 flex justify-between items-center border-b border-gray-100">
<span className="text-[11px] font-black text-gray-600 uppercase tracking-widest">{p.title}</span>
<button onClick={() => { navigator.clipboard.writeText(p.content); setCopiedIndex(i); setTimeout(() => setCopiedIndex(null), 2000); }} className={`text-[10px] font-black px-4 py-1.5 rounded-full uppercase tracking-widest ${copiedIndex === i ? 'bg-green-500 text-white' : 'bg-primary/10 text-primary'}`}>{copiedIndex === i ? 'MÁSOLVA!' : 'MÁSOLÁS'}</button>
</div>
<div className="p-8 bg-gray-900 overflow-x-auto"><pre className="text-gray-300 text-[11px] font-mono leading-relaxed"><code>{p.content}</code></pre></div>
</div>
))}
</div>
</section>
</div>
<div className="md:col-span-4 space-y-10 border-l border-gray-100 pl-10">
<section className="bg-blue-50/50 p-8 rounded-[40px] border border-blue-100 shadow-sm">
<h3 className="text-blue-900 font-black mb-6 flex items-center gap-3 text-lg uppercase tracking-tighter"><Globe className="w-5 h-5" /> Demo URL közzététele</h3>
<div className="flex flex-col gap-3">
<input
type="text"
value={demoUrlInput}
onChange={e => setDemoUrlInput(e.target.value)}
placeholder="https://..."
className="w-full px-5 py-4 rounded-2xl border border-gray-200 bg-white text-black text-sm font-bold outline-none focus:border-blue-500 transition-all shadow-sm"
/>
<button
onClick={handleUpdateDemoUrl}
disabled={savingDemoUrl}
className="bg-blue-600 hover:bg-blue-700 text-white font-black px-6 py-4 rounded-2xl text-xs uppercase tracking-widest transition-all disabled:opacity-50 shadow-lg shadow-blue-200"
>
{savingDemoUrl ? 'MENTÉS...' : 'PUBLIKÁLÁS ÉS ÉRTESÍTÉS'}
</button>
</div>
</section>
<section>
<h3 className="text-gray-900 font-black mb-6 flex items-center gap-3 text-lg uppercase tracking-tighter"><Clock className="w-5 h-5 text-primary" /> Státusz Történet</h3>
<div className="space-y-6 relative before:absolute before:left-[11px] before:top-2 before:bottom-2 before:w-0.5 before:bg-gray-100">
{viewOrder.history?.map((h, i) => (
<div key={h.id} className="relative pl-8">
<div className={`absolute left-0 top-1.5 w-6 h-6 rounded-full border-4 border-white shadow-sm z-10 ${i === 0 ? 'bg-primary' : 'bg-gray-300'}`} />
<div className="text-black">
<StatusBadge status={h.status} />
<p className="text-[11px] font-bold text-gray-500 mt-1 uppercase">{new Date(h.changed_at).toLocaleString('hu-HU')}</p>
</div>
</div>
))}
</div>
</section>
</div>
</div>
</div>
</div>
</div>
)}
</div>
</div>
);
};