docs: Add JSDoc and inline comments to enhance code readability and explain functionality across multiple pages and components.

This commit is contained in:
2026-02-26 10:38:13 +00:00
parent c4164e50a0
commit 5b866a161e
25 changed files with 675 additions and 174 deletions

View File

@@ -1,7 +1,14 @@
/**
* @file AppContext.tsx
* @description Contexto global principal ("State Manager") da aplicação.
* Efetua a ligação direta e centralizada com a base de dados Supabase
* lidando com Auth, Consultas (Shops/Services) e CRUD (Criar/Ler/Atualizar/Apagar).
*/
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { Appointment, Barber, BarberShop, CartItem, Order, Product, Service, User } from '../types';
import { supabase } from '../lib/supabase';
// Tipo interno determinando as propriedades globais partilhadas (Estados e Funções)
type State = {
user?: User;
shops: BarberShop[];
@@ -26,9 +33,14 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<User | undefined>(undefined);
const [loading, setLoading] = useState(true);
// 🔹 Carregar utilizador autenticado
/**
* Hook executado no carregamento (mount) inicial.
* Valida através de `supabase.auth.getUser()` se existe um token de sessão válido
* (identificando o utilizador sem necessidade de o cliente refazer login ativamente).
*/
useEffect(() => {
const loadUser = async () => {
// Pedido restrito à API de autenticação do Supabase
const { data } = await supabase.auth.getUser();
if (data.user) {
setUser({
@@ -41,10 +53,16 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
loadUser();
}, []);
// 🔹 Buscar shops + services
/**
* Consulta mestra (Query) - Refresca todo o ecossistema de dados das barbearias.
* Faz 2 queries (`supabase.from('shops').select('*')` e `services`) e depois
* executa um JOIN manual via Javascript para injetar os serviços dentro
* dos objetos da barbearia respetiva.
*/
const refreshShops = async () => {
console.log("A buscar shops...");
// Query 1: Obtém a listagem completa (tabela 'shops')
const { data: shopsData, error: shopsError } = await supabase
.from('shops')
.select('*');
@@ -54,6 +72,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
return;
}
// Query 2: Obtém a listagem associada globalmente (tabela 'services')
const { data: servicesData, error: servicesError } = await supabase
.from('services')
.select('*');
@@ -63,9 +82,10 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
return;
}
// Associar serviços às respetivas shops
// Associar serviços às respetivas shops, simulando um INNER JOIN nativo do SQL
const shopsWithServices = shopsData.map((shop) => ({
...shop,
// Relaciona a 'foreign key' (shop_id) com o resgistro primário (shop.id)
services: servicesData.filter((s) => s.shop_id === shop.id),
products: [],
barbers: [],
@@ -76,6 +96,11 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
setShops(shopsWithServices);
};
/**
* Hook de Inicialização Master.
* Aciona a função de preenchimento do Contexto assincronamente e liberta
* a interface UI da view de Loading (`setLoading(false)`).
*/
useEffect(() => {
const init = async () => {
await refreshShops();
@@ -84,11 +109,16 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
init();
}, []);
/**
* Encerra a sessão JWT ativa com o Supabase Auth.
* Limpa integralmente a interface do User local (estado React vazio).
*/
const logout = async () => {
await supabase.auth.signOut();
setUser(undefined);
};
// Funções elementares do fluxo transacional não persistido (Estado do Carrinho transitório/local)
const addToCart = (item: CartItem) => {
setCart((prev) => [...prev, item]);
};
@@ -97,7 +127,13 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
// 🔹 CRUD SERVICES (SUPABASE REAL)
/**
* Executa um INSERT na BD (via API REST gerada) protegendo interações com a tabela estrita 'services'.
* @param {string} shopId - A foreign key relacionando o estabelecimento.
* @param {Omit<Service, 'id'>} service - O DTO (Data Transfer Object) sem a primary key autonumerável.
*/
const addService = async (shopId: string, service: Omit<Service, 'id'>) => {
// Insere os campos exatos formatados estritamente na query Supabase
const { error } = await supabase.from('services').insert([
{
shop_id: shopId,
@@ -112,9 +148,13 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
return;
}
// Para manter integridade reativa pura, força refetch dos dados pós-mutação
await refreshShops();
};
/**
* Executa um UPDATE num tuplo específico filtrando analiticamente pela primary key `(eq('id', service.id))`.
*/
const updateService = async (shopId: string, service: Service) => {
const { error } = await supabase
.from('services')
@@ -123,7 +163,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
price: service.price,
duration: service.duration,
})
.eq('id', service.id);
.eq('id', service.id); // Identificador vital do update
if (error) {
console.error("Erro ao atualizar serviço:", error);
@@ -133,6 +173,9 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
await refreshShops();
};
/**
* Executa uma instrução SQL DELETE remota rígida, baseada no ID unívoco do tuplo.
*/
const deleteService = async (shopId: string, serviceId: string) => {
const { error } = await supabase
.from('services')
@@ -147,6 +190,7 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
await refreshShops();
};
// Empacotamento em objeto estabilizado memoizado face renderizações espúrias (React Context Pattern)
const value: AppContextValue = useMemo(
() => ({
user,
@@ -163,11 +207,13 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
[user, shops, cart]
);
// Loading Shield evita quebra generalizada se o app renderizar sem BD disponível
if (loading) return null;
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};
// Hook prático de acesso central sem import múltiplo do 'useContext' em toda aplicação
export const useApp = () => {
const ctx = useContext(AppContext);
if (!ctx) throw new Error('useApp deve ser usado dentro de AppProvider');