Commit inicial - upload de todos os arquivos da pasta

This commit is contained in:
2026-06-13 17:32:41 -03:00
commit 759e2663ec
311 changed files with 31868 additions and 0 deletions

0
src/constants/.gitkeep Normal file
View File

View File

@@ -0,0 +1,63 @@
import { describe, expect, it } from 'vitest';
import { breadcrumbRoutes } from '../breadcrumbs';
const expectedRoutes = [
'/',
'/entregaveis',
'/entregaveis/:id',
'/ordens-servico',
'/sprints',
'/profissionais',
'/usuarios',
'/usuarios/novo',
'/usuarios/:id/editar',
'/clientes',
'/configuracoes',
];
describe('breadcrumbRoutes', () => {
it('cobre todas as rotas do sistema', () => {
const patterns = breadcrumbRoutes.map((r) => r.pattern);
for (const route of expectedRoutes) {
expect(patterns).toContain(route);
}
});
it('cada configuração tem pelo menos um item', () => {
for (const route of breadcrumbRoutes) {
expect(route.items.length).toBeGreaterThanOrEqual(1);
}
});
it('último item de cada configuração não tem "to"', () => {
for (const route of breadcrumbRoutes) {
const lastItem = route.items[route.items.length - 1];
expect(lastItem.to).toBeUndefined();
}
});
it('rotas com parâmetros usam padrão :param', () => {
const dynamicRoutes = breadcrumbRoutes.filter((r) => r.pattern.includes(':'));
expect(dynamicRoutes.length).toBeGreaterThan(0);
for (const route of dynamicRoutes) {
expect(route.pattern).toMatch(/:\w+/);
}
});
it('Dashboard é sempre a raiz dos breadcrumbs', () => {
for (const route of breadcrumbRoutes) {
expect(route.items[0].label).toBe('Dashboard');
}
});
it('página Dashboard não tem link no item', () => {
const dashboardRoute = breadcrumbRoutes.find((r) => r.pattern === '/');
expect(dashboardRoute?.items[0].to).toBeUndefined();
});
it('não inclui rotas de autenticação', () => {
const patterns = breadcrumbRoutes.map((r) => r.pattern);
expect(patterns).not.toContain('/login');
expect(patterns).not.toContain('/trocar-senha');
});
});

View File

