docs: Add JSDoc and inline comments to enhance code readability and explain functionality across multiple pages and components.
This commit is contained in:
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user