From 330e4e175f840130e838c82a1b56b1566239c993 Mon Sep 17 00:00:00 2001 From: Rodribm10 Date: Tue, 14 Apr 2026 21:20:35 -0300 Subject: [PATCH] feat(fase4-e2): 5 abas CRUD restantes (categorias, precos, fotos, extras, reservas) - CategoriasTab: edita marcas.categorias[] via lista reordenavel - PrecosTab: grid categoria x permanencia, salva via delete + insert - FotosTab: upload pro Supabase Storage (bucket reserva-fotos) + URL manual + reorder - ExtrasTab: CRUD padrao com titulo/preco/descricao - ReservasTab: read-only com filtros (status/datas) + link pra conversa no Chatwoot - AdminLayout TABS com as 8 abas - Router com todas as rotas --- src/pages/admin/AdminLayout.tsx | 5 + src/pages/admin/CategoriasTab.tsx | 136 +++++++++++++++++ src/pages/admin/ExtrasTab.tsx | 246 ++++++++++++++++++++++++++++++ src/pages/admin/FotosTab.tsx | 218 ++++++++++++++++++++++++++ src/pages/admin/PrecosTab.tsx | 183 ++++++++++++++++++++++ src/pages/admin/ReservasTab.tsx | 130 ++++++++++++++++ src/router.tsx | 10 ++ 7 files changed, 928 insertions(+) create mode 100644 src/pages/admin/CategoriasTab.tsx create mode 100644 src/pages/admin/ExtrasTab.tsx create mode 100644 src/pages/admin/FotosTab.tsx create mode 100644 src/pages/admin/PrecosTab.tsx create mode 100644 src/pages/admin/ReservasTab.tsx diff --git a/src/pages/admin/AdminLayout.tsx b/src/pages/admin/AdminLayout.tsx index 423ce44..ae997de 100644 --- a/src/pages/admin/AdminLayout.tsx +++ b/src/pages/admin/AdminLayout.tsx @@ -7,6 +7,11 @@ const TABS = [ { to: 'aparencia', label: 'Aparência' }, { to: 'marcas', label: 'Marcas' }, { to: 'unidades', label: 'Unidades' }, + { to: 'categorias', label: 'Categorias' }, + { to: 'precos', label: 'Preços' }, + { to: 'fotos', label: 'Fotos' }, + { to: 'extras', label: 'Extras' }, + { to: 'reservas', label: 'Reservas' }, ] export function AdminLayout() { diff --git a/src/pages/admin/CategoriasTab.tsx b/src/pages/admin/CategoriasTab.tsx new file mode 100644 index 0000000..89ba26f --- /dev/null +++ b/src/pages/admin/CategoriasTab.tsx @@ -0,0 +1,136 @@ +import { useEffect, useState } from 'react' +import { supabase } from '@/lib/supabase' +import { useTenantId } from '@/hooks/useAppConfig' +import type { Database } from '@/types/database' +import { SelectField } from '@/components/SelectField' +import { FormField } from '@/components/FormField' +import { Button } from '@/components/ui/button' + +type Marca = Database['reserva_hotel']['Tables']['marcas']['Row'] + +export function CategoriasTab() { + const tenantId = useTenantId() + const [marcas, setMarcas] = useState([]) + const [selectedMarcaId, setSelectedMarcaId] = useState('') + const [categorias, setCategorias] = useState([]) + const [newCat, setNewCat] = useState('') + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + const [successMsg, setSuccessMsg] = useState(null) + + useEffect(() => { + if (!tenantId) return + supabase + .from('marcas') + .select('*') + .eq('tenant_id', tenantId) + .order('nome') + .then(({ data }) => { + setMarcas(data ?? []) + if (data && data.length > 0 && !selectedMarcaId) { + setSelectedMarcaId(data[0].id) + } + }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tenantId]) + + useEffect(() => { + const marca = marcas.find((m) => m.id === selectedMarcaId) + setCategorias(marca?.categorias ?? []) + }, [selectedMarcaId, marcas]) + + const addCat = () => { + const trimmed = newCat.trim() + if (!trimmed) return + if (categorias.includes(trimmed)) return + setCategorias([...categorias, trimmed]) + setNewCat('') + } + + const removeCat = (cat: string) => { + setCategorias(categorias.filter((c) => c !== cat)) + } + + const move = (idx: number, dir: -1 | 1) => { + const next = [...categorias] + const target = idx + dir + if (target < 0 || target >= next.length) return + ;[next[idx], next[target]] = [next[target], next[idx]] + setCategorias(next) + } + + const handleSave = async () => { + if (!selectedMarcaId) return + setSaving(true) + setError(null) + setSuccessMsg(null) + try { + const { error: err } = await supabase + .from('marcas') + .update({ categorias }) + .eq('id', selectedMarcaId) + if (err) throw new Error(err.message) + + setMarcas(marcas.map((m) => (m.id === selectedMarcaId ? { ...m, categorias } : m))) + setSuccessMsg('Categorias salvas!') + setTimeout(() => setSuccessMsg(null), 2500) + } catch (e) { + setError(e instanceof Error ? e.message : 'Erro ao salvar') + } finally { + setSaving(false) + } + } + + return ( +
+
+

Categorias de Suíte

+

Escolha uma marca e edite as categorias disponíveis.

+
+ + setSelectedMarcaId(e.target.value)} + options={marcas.map((m) => ({ value: m.id, label: m.nome }))} + /> + +
+

Categorias

+ + {categorias.length === 0 && ( +

Nenhuma categoria cadastrada.

+ )} + +
    + {categorias.map((cat, idx) => ( +
  • + {cat} + + + +
  • + ))} +
+ +
+ setNewCat(e.target.value)} + placeholder="Ex: Alexa, Stilo, Hidromassagem" + className="flex-1" + /> + +
+
+ + {error &&
{error}
} + {successMsg &&
{successMsg}
} + + +
+ ) +} diff --git a/src/pages/admin/ExtrasTab.tsx b/src/pages/admin/ExtrasTab.tsx new file mode 100644 index 0000000..9959821 --- /dev/null +++ b/src/pages/admin/ExtrasTab.tsx @@ -0,0 +1,246 @@ +import { useEffect, useState } from 'react' +import { supabase } from '@/lib/supabase' +import { useTenantId } from '@/hooks/useAppConfig' +import type { Database } from '@/types/database' +import { useCrud } from '@/hooks/useCrud' +import { DataTable, type Column } from '@/components/admin/DataTable' +import { Modal } from '@/components/admin/Modal' +import { FormField } from '@/components/FormField' +import { SelectField } from '@/components/SelectField' +import { Button } from '@/components/ui/button' +import { formatBRL } from '@/lib/formatters' + +type Extra = Database['reserva_hotel']['Tables']['extras']['Row'] +type Marca = Database['reserva_hotel']['Tables']['marcas']['Row'] + +const EMPTY_FORM = { + id_marca: '', + titulo: '', + descricao: '', + preco: '', + imagem_url: '', + ordem: '0', + ativo: true, +} + +export function ExtrasTab() { + const tenantId = useTenantId() + const { rows, loading, error, create, update, remove } = useCrud('extras', { + orderBy: 'ordem', + ascending: true, + }) + + const [marcas, setMarcas] = useState([]) + const [modalOpen, setModalOpen] = useState(false) + const [editing, setEditing] = useState(null) + const [form, setForm] = useState(EMPTY_FORM) + const [saving, setSaving] = useState(false) + const [formError, setFormError] = useState(null) + + useEffect(() => { + if (!tenantId) return + void supabase + .from('marcas') + .select('*') + .eq('tenant_id', tenantId) + .order('nome') + .then(({ data }) => setMarcas(data ?? [])) + }, [tenantId]) + + const openCreate = () => { + setEditing(null) + setForm(EMPTY_FORM) + setFormError(null) + setModalOpen(true) + } + + const openEdit = (extra: Extra) => { + setEditing(extra) + setForm({ + id_marca: extra.id_marca, + titulo: extra.titulo, + descricao: extra.descricao ?? '', + preco: String(extra.preco), + imagem_url: extra.imagem_url ?? '', + ordem: String(extra.ordem), + ativo: extra.ativo, + }) + setFormError(null) + setModalOpen(true) + } + + const handleDelete = async (extra: Extra) => { + if (!confirm(`Excluir o extra "${extra.titulo}"?`)) return + try { + await remove(extra.id) + } catch (e) { + alert(e instanceof Error ? e.message : 'Erro ao excluir') + } + } + + const handleSave = async () => { + if (!form.titulo.trim()) { + setFormError('Título é obrigatório') + return + } + if (!form.id_marca) { + setFormError('Marca é obrigatória') + return + } + const preco = Number(form.preco.replace(',', '.')) + if (isNaN(preco) || preco < 0) { + setFormError('Preço inválido') + return + } + setSaving(true) + setFormError(null) + try { + const payload = { + id_marca: form.id_marca, + titulo: form.titulo.trim(), + descricao: form.descricao.trim() || null, + preco, + imagem_url: form.imagem_url.trim() || null, + ordem: Number(form.ordem) || 0, + ativo: form.ativo, + } + if (editing) { + await update(editing.id, payload) + } else { + await create(payload) + } + setModalOpen(false) + } catch (e) { + setFormError(e instanceof Error ? e.message : 'Erro ao salvar') + } finally { + setSaving(false) + } + } + + const columns: Column[] = [ + { key: 'titulo', label: 'Título' }, + { + key: 'id_marca', + label: 'Marca', + render: (row) => marcas.find((m) => m.id === row.id_marca)?.nome ?? '—', + }, + { + key: 'preco', + label: 'Preço', + render: (row) => formatBRL(Math.round(Number(row.preco) * 100)), + }, + { key: 'ordem', label: 'Ordem', render: (row) => String(row.ordem) }, + { + key: 'ativo', + label: 'Status', + render: (row) => ( + + {row.ativo ? 'Ativo' : 'Inativo'} + + ), + }, + ] + + return ( +
+
+
+

Extras

+

Serviços e produtos extras oferecidos na reserva.

+
+ +
+ + {error && ( +
{error}
+ )} + + + + setModalOpen(false)} + footer={ + <> + + + + } + > + setForm({ ...form, id_marca: e.target.value })} + options={marcas.map((m) => ({ value: m.id, label: m.nome }))} + /> + + setForm({ ...form, titulo: e.target.value })} + placeholder="Ex: Café da manhã, Espumante, Jacuzzi" + /> + + setForm({ ...form, descricao: e.target.value })} + placeholder="Breve descrição do extra" + /> + +
+ setForm({ ...form, preco: e.target.value })} + placeholder="0.00" + /> + setForm({ ...form, ordem: e.target.value })} + placeholder="0" + /> +
+ + setForm({ ...form, imagem_url: e.target.value })} + placeholder="https://..." + /> + + + + {formError && ( +
+ {formError} +
+ )} +
+
+ ) +} diff --git a/src/pages/admin/FotosTab.tsx b/src/pages/admin/FotosTab.tsx new file mode 100644 index 0000000..ab2368b --- /dev/null +++ b/src/pages/admin/FotosTab.tsx @@ -0,0 +1,218 @@ +import { useEffect, useState, type ChangeEvent } from 'react' +import { supabase } from '@/lib/supabase' +import { useTenantId } from '@/hooks/useAppConfig' +import type { Database } from '@/types/database' +import { SelectField } from '@/components/SelectField' +import { FormField } from '@/components/FormField' +import { Button } from '@/components/ui/button' + +type Unidade = Database['reserva_hotel']['Tables']['unidades']['Row'] +type Foto = Database['reserva_hotel']['Tables']['fotos_categoria']['Row'] + +const STORAGE_BUCKET = 'reserva-fotos' + +export function FotosTab() { + const tenantId = useTenantId() + const [unidades, setUnidades] = useState([]) + const [selectedUnidadeId, setSelectedUnidadeId] = useState('') + const [selectedCategoria, setSelectedCategoria] = useState('') + const [fotos, setFotos] = useState([]) + const [loading, setLoading] = useState(false) + const [uploading, setUploading] = useState(false) + const [newUrl, setNewUrl] = useState('') + const [newAlt, setNewAlt] = useState('') + const [error, setError] = useState(null) + + useEffect(() => { + if (!tenantId) return + supabase + .from('unidades') + .select('*') + .eq('tenant_id', tenantId) + .order('nome') + .then(({ data }) => { + setUnidades(data ?? []) + if (data && data.length > 0 && !selectedUnidadeId) { + setSelectedUnidadeId(data[0].id) + } + }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tenantId]) + + const unidade = unidades.find((u) => u.id === selectedUnidadeId) + const categoriasVisiveis = unidade?.categorias_visiveis ?? [] + + useEffect(() => { + if (categoriasVisiveis.length > 0 && !selectedCategoria) { + setSelectedCategoria(categoriasVisiveis[0]) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(categoriasVisiveis)]) + + const loadFotos = async () => { + if (!selectedUnidadeId || !selectedCategoria) return + setLoading(true) + const { data } = await supabase + .from('fotos_categoria') + .select('*') + .eq('id_unidade', selectedUnidadeId) + .eq('categoria', selectedCategoria) + .order('ordem') + setFotos(data ?? []) + setLoading(false) + } + + useEffect(() => { + void loadFotos() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedUnidadeId, selectedCategoria]) + + const handleAddUrl = async () => { + if (!tenantId || !selectedUnidadeId || !selectedCategoria || !newUrl.trim()) return + setError(null) + try { + const { error: err } = await supabase.from('fotos_categoria').insert({ + tenant_id: tenantId, + id_unidade: selectedUnidadeId, + categoria: selectedCategoria, + url_foto: newUrl.trim(), + alt: newAlt.trim() || null, + ordem: fotos.length, + ativa: true, + }) + if (err) throw new Error(err.message) + setNewUrl('') + setNewAlt('') + await loadFotos() + } catch (e) { + setError(e instanceof Error ? e.message : 'Erro ao adicionar') + } + } + + const handleFileUpload = async (e: ChangeEvent) => { + const file = e.target.files?.[0] + if (!file || !tenantId || !selectedUnidadeId || !selectedCategoria) return + setUploading(true) + setError(null) + try { + const ext = file.name.split('.').pop() ?? 'jpg' + const path = `${tenantId}/${selectedUnidadeId}/${selectedCategoria}/${Date.now()}.${ext}` + const { error: uploadErr } = await supabase.storage + .from(STORAGE_BUCKET) + .upload(path, file, { upsert: false }) + if (uploadErr) throw new Error(uploadErr.message) + + const { data: publicData } = supabase.storage.from(STORAGE_BUCKET).getPublicUrl(path) + + const { error: insertErr } = await supabase.from('fotos_categoria').insert({ + tenant_id: tenantId, + id_unidade: selectedUnidadeId, + categoria: selectedCategoria, + url_foto: publicData.publicUrl, + alt: file.name, + ordem: fotos.length, + ativa: true, + }) + if (insertErr) throw new Error(insertErr.message) + await loadFotos() + e.target.value = '' + } catch (e) { + setError(e instanceof Error ? e.message : 'Erro no upload') + } finally { + setUploading(false) + } + } + + const handleDelete = async (foto: Foto) => { + if (!confirm('Excluir foto?')) return + const { error: err } = await supabase.from('fotos_categoria').delete().eq('id', foto.id) + if (err) { + setError(err.message) + return + } + await loadFotos() + } + + const move = async (idx: number, dir: -1 | 1) => { + const target = idx + dir + if (target < 0 || target >= fotos.length) return + const a = fotos[idx] + const b = fotos[target] + await supabase.from('fotos_categoria').update({ ordem: b.ordem }).eq('id', a.id) + await supabase.from('fotos_categoria').update({ ordem: a.ordem }).eq('id', b.id) + await loadFotos() + } + + return ( +
+
+

Fotos das suítes

+

Upload direto ou URL pública. Organize por unidade + categoria.

+
+ +
+ setSelectedUnidadeId(e.target.value)} + options={unidades.map((u) => ({ value: u.id, label: u.nome }))} + /> + setSelectedCategoria(e.target.value)} + options={categoriasVisiveis.map((c) => ({ value: c, label: c }))} + /> +
+ +
+

Adicionar foto

+ +
+ + + {uploading &&

Enviando...

} +
+ +
+

Ou cole uma URL

+ setNewUrl(e.target.value)} placeholder="https://..." /> + setNewAlt(e.target.value)} placeholder="Descrição curta" /> + +
+
+ + {error &&
{error}
} + +
+