@@ -0,0 +1,143 @@
import type { BreadcrumbItem } from '../components/ui/Breadcrumbs';
export interface BreadcrumbRouteConfig {
pattern: string;
items: BreadcrumbItem[];
}
const dashboard: BreadcrumbItem = { label: 'Dashboard', to: '/' };
export const breadcrumbRoutes: BreadcrumbRouteConfig[] = [
{ pattern: '/', items: [{ label: 'Dashboard' }] },
{ pattern: '/entregaveis', items: [dashboard, { label: 'Entregáveis' }] },
{
pattern: '/entregaveis/novo',
items: [dashboard, { label: 'Entregáveis', to: '/entregaveis' }, { label: 'Novo Entregável' }],
},
{
pattern: '/entregaveis/:id',
items: [dashboard, { label: 'Entregáveis', to: '/entregaveis' }, { label: 'Entregável #:id' }],
},
{
pattern: '/entregaveis/:id/editar',
items: [
dashboard,
{ label: 'Entregáveis', to: '/entregaveis' },
{ label: 'Editar Entregável' },
],
},
{ pattern: '/ordens-servico', items: [dashboard, { label: 'Ordens de Serviço' }] },
{
pattern: '/ordens-servico/nova',
items: [dashboard, { label: 'Ordens de Serviço', to: '/ordens-servico' }, { label: 'Nova OS' }],
},
{
pattern: '/ordens-servico/:id',
items: [dashboard, { label: 'Ordens de Serviço', to: '/ordens-servico' }, { label: ':name' }],
},
{
pattern: '/ordens-servico/:id/editar',
items: [
dashboard,
{ label: 'Ordens de Serviço', to: '/ordens-servico' },
{ label: 'Editar OS' },
],
},
{ pattern: '/sprints', items: [dashboard, { label: 'Sprints' }] },
{
pattern: '/sprints/novo',
items: [dashboard, { label: 'Sprints', to: '/sprints' }, { label: 'Nova Sprint' }],
},
{
pattern: '/sprints/:id',
items: [dashboard, { label: 'Sprints', to: '/sprints' }, { label: ':name' }],
},
{
pattern: '/sprints/:id/editar',
items: [dashboard, { label: 'Sprints', to: '/sprints' }, { label: 'Editar :name' }],
},
{ pattern: '/profissionais', items: [dashboard, { label: 'Profissionais' }] },
{
pattern: '/profissionais/novo',
items: [
dashboard,
{ label: 'Profissionais', to: '/profissionais' },
{ label: 'Novo Profissional' },
],
},
{
pattern: '/profissionais/:id/editar',
items: [dashboard, { label: 'Profissionais', to: '/profissionais' }, { label: 'Editar :name' }],
},
{ pattern: '/usuarios', items: [dashboard, { label: 'Usuários' }] },
{
pattern: '/usuarios/novo',
items: [dashboard, { label: 'Usuários', to: '/usuarios' }, { label: 'Novo Usuário' }],
},
{
pattern: '/usuarios/:id/editar',
items: [dashboard, { label: 'Usuários', to: '/usuarios' }, { label: 'Editar :name' }],
},
{ pattern: '/clientes', items: [dashboard, { label: 'Clientes' }] },
{
pattern: '/clientes/novo',
items: [dashboard, { label: 'Clientes', to: '/clientes' }, { label: 'Novo Cliente' }],
},
{
pattern: '/clientes/:id',
items: [dashboard, { label: 'Clientes', to: '/clientes' }, { label: ':name' }],
},
{
pattern: '/clientes/:id/editar',
items: [dashboard, { label: 'Clientes', to: '/clientes' }, { label: 'Editar :name' }],
},
{
pattern: '/clientes/:id/contratos/novo',
items: [
dashboard,
{ label: 'Clientes', to: '/clientes' },
{ label: ':name' },
{ label: 'Novo Contrato' },
],
},
{
pattern: '/clientes/:id/itens-contrato/novo',
items: [
dashboard,
{ label: 'Clientes', to: '/clientes' },
{ label: ':name' },
{ label: 'Novo Item de Contrato' },
],
},
{
pattern: '/clientes/:id/itens-contrato/:itemId/editar',
items: [
dashboard,
{ label: 'Clientes', to: '/clientes' },
{ label: ':name' },
{ label: 'Editar Item de Contrato' },
],
},
{
pattern: '/clientes/:id/projetos/novo',
items: [
dashboard,
{ label: 'Clientes', to: '/clientes' },
{ label: ':name' },
{ label: 'Novo Projeto' },
],
},
{
pattern: '/contratos/:id/editar',
items: [dashboard, { label: 'Clientes', to: '/clientes' }, { label: 'Editar Contrato' }],
},
{
pattern: '/projetos/:id/editar',
items: [dashboard, { label: 'Clientes', to: '/clientes' }, { label: 'Editar Projeto' }],
},
{ pattern: '/configuracoes', items: [dashboard, { label: 'Configurações' }] },
{
pattern: '/admin/integracoes/api-keys',
items: [dashboard, { label: 'Integrações' }, { label: 'API Keys' }],
},
];

View File

@@ -0,0 +1,28 @@
import { ContractItemType } from '../types/contract-item.types';
export const CONTRACT_ITEM_TYPE_LABELS: Record<ContractItemType, string> = {
[ContractItemType.UST]: 'Unidade de Serviço Técnico (UST)',
[ContractItemType.SAAS_LICENSE]: 'Licença SaaS',
};
export interface ContractItemTypeBadgeConfig {
label: string;
color: 'gray' | 'blue';
}
const badgeConfigMap: Record<ContractItemType, ContractItemTypeBadgeConfig> = {
[ContractItemType.UST]: {
label: CONTRACT_ITEM_TYPE_LABELS[ContractItemType.UST],
color: 'gray',
},
[ContractItemType.SAAS_LICENSE]: {
label: CONTRACT_ITEM_TYPE_LABELS[ContractItemType.SAAS_LICENSE],
color: 'blue',
},
};
export function getContractItemTypeBadgeConfig(
type: ContractItemType,
): ContractItemTypeBadgeConfig {
return badgeConfigMap[type];
}

