diff --git a/src/app/api/tenant/agenda/route.ts b/src/app/api/tenant/agenda/route.ts index 3d0042d..ee38583 100644 --- a/src/app/api/tenant/agenda/route.ts +++ b/src/app/api/tenant/agenda/route.ts @@ -56,7 +56,11 @@ export async function GET(req: NextRequest) { export async function POST(req: NextRequest) { try { - const { slug, professionalId, client, service, startHour, startMin, duration, price } = await req.json(); + const { + slug, professionalId, client, phone, email, service, + serviceId, startHour, startMin, duration, price, date, status, notes + } = await req.json(); + if (!slug || !professionalId) return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }); const tenantRes = await query('SELECT id FROM tenants WHERE slug = $1', [slug]); @@ -70,10 +74,36 @@ export async function POST(req: NextRequest) { dateObj.setMinutes(dateObj.getMinutes() + Number(duration)); const endTime = dateObj.toISOString().substr(11, 8); + const targetDate = date || new Date().toISOString().split('T')[0]; + const initialStatus = status || 'confirmed'; + const res = await query( - `INSERT INTO appointments (tenant_id, professional_id, client_name, notes, date, start_time, end_time, price, status) - VALUES ($1, $2, $3, $4, CURRENT_DATE, $5, $6, $7, 'confirmed') RETURNING *`, - [tenantId, professionalId, client, service, startTime, endTime, price] + `INSERT INTO appointments ( + tenant_id, professional_id, client_name, client_phone, client_email, + service_id, price, date, start_time, end_time, status, notes + ) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`, + [ + tenantId, + professionalId, + client, + phone || null, + email || null, + serviceId || null, + price, + targetDate, + startTime, + endTime, + initialStatus, + notes || service || null + ] + ); + + // Insert into notifications + await query( + `INSERT INTO notifications (tenant_id, type, title, message) + VALUES ($1, 'info', 'Novo Agendamento', $2)`, + [tenantId, `${client} agendou ${service || 'Serviço'} para o dia ${targetDate} às ${startTime.substring(0, 5)}`] ); return NextResponse.json(res.rows[0]); @@ -82,3 +112,53 @@ export async function POST(req: NextRequest) { return NextResponse.json({ error: error.message }, { status: 500 }); } } + +export async function PUT(req: NextRequest) { + try { + const { id, status, professionalId, client, service, startHour, startMin, duration, price, date } = await req.json(); + if (!id) return NextResponse.json({ error: 'Missing appointment id' }, { status: 400 }); + + if (status && Object.keys(await req.clone().json()).length === 3) { + // Direct status update (like starting/finishing serving) + const res = await query( + `UPDATE appointments SET status = $1, updated_at = NOW() WHERE id = $2 RETURNING *`, + [status, id] + ); + return NextResponse.json(res.rows[0]); + } + + // Full update + const startTime = `${String(startHour).padStart(2, '0')}:${String(startMin).padStart(2, '0')}:00`; + const dateObj = new Date(`1970-01-01T${startTime}Z`); + dateObj.setMinutes(dateObj.getMinutes() + Number(duration)); + const endTime = dateObj.toISOString().substr(11, 8); + const targetDate = date || new Date().toISOString().split('T')[0]; + + const res = await query( + `UPDATE appointments SET + professional_id = $1, client_name = $2, notes = $3, date = $4, + start_time = $5, end_time = $6, price = $7, status = $8, updated_at = NOW() + WHERE id = $9 RETURNING *`, + [professionalId, client, service, targetDate, startTime, endTime, price, status || 'confirmed', id] + ); + + return NextResponse.json(res.rows[0]); + } catch (error: any) { + console.error('PUT Agenda error:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +export async function DELETE(req: NextRequest) { + try { + const { searchParams } = new URL(req.url); + const id = searchParams.get('id'); + if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 }); + + await query('DELETE FROM appointments WHERE id = $1', [id]); + return NextResponse.json({ success: true }); + } catch (error: any) { + console.error('DELETE Agenda error:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/src/app/api/tenant/notifications/route.ts b/src/app/api/tenant/notifications/route.ts new file mode 100644 index 0000000..82e47c2 --- /dev/null +++ b/src/app/api/tenant/notifications/route.ts @@ -0,0 +1,103 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { query } from '@/lib/db'; + +export async function GET(req: NextRequest) { + const { searchParams } = new URL(req.url); + const slug = searchParams.get('slug'); + + if (!slug) { + return NextResponse.json({ error: 'Missing tenant slug' }, { status: 400 }); + } + + try { + const tenantRes = await query('SELECT id FROM tenants WHERE slug = $1', [slug]); + if (tenantRes.rows.length === 0) return NextResponse.json([]); + const tenantId = tenantRes.rows[0].id; + + // Fetch notifications + const notifsRes = await query(` + SELECT + id, + type, + title, + message, + is_read as read, + created_at + FROM notifications + WHERE tenant_id = $1 + ORDER BY created_at DESC + LIMIT 20 + `, [tenantId]); + + // Format relative time helper + const formatted = notifsRes.rows.map(n => { + const diffMs = new Date().getTime() - new Date(n.created_at).getTime(); + const diffMins = Math.floor(diffMs / 60000); + const diffHours = Math.floor(diffMins / 60); + const diffDays = Math.floor(diffHours / 24); + + let timeStr = 'Agora mesmo'; + if (diffDays > 0) { + timeStr = `${diffDays} dia${diffDays > 1 ? 's' : ''} atrás`; + } else if (diffHours > 0) { + timeStr = `${diffHours} hora${diffHours > 1 ? 's' : ''} atrás`; + } else if (diffMins > 0) { + timeStr = `${diffMins} min${diffMins > 1 ? 's' : ''} atrás`; + } + + return { + id: n.id, + type: n.type, + title: n.title, + message: n.message, + read: n.read, + time: timeStr + }; + }); + + return NextResponse.json(formatted); + } catch (error: any) { + console.error('GET Notifications error:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +export async function PUT(req: NextRequest) { + try { + const { slug } = await req.json(); + if (!slug) return NextResponse.json({ error: 'Missing tenant slug' }, { status: 400 }); + + const tenantRes = await query('SELECT id FROM tenants WHERE slug = $1', [slug]); + if (tenantRes.rows.length === 0) return NextResponse.json({ error: 'Tenant not found' }, { status: 404 }); + const tenantId = tenantRes.rows[0].id; + + // Mark all as read + await query('UPDATE notifications SET is_read = true WHERE tenant_id = $1', [tenantId]); + return NextResponse.json({ success: true }); + } catch (error: any) { + console.error('PUT Notifications error:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +export async function DELETE(req: NextRequest) { + const { searchParams } = new URL(req.url); + const slug = searchParams.get('slug'); + + if (!slug) { + return NextResponse.json({ error: 'Missing tenant slug' }, { status: 400 }); + } + + try { + const tenantRes = await query('SELECT id FROM tenants WHERE slug = $1', [slug]); + if (tenantRes.rows.length === 0) return NextResponse.json({ error: 'Tenant not found' }, { status: 404 }); + const tenantId = tenantRes.rows[0].id; + + // Delete all notifications for this tenant + await query('DELETE FROM notifications WHERE tenant_id = $1', [tenantId]); + return NextResponse.json({ success: true }); + } catch (error: any) { + console.error('DELETE Notifications error:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index db65efb..ab9470c 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -8,8 +8,23 @@ export default function LoginPage() { const [password, setPassword] = useState(''); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); + const [isForgot, setIsForgot] = useState(false); + const [resetEmail, setResetEmail] = useState(''); + const [resetSent, setResetSent] = useState(false); const router = useRouter(); + const handleResetPassword = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(''); + + // Simulate sending recovery email + setTimeout(() => { + setLoading(false); + setResetSent(true); + }, 1500); + }; + const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); @@ -64,35 +79,69 @@ export default function LoginPage() { {/* Lado Direito - Formulário */}
Faça login para acessar o seu painel
+ {!isForgot ? ( + <> +Faça login para acessar o seu painel
- {error &&- Ainda não tem uma conta? Crie agora -
++ Ainda não tem uma conta? Crie agora +
+ > + ) : ( + <> +Digite seu e-mail cadastrado para receber as instruções
+ + {resetSent ? ( +E-mail enviado! 📨
+Instruções de redefinição foram enviadas para {resetEmail}.
+