Fotos cadastradas

+ {loading &&

Carregando...

} + {!loading && fotos.length === 0 &&

Nenhuma foto nesta categoria.

} + +
+ {fotos.map((foto, idx) => ( +
+
+ {foto.alt +
+
+

{foto.alt ?? 'Sem alt'}

+
+ + + +
+
+
+ ))} +
+
+
+ ) +} diff --git a/src/pages/admin/PrecosTab.tsx b/src/pages/admin/PrecosTab.tsx new file mode 100644 index 0000000..56bc778 --- /dev/null +++ b/src/pages/admin/PrecosTab.tsx @@ -0,0 +1,183 @@ +import { useEffect, useState } from 'react' +import { supabase } from '@/lib/supabase' +import { useTenantId } from '@/hooks/useAppConfig' +import type { Database } from '@/types/database' +import { SelectField } from '@/components/SelectField' +import { Button } from '@/components/ui/button' + +type Marca = Database['reserva_hotel']['Tables']['marcas']['Row'] +type Preco = Database['reserva_hotel']['Tables']['precos']['Row'] + +// key: `${categoria}|${permanencia}` → valor em reais (string for input) +type PriceMap = Record + +export function PrecosTab() { + const tenantId = useTenantId() + const [marcas, setMarcas] = useState([]) + const [selectedMarcaId, setSelectedMarcaId] = useState('') + const [priceMap, setPriceMap] = useState({}) + const [loading, setLoading] = useState(false) + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + const [successMsg, setSuccessMsg] = useState(null) + + useEffect(() => { + if (!tenantId) return + supabase + .from('marcas') + .select('*') + .eq('tenant_id', tenantId) + .order('nome') + .then(({ data }) => { + setMarcas(data ?? []) + if (data && data.length > 0 && !selectedMarcaId) setSelectedMarcaId(data[0].id) + }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tenantId]) + + useEffect(() => { + if (!selectedMarcaId) return + setLoading(true) + supabase + .from('precos') + .select('*') + .eq('id_marca', selectedMarcaId) + .eq('periodo_semana', 'default') + .then(({ data }) => { + const map: PriceMap = {} + ;(data ?? []).forEach((p: Preco) => { + map[`${p.categoria}|${p.permanencia}`] = String(p.valor) + }) + setPriceMap(map) + setLoading(false) + }) + }, [selectedMarcaId]) + + const marca = marcas.find((m) => m.id === selectedMarcaId) + const categorias = marca?.categorias ?? [] + const permanencias = marca?.permanencias ?? [] + + const setPrice = (categoria: string, permanencia: string, value: string) => { + setPriceMap({ ...priceMap, [`${categoria}|${permanencia}`]: value }) + } + + const handleSave = async () => { + if (!selectedMarcaId || !tenantId) return + setSaving(true) + setError(null) + setSuccessMsg(null) + try { + const rows = categorias.flatMap((cat) => + permanencias.map((perm) => { + const raw = priceMap[`${cat}|${perm}`] + const valor = raw ? Number(raw.replace(',', '.')) : 0 + return valor > 0 + ? { + tenant_id: tenantId, + id_marca: selectedMarcaId, + categoria: cat, + permanencia: perm, + periodo_semana: 'default', + valor, + ativo: true, + } + : null + }) + ).filter(Boolean) as Array<{ + tenant_id: number + id_marca: string + categoria: string + permanencia: string + periodo_semana: string + valor: number + ativo: boolean + }> + + const { error: delErr } = await supabase + .from('precos') + .delete() + .eq('id_marca', selectedMarcaId) + .eq('periodo_semana', 'default') + if (delErr) throw new Error(delErr.message) + + if (rows.length > 0) { + const { error: insErr } = await supabase.from('precos').insert(rows) + if (insErr) throw new Error(insErr.message) + } + + setSuccessMsg('Preços salvos!') + setTimeout(() => setSuccessMsg(null), 2500) + } catch (e) { + setError(e instanceof Error ? e.message : 'Erro ao salvar') + } finally { + setSaving(false) + } + } + + return ( +
+
+