View File

@@ -0,0 +1,45 @@
import { DeliverableStatus } from '../types/deliverable.types';
type StatusVariant =
| 'success'
| 'danger'
| 'warning'
| 'info'
| 'primary'
| 'purple'
| 'success-dark'
| 'danger-dark'
| 'neutral';
export interface StatusConfig {
label: string;
variant: StatusVariant;
}
const statusConfigMap: Record<DeliverableStatus, StatusConfig> = {
[DeliverableStatus.RASCUNHO]: { label: 'Rascunho', variant: 'neutral' },
[DeliverableStatus.EMITIDA]: { label: 'Emitida', variant: 'info' },
[DeliverableStatus.EM_EXECUCAO]: { label: 'Em Execução', variant: 'warning' },
[DeliverableStatus.AGUARDANDO_VALIDACAO]: { label: 'Aguardando Validação', variant: 'warning' },
[DeliverableStatus.EM_REVISAO]: { label: 'Em Revisão', variant: 'purple' },
[DeliverableStatus.APROVADA]: { label: 'Aprovada', variant: 'success' },
[DeliverableStatus.GLOSADA]: { label: 'Glosada', variant: 'danger' },
[DeliverableStatus.ENCERRADA]: { label: 'Encerrada', variant: 'success-dark' },
[DeliverableStatus.CANCELADA]: { label: 'Cancelada', variant: 'danger-dark' },
[DeliverableStatus.AGUARDANDO_PAGAMENTO]: {
label: 'Aguardando Pagamento',
variant: 'warning',
},
[DeliverableStatus.PAGA]: { label: 'Paga', variant: 'success' },
};
export function getStatusConfig(status: DeliverableStatus): StatusConfig {
return statusConfigMap[status];
}
export const DELIVERABLE_STATUS_OPTIONS = Object.entries(statusConfigMap).map(
([value, config]) => ({
value,
label: config.label,
}),
);

View File

@@ -0,0 +1,23 @@
export const DeliverableType = {
DESCOBERTA: 'DESCOBERTA',
DESIGN: 'DESIGN',
ARQUITETURA: 'ARQUITETURA',
CONSTRUCAO: 'CONSTRUCAO',
MANUTENCAO: 'MANUTENCAO',
LICENCA: 'LICENCA',
} as const;
export type DeliverableType = (typeof DeliverableType)[keyof typeof DeliverableType];
export const DELIVERABLE_TYPE_LABELS: Record<DeliverableType, string> = {
[DeliverableType.DESCOBERTA]: 'Descoberta',
[DeliverableType.DESIGN]: 'Design',
[DeliverableType.ARQUITETURA]: 'Arquitetura',
[DeliverableType.CONSTRUCAO]: 'Construção',
[DeliverableType.MANUTENCAO]: 'Manutenção',
[DeliverableType.LICENCA]: 'Licença',
};
export const DELIVERABLE_TYPE_OPTIONS = Object.entries(DELIVERABLE_TYPE_LABELS).map(
([value, label]) => ({ value, label }),
);

View File

