Files
ComFi/contexts/ComFiContext.tsx
MMrp89 1a57ac7754 feat: Initialize ComFi project with Vite
Setup project structure, dependencies, and basic configuration for the ComFi application. Includes initial setup for Vite, React, TypeScript, Tailwind CSS, and essential development tools. Defines core types and provides a basic README for local development.
2026-02-09 20:28:37 -03:00

196 lines
11 KiB
TypeScript

import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { Company, Expense, Receivable, Service, AppUser, Client, FinancialSummary, TenantProfile, Category, Proposal } from '../types';
import { useStickyState } from '../hooks/useStickyState';
// --- MOCK DATA GENERATORS (Moved from App.tsx) ---
const getDynamicDate = (daysOffset: number) => {
const date = new Date();
date.setDate(date.getDate() + daysOffset);
return date.toISOString().split('T')[0];
};
const initialServices: Service[] = [
{ id: '1', name: 'Consultoria Financeira', category: 'Consultoria', price: 2500, active: true, description: 'Análise completa.', billingType: 'one-time' },
{ id: '2', name: 'Gestão de Mídias', category: 'Marketing', price: 1500, active: true, description: 'Postagens mensais.', billingType: 'recurring' },
{ id: '3', name: 'Suporte TI', category: 'TI', price: 800, active: true, description: 'SLA 24h.', billingType: 'recurring' },
{ id: '4', name: 'Desenvolvimento Web', category: 'Tecnologia', price: 5000, active: true, description: 'Site institucional.', billingType: 'one-time' },
];
const initialCompanies: Company[] = [
{
id: '1', name: 'Uda Studios Tecnologia LTDA', fantasyName: 'Uda Studios', cnpj: '12.345.678/0001-90', ie: '123.456.789.000', city: 'São Paulo - SP', logo: 'https://i.pravatar.cc/150?u=uda', status: 'active', industry: 'Tecnologia', email: 'financeiro@uda.com', phone: '(11) 3344-5566', address: 'Av. Paulista, 1000, Bela Vista', since: '2022', description: 'Estúdio de software.', contacts: [{ id: 'c1', name: 'Roberto Silva', role: 'CEO', email: 'roberto@uda.com', phone: '11999998888', avatar: 'https://i.pravatar.cc/150?u=1' }], documents: [], activeServices: [initialServices[1], initialServices[3]]
},
{
id: '2', name: 'Angels Healthcare S.A.', fantasyName: 'Angels Health', cnpj: '98.765.432/0001-10', city: 'Rio de Janeiro - RJ', logo: 'https://i.pravatar.cc/150?u=angels', status: 'active', industry: 'Saúde', email: 'contato@angels.com', phone: '(21) 2233-4455', address: 'Rua da Saúde, 500', since: '2021', description: 'Rede de clínicas.', contacts: [], documents: [], activeServices: [initialServices[0], initialServices[2]]
},
{
id: '3', name: 'Padaria do João MEI', fantasyName: 'Padaria do João', cnpj: '11.111.111/0001-11', city: 'Belo Horizonte - MG', logo: 'https://i.pravatar.cc/150?u=joao', status: 'overdue', industry: 'Varejo', email: 'joao@padaria.com', phone: '(31) 3333-3333', address: 'Rua do Pão, 10', since: '2023', description: 'Padaria artesanal.', contacts: [], documents: [], activeServices: [initialServices[1]]
}
];
const initialExpenses: Expense[] = [
{ id: 'e1', title: 'Aluguel Escritório', category: 'Operacional', amount: 3500, dueDate: getDynamicDate(-30), status: 'paid', type: 'fixed' },
{ id: 'e2', title: 'Servidor AWS', category: 'TI', amount: 850, dueDate: getDynamicDate(-28), status: 'paid', type: 'variable' },
{ id: 'e3', title: 'Energia Elétrica', category: 'Operacional', amount: 450, dueDate: getDynamicDate(-2), status: 'paid', type: 'variable' },
{ id: 'e4', title: 'Licença Software CRM', category: 'TI', amount: 200, dueDate: getDynamicDate(5), status: 'pending', type: 'fixed' },
{ id: 'e5', title: 'Folha de Pagamento', category: 'Pessoal', amount: 15000, dueDate: getDynamicDate(10), status: 'pending', type: 'fixed' },
{ id: 'e6', title: 'DAS Simples Nacional', category: 'Impostos', amount: 1200, dueDate: getDynamicDate(15), status: 'pending', type: 'variable' },
{ id: 'e7', title: 'Aluguel Escritório', category: 'Operacional', amount: 3500, dueDate: getDynamicDate(30), status: 'pending', type: 'fixed' },
];
const initialProposals: Proposal[] = [
{
id: 'p1', number: 'PROP-001', clientId: '1', clientName: 'Uda Studios', clientEmail: 'roberto@uda.com',
issueDate: getDynamicDate(-2), validUntil: getDynamicDate(5), status: 'sent', totalValue: 7500,
items: [
{ id: 'i1', description: 'Consultoria Financeira', quantity: 1, unitPrice: 2500, total: 2500 },
{ id: 'i2', description: 'Desenvolvimento Web', quantity: 1, unitPrice: 5000, total: 5000 }
],
notes: 'Pagamento 50% na entrada e 50% na entrega.'
}
];
const initialSuperAdmin: AppUser = { id: 'u1', name: 'Nella Vita', email: 'admin@comfi.com', role: 'super_admin', active: true, avatar: 'https://i.pravatar.cc/150?u=admin', permissions: [] };
const initialUsers: AppUser[] = [
initialSuperAdmin,
{ id: 'u2', name: 'João Comercial', email: 'vendas@comfi.com', role: 'user', active: true, avatar: 'https://i.pravatar.cc/150?u=joao', permissions: ['crm', 'kanban', 'calendar', 'proposals'] },
{ id: 'u3', name: 'Maria Financeiro', email: 'fin@comfi.com', role: 'user', active: true, avatar: 'https://i.pravatar.cc/150?u=maria', permissions: ['dashboard', 'receivables', 'payables', 'invoicing'] }
];
const initialTenant: TenantProfile = {
name: 'Minha Empresa S.A.',
cnpj: '00.000.000/0001-00',
email: 'contato@minhaempresa.com',
phone: '(11) 99999-9999',
address: 'Av. Brigadeiro Faria Lima, 1234, SP',
logo: '',
primaryColor: '#f97316'
};
const initialCategories: Category[] = [
{ id: 'c1', name: 'Operacional', type: 'expense', color: 'bg-red-50 text-red-600' },
{ id: 'c2', name: 'Administrativo', type: 'expense', color: 'bg-blue-50 text-blue-600' },
{ id: 'c3', name: 'Marketing', type: 'expense', color: 'bg-purple-50 text-purple-600' },
{ id: 'c4', name: 'Pessoal', type: 'expense', color: 'bg-yellow-50 text-yellow-600' },
{ id: 'c5', name: 'Impostos', type: 'expense', color: 'bg-slate-50 text-slate-600' },
{ id: 'c6', name: 'Serviços', type: 'income', color: 'bg-green-50 text-green-600' },
];
const generateInitialReceivables = (companies: Company[]): Receivable[] => {
const receivables: Receivable[] = [];
companies.forEach((company, cIndex) => {
company.activeServices.forEach((service, sIndex) => {
receivables.push({ id: `r-${company.id}-${sIndex}-past`, description: service.name, companyName: company.fantasyName || company.name, category: service.category, value: service.price, dueDate: getDynamicDate(-30 - (cIndex * 2)), status: 'paid', type: service.billingType === 'recurring' ? 'recurring' : 'one-time' });
const isOverdue = company.status === 'overdue' && sIndex === 0;
receivables.push({ id: `r-${company.id}-${sIndex}-curr`, description: service.name, companyName: company.fantasyName || company.name, category: service.category, value: service.price, dueDate: getDynamicDate(isOverdue ? -5 : 10 + (cIndex * 3)), status: isOverdue ? 'overdue' : 'pending', type: service.billingType === 'recurring' ? 'recurring' : 'one-time' });
if (service.billingType === 'recurring') {
receivables.push({ id: `r-${company.id}-${sIndex}-next`, description: service.name, companyName: company.fantasyName || company.name, category: service.category, value: service.price, dueDate: getDynamicDate(40 + (cIndex * 2)), status: 'pending', type: 'recurring' });
}
});
});
return receivables;
};
// --- CONTEXT INTERFACE ---
interface ComFiContextData {
services: Service[];
setServices: (data: Service[]) => void;
companies: Company[];
setCompanies: (data: Company[]) => void;
expenses: Expense[];
setExpenses: (data: Expense[]) => void;
clients: Client[];
setClients: (data: Client[]) => void;
receivables: Receivable[];
setReceivables: React.Dispatch<React.SetStateAction<Receivable[]>>;
proposals: Proposal[];
setProposals: (data: Proposal[]) => void;
currentUser: AppUser;
setCurrentUser: (user: AppUser) => void;
users: AppUser[];
setUsers: (users: AppUser[]) => void;
financialSummary: FinancialSummary;
addReceivable: (receivable: Receivable) => void;
tenant: TenantProfile;
setTenant: (tenant: TenantProfile) => void;
categories: Category[];
setCategories: (categories: Category[]) => void;
}
const ComFiContext = createContext<ComFiContextData>({} as ComFiContextData);
export const ComFiProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
// State Initialization
const [services, setServices] = useStickyState<Service[]>(initialServices, 'comfi_services');
const [companies, setCompanies] = useStickyState<Company[]>(initialCompanies, 'comfi_companies');
const [expenses, setExpenses] = useStickyState<Expense[]>(initialExpenses, 'comfi_expenses');
const [clients, setClients] = useStickyState<Client[]>([], 'comfi_clients');
const [currentUser, setCurrentUser] = useStickyState<AppUser>(initialSuperAdmin, 'comfi_current_user');
const [users, setUsers] = useStickyState<AppUser[]>(initialUsers, 'comfi_users');
const [tenant, setTenant] = useStickyState<TenantProfile>(initialTenant, 'comfi_tenant');
const [categories, setCategories] = useStickyState<Category[]>(initialCategories, 'comfi_categories');
const [proposals, setProposals] = useStickyState<Proposal[]>(initialProposals, 'comfi_proposals');
const [receivables, setReceivables] = useState<Receivable[]>(() => {
try {
const saved = window.localStorage.getItem('comfi_receivables');
if (saved) return JSON.parse(saved);
return generateInitialReceivables(initialCompanies);
} catch (e) {
return generateInitialReceivables(initialCompanies);
}
});
useEffect(() => {
window.localStorage.setItem('comfi_receivables', JSON.stringify(receivables));
}, [receivables]);
// Derived State: Financial Summary
const financialSummary = useMemo(() => {
const currentMonth = new Date().getMonth();
const currentYear = new Date().getFullYear();
const isCurrentMonth = (dateStr: string) => dateStr.startsWith(`${currentYear}-${String(currentMonth + 1).padStart(2, '0')}`);
const currentReceivables = receivables.filter(r => isCurrentMonth(r.dueDate));
const currentExpenses = expenses.filter(e => isCurrentMonth(e.dueDate));
const totalRevenue = currentReceivables.reduce((acc, r) => acc + r.value, 0);
const totalExpenses = currentExpenses.reduce((acc, expense) => acc + expense.amount, 0);
const profit = totalRevenue - totalExpenses;
const payablePending = expenses.filter(e => e.status !== 'paid').reduce((acc, expense) => acc + expense.amount, 0);
const receivablePending = receivables.filter(r => r.status !== 'paid').reduce((acc, r) => acc + r.value, 0);
return { totalRevenue, totalExpenses, profit, payablePending, receivablePending };
}, [receivables, expenses]);
const addReceivable = (newRec: Receivable) => {
setReceivables(prev => [...prev, newRec]);
};
return (
<ComFiContext.Provider value={{
services, setServices,
companies, setCompanies,
expenses, setExpenses,
clients, setClients,
receivables, setReceivables,
currentUser, setCurrentUser,
users, setUsers,
financialSummary,
addReceivable,
tenant, setTenant,
categories, setCategories,
proposals, setProposals
}}>
{children}
</ComFiContext.Provider>
);
};
export const useComFi = () => {
const context = useContext(ComFiContext);
if (!context) throw new Error('useComFi must be used within a ComFiProvider');
return context;
};