Preços

+

Grid categoria × permanência. Valores em reais.

+
+ + setSelectedMarcaId(e.target.value)} + options={marcas.map((m) => ({ value: m.id, label: m.nome }))} + /> + + {loading &&

Carregando preços...

} + + {!loading && marca && (categorias.length === 0 || permanencias.length === 0) && ( +
+ Essa marca não tem categorias e/ou permanências cadastradas. Edite primeiro na aba Marcas. +
+ )} + + {!loading && categorias.length > 0 && permanencias.length > 0 && ( +
+ + + + + {permanencias.map((p) => ( + + ))} + + + + {categorias.map((cat) => ( + + + {permanencias.map((perm) => ( + + ))} + + ))} + +
Categoria + {p} +
{cat} + setPrice(cat, perm, e.target.value)} + placeholder="0.00" + /> +
+
+ )} + + {error &&
{error}
} + {successMsg &&
{successMsg}
} + + +
+ ) +} diff --git a/src/pages/admin/ReservasTab.tsx b/src/pages/admin/ReservasTab.tsx new file mode 100644 index 0000000..bd3b6a7 --- /dev/null +++ b/src/pages/admin/ReservasTab.tsx @@ -0,0 +1,130 @@ +import { useMemo, useState } from 'react' +import type { Database } from '@/types/database' +import { useCrud } from '@/hooks/useCrud' +import { DataTable, type Column } from '@/components/admin/DataTable' +import { formatBRL } from '@/lib/formatters' + +type Reserva = Database['reserva_hotel']['Tables']['reservas']['Row'] + +const CHATWOOT_URL = import.meta.env.VITE_CHATWOOT_API_URL || '' + +export function ReservasTab() { + const { rows, loading, error } = useCrud('reservas', { + orderBy: 'created_at', + ascending: false, + }) + + const [statusFilter, setStatusFilter] = useState('') + const [dateFrom, setDateFrom] = useState('') + const [dateTo, setDateTo] = useState('') + + const filtered = useMemo(() => { + return rows.filter((r) => { + if (statusFilter && r.status !== statusFilter) return false + if (dateFrom && r.data_checkin < dateFrom) return false + if (dateTo && r.data_checkin > dateTo + 'T23:59:59') return false + return true + }) + }, [rows, statusFilter, dateFrom, dateTo]) + + const columns: Column[] = [ + { + key: 'data_checkin', + label: 'Check-in', + render: (r) => new Date(r.data_checkin).toLocaleString('pt-BR', { dateStyle: 'short', timeStyle: 'short' }), + }, + { key: 'nome_cliente', label: 'Cliente' }, + { key: 'tipo_permanencia', label: 'Permanência' }, + { + key: 'valor_total', + label: 'Valor', + render: (r) => formatBRL(Math.round(Number(r.valor_total) * 100)), + }, + { + key: 'status', + label: 'Status', + render: (r) => , + }, + { + key: 'chatwoot_conversation_id', + label: 'Conversa', + render: (r) => + r.chatwoot_conversation_id && CHATWOOT_URL ? ( + + #{r.chatwoot_conversation_id} + + ) : '—', + }, + ] + + return ( +
+
+

Reservas

+

Histórico de reservas da sua rede (somente leitura).

+
+ +
+
+ + +
+
+ + setDateFrom(e.target.value)} + className="mt-2 w-full rounded-lg border border-champagne/30 bg-midnight/60 px-4 py-3 text-ivory focus:border-champagne focus:outline-none" + /> +
+
+ + setDateTo(e.target.value)} + className="mt-2 w-full rounded-lg border border-champagne/30 bg-midnight/60 px-4 py-3 text-ivory focus:border-champagne focus:outline-none" + /> +
+
+ + {error &&
{error}
} + + +
+ ) +} + +function StatusBadge({ status }: { status: string }) { + const colors: Record = { + pago: 'text-emerald border-emerald/40 bg-emerald/10', + pendente_pagamento: 'text-champagne border-champagne/40 bg-champagne/10', + cancelada: 'text-slate border-slate/40 bg-slate/10', + } + const cls = colors[status] ?? 'text-slate border-slate/40 bg-slate/10' + return ( + + {status} + + ) +} diff --git a/src/router.tsx b/src/router.tsx index ca3728c..53211d0 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -5,6 +5,11 @@ import { AdminLayout } from '@/pages/admin/AdminLayout' import { AparenciaTab } from '@/pages/admin/AparenciaTab' import { MarcasTab } from '@/pages/admin/MarcasTab' import { UnidadesTab } from '@/pages/admin/UnidadesTab' +import { CategoriasTab } from '@/pages/admin/CategoriasTab' +import { PrecosTab } from '@/pages/admin/PrecosTab' +import { FotosTab } from '@/pages/admin/FotosTab' +import { ExtrasTab } from '@/pages/admin/ExtrasTab' +import { ReservasTab } from '@/pages/admin/ReservasTab' const router = createBrowserRouter([ { path: '/', element: }, @@ -17,6 +22,11 @@ const router = createBrowserRouter([ { path: 'aparencia', element: }, { path: 'marcas', element: }, { path: 'unidades', element: }, + { path: 'categorias', element: }, + { path: 'precos', element: }, + { path: 'fotos', element: }, + { path: 'extras', element: }, + { path: 'reservas', element: }, ], }, ])