@@ -0,0 +1,70 @@
import type { UserRole } from '../types/auth.types';
export const FINANCIAL_FIELDS = new Set([
'totalValue',
'ustValue',
'ustQuantity',
'reservedUst',
'calculatedValue',
'timeboxDescoberta',
'timeboxDesign',
'timeboxArquitetura',
'timeboxConstrucao',
'timeboxManutencao',
'numWeeks',
]);
// Mirrors backend VISIBILITY_MAP (visibility-map.ts).
// ADMIN is not listed — handled inline (always visible).
// Default-deny: field absent from a client role's set = invisible.
export const FIELD_VISIBILITY_MAP: Partial<Record<UserRole, Set<string>>> = {
GESTOR_PROJETOS: new Set([
'id', 'code', 'name', 'title', 'description', 'status', 'type',
'isActive', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy',
'startDate', 'endDate', 'expectedEndDate',
'clientId', 'contractId', 'projectId', 'contractItemId', 'workOrderId', 'sprintId',
'client', 'contract', 'project', 'contractItem', 'workOrder', 'sprint',
'statusHistory', 'assignments', 'backlogItems', 'timelineEvents', 'notes',
'sprintHistory', 'allocations', 'deliverables', 'projects',
'contractItems', 'users', 'profiles', 'allocationTemplates',
'email', 'role', 'clientSubRole', 'mustChangePassword',
'workOrders', 'goal', 'sprintType', 'totalUst', 'itemType',
'document', 'phone', 'contactName',
'professional', 'professionals',
'acceptanceCriteria', 'rejectionReason', 'sortOrder',
'noteType', 'isRelevant',
'profileId', 'quantity', 'allocationPercentage', 'profile',
'token', 'scopes', 'lastFourChars', 'expiresAt', 'lastUsedAt', 'rateLimitPerMinute',
]),
PO: new Set([
'id', 'code', 'title', 'description', 'status', 'type',
'isActive', 'createdAt', 'updatedAt',
'startDate', 'expectedEndDate',
'clientId', 'contractId', 'projectId',
'client', 'contract', 'project',
'backlogItems', 'timelineEvents',
'acceptanceCriteria', 'rejectionReason', 'sortOrder',
'name', 'email', 'role',
]),
FISCAL_CONTRATO: new Set([
'id', 'code', 'title', 'description', 'status', 'type',
'isActive', 'createdAt', 'updatedAt',
'startDate', 'expectedEndDate',
'clientId', 'contractId', 'projectId',
'client', 'contract', 'project',
'statusHistory', 'timelineEvents',
'name', 'email', 'role',
]),
GESTOR_CONTRATO: new Set([
'id', 'code', 'name', 'description', 'status',
'isActive', 'createdAt', 'updatedAt',
'startDate', 'endDate',
'contractId', 'contractItemId',
'contract', 'contractItem', 'projects', 'deliverables', 'statusHistory',
'totalUst', 'itemType',
'email', 'role',
]),
};

View File

@@ -0,0 +1,29 @@
import {
LayoutDashboard,
FileText,
Timer,
Users,
UserCog,
Building2,
Briefcase,
KeyRound,
} from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
export interface NavItemConfig {
icon: LucideIcon;
label: string;
to: string;
}
export const navigationItems: NavItemConfig[] = [
{ icon: LayoutDashboard, label: 'Dashboard', to: '/' },
{ icon: Briefcase, label: 'Ordens de Serviço', to: '/ordens-servico' },
{ icon: FileText, label: 'Entregáveis', to: '/entregaveis' },
{ icon: Building2, label: 'Clientes', to: '/clientes' },
{ icon: Timer, label: 'Sprints', to: '/sprints' },
{ icon: Users, label: 'Profissionais', to: '/profissionais' },
{ icon: UserCog, label: 'Usuários', to: '/usuarios' },
{ icon: KeyRound, label: 'API Keys', to: '/admin/integracoes/api-keys' },
// { icon: Settings, label: 'Configurações', to: '/configuracoes' },
];

View File

@@ -0,0 +1,21 @@
import type { UserRole } from '../types/auth.types';
const OPERATOR_ROUTES = ['/', '/entregaveis', '/ordens-servico', '/sprints', '/profissionais', '/clientes'];
export const routePermissions: Record<UserRole, string[]> = {
ADMIN: [
'/',
'/entregaveis',
'/ordens-servico',
'/sprints',
'/profissionais',
'/usuarios',
'/clientes',
'/configuracoes',
'/admin/integracoes/api-keys',
],
GESTOR_PROJETOS: OPERATOR_ROUTES,
PO: ['/', '/entregaveis', '/ordens-servico', '/clientes', '/sprints', '/po'],
FISCAL_CONTRATO: ['/', '/entregaveis', '/ordens-servico', '/clientes', '/sprints', '/fiscal-contrato'],
GESTOR_CONTRATO: ['/', '/ordens-servico', '/clientes', '/gestor-contrato'],
};

View File

@@ -0,0 +1,20 @@
export const PROFESSIONAL_ROLE_OPTIONS = [
{ value: 'Gerente de Operação', label: 'Gerente de Operação' },
{ value: 'Gerente de Projetos', label: 'Gerente de Projetos' },
{ value: 'Product Owner', label: 'Product Owner' },
{ value: 'Scrum Master', label: 'Scrum Master' },
{ value: 'Tech Lead', label: 'Tech Lead' },
{ value: 'Arquiteto de Software', label: 'Arquiteto de Software' },
{ value: 'Desenvolvedor Frontend', label: 'Desenvolvedor Frontend' },
{ value: 'Desenvolvedor Backend', label: 'Desenvolvedor Backend' },
{ value: 'Desenvolvedor Fullstack', label: 'Desenvolvedor Fullstack' },
{ value: 'Desenvolvedor Mobile', label: 'Desenvolvedor Mobile' },
{ value: 'DevOps', label: 'DevOps' },
{ value: 'QA / Analista de Testes', label: 'QA / Analista de Testes' },
{ value: 'UX/UI Designer', label: 'UX/UI Designer' },
{ value: 'Analista de Dados', label: 'Analista de Dados' },
{ value: 'Analista de Sistemas', label: 'Analista de Sistemas' },
{ value: 'Analista de Suporte', label: 'Analista de Suporte' },
{ value: 'DBA', label: 'DBA' },
{ value: 'Analista de Segurança', label: 'Analista de Segurança' },
];

39
src/constants/sprint.ts Normal file
View File

@@ -0,0 +1,39 @@
import type { SprintType, SprintStatus } from '../types/sprint.types';
export const SPRINT_TYPE_LABELS: Record<SprintType, string> = {
DESCOBERTA: 'Descoberta',
DESIGN: 'Design',
ARQUITETURA: 'Arquitetura',
CONSTRUCAO: 'Construção',
};
export const SPRINT_TYPE_OPTIONS = Object.entries(SPRINT_TYPE_LABELS).map(([value, label]) => ({
value,
label,
}));
export const SPRINT_STATUS_LABELS: Record<SprintStatus, string> = {
RASCUNHO: 'Rascunho',
EM_EXECUCAO: 'Em Execução',
FINALIZADA: 'Finalizada',
CANCELADA: 'Cancelada',
};
export const SPRINT_STATUS_OPTIONS = Object.entries(SPRINT_STATUS_LABELS).map(([value, label]) => ({
value,
label,
}));
export const SPRINT_STATUS_VARIANTS: Record<SprintStatus, string> = {
RASCUNHO: 'info',
EM_EXECUCAO: 'success',
FINALIZADA: 'neutral',
CANCELADA: 'danger',
};
export const SPRINT_TYPE_VARIANTS: Record<SprintType, string> = {
DESCOBERTA: 'purple',
DESIGN: 'info',
ARQUITETURA: 'warning',
CONSTRUCAO: 'primary',
};

View File

@@ -0,0 +1,65 @@
import { Briefcase, FileEdit, Send, PlayCircle, CheckCircle2, XCircle } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import { WorkOrderStatus } from '../types/work-order.types';
type StatusVariant =
| 'success'
| 'danger'
| 'warning'
| 'info'
| 'primary'
| 'purple'
| 'success-dark'
| 'danger-dark'
| 'neutral';
export interface WorkOrderStatusConfig {
label: string;
variant: StatusVariant;
color: string;
icon: LucideIcon;
}
const configMap: Record<WorkOrderStatus, WorkOrderStatusConfig> = {
[WorkOrderStatus.RASCUNHO]: {
label: 'Rascunho',
variant: 'neutral',
color: 'gray',
icon: FileEdit,
},
[WorkOrderStatus.EMITIDA]: {
label: 'Emitida',
variant: 'info',
color: 'blue',
icon: Send,
},
[WorkOrderStatus.EM_EXECUCAO]: {
label: 'Em execução',
variant: 'success',
color: 'green',
icon: PlayCircle,
},
[WorkOrderStatus.TOTALMENTE_PAGA]: {
label: 'Totalmente paga',
variant: 'purple',
color: 'purple',
icon: CheckCircle2,
},
[WorkOrderStatus.CANCELADA]: {
label: 'Cancelada',
variant: 'danger-dark',
color: 'red',
icon: XCircle,
},
};
export function getWorkOrderStatusConfig(status: WorkOrderStatus): WorkOrderStatusConfig {
return configMap[status];
}
export const WORK_ORDER_STATUS_OPTIONS = Object.entries(configMap).map(([value, config]) => ({
value,
label: config.label,
}));
export const WorkOrderIcon = Briefcase;