diff --git a/.agent/rules/convex-svelte-guidelines.md b/.agent/rules/convex-svelte-guidelines.md new file mode 100644 index 0000000..f61e324 --- /dev/null +++ b/.agent/rules/convex-svelte-guidelines.md @@ -0,0 +1,275 @@ +--- +trigger: glob +globs: **/*.svelte, **/*.ts, **/*.svelte.ts +--- + +# Convex + Svelte Guidelines + +## Overview + +These guidelines describe how to write **Convex** backend code **and** consume it from a **Svelte** (SvelteKit) frontend. The syntax for Convex functions stays exactly the same, but the way you import and call them from the client differs from a React/Next.js project. Below you will find the adapted sections from the original Convex style guide with Svelte‑specific notes. + +--- + +## 1. Function Syntax (Backend) + +> **No change** – keep the new Convex function syntax. + +```typescript +import { + query, + mutation, + action, + internalQuery, + internalMutation, + internalAction +} from './_generated/server'; +import { v } from 'convex/values'; + +export const getUser = query({ + args: { userId: v.id('users') }, + returns: v.object({ name: v.string(), email: v.string() }), + handler: async (ctx, args) => { + const user = await ctx.db.get(args.userId); + if (!user) throw new Error('User not found'); + return { name: user.name, email: user.email }; + } +}); +``` + +--- + +## 2. HTTP Endpoints (Backend) + +> **No change** – keep the same `convex/http.ts` file. + +```typescript +import { httpRouter } from 'convex/server'; +import { httpAction } from './_generated/server'; + +const http = httpRouter(); + +http.route({ + path: '/api/echo', + method: 'POST', + handler: httpAction(async (ctx, req) => { + const body = await req.bytes(); + return new Response(body, { status: 200 }); + }) +}); +``` + +--- + +## 3. Validators (Backend) + +> **No change** – keep the same validators (`v.string()`, `v.id()`, etc.). + +--- + +## 4. Function Registration (Backend) + +> **No change** – use `query`, `mutation`, `action` for public functions and `internal*` for private ones. + +--- + +## 5. Function Calling from **Svelte** + +### 5.1 Install the Convex client + +```bash +npm i convex @convex-dev/convex-svelte +``` + +> The `@convex-dev/convex-svelte` package provides a thin wrapper that works with Svelte stores. + +### 5.2 Initialise the client (e.g. in `src/lib/convex.ts`) + +```typescript +import { createConvexClient } from '@convex-dev/convex-svelte'; + +export const convex = createConvexClient({ + url: import.meta.env.VITE_CONVEX_URL // set in .env +}); +``` + +### 5.3 Using queries in a component + +```svelte + + +{#if loading} +

Loading…

+{:else if error} +

{error}

+{:else if user} +

{user.name}

+

{user.email}

+{/if} +``` + +### 5.4 Using mutations in a component + +```svelte + + + + +{#if error}

{error}

{/if} +``` + +### 5.5 Using **actions** (Node‑only) from Svelte + +Actions run in a Node environment, so they cannot be called directly from the browser. Use a **mutation** that internally calls the action, or expose a HTTP endpoint that triggers the action. + +--- + +## 6. Scheduler / Cron (Backend) + +> Same as original guide – define `crons.ts` and export the default `crons` object. + +--- + +## 7. File Storage (Backend) + +> Same as original guide – use `ctx.storage.getUrl()` and query `_storage` for metadata. + +--- + +## 8. TypeScript Helpers (Backend) + +> Keep using `Id<'table'>` from `./_generated/dataModel`. + +--- + +## 9. Svelte‑Specific Tips + +| Topic | Recommendation | +| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| **Store‑based data** | If you need reactive data across many components, wrap `convex.query` in a Svelte store (`readable`, `writable`). | +| **Error handling** | Use `try / catch` around every client call; surface the error in the UI. | +| **SSR / SvelteKit** | Calls made in `load` functions run on the server; you can use `convex.query` there without worrying about the browser environment. | +| **Environment variables** | Prefix with `VITE_` for client‑side access (`import.meta.env.VITE_CONVEX_URL`). | +| **Testing** | Use the Convex mock client (`createMockConvexClient`) provided by `@convex-dev/convex-svelte` for unit tests. | + +--- + +## 10. Full Example (SvelteKit + Convex) + +### 10.1 Backend (`convex/users.ts`) + +```typescript +import { mutation, query } from './_generated/server'; +import { v } from 'convex/values'; + +export const createUser = mutation({ + args: { name: v.string() }, + returns: v.id('users'), + handler: async (ctx, args) => { + return await ctx.db.insert('users', { name: args.name }); + } +}); + +export const getUser = query({ + args: { userId: v.id('users') }, + returns: v.object({ name: v.string() }), + handler: async (ctx, args) => { + const user = await ctx.db.get(args.userId); + if (!user) throw new Error('Not found'); + return { name: user.name }; + } +}); +``` + +### 10.2 Frontend (`src/routes/+page.svelte`) + +```svelte + + + + +{#if createdId}

Created user id: {createdId}

{/if} +{#if error}

{error}

{/if} +``` + +--- + +## 11. Checklist for New Files + +- ✅ All Convex functions use the **new syntax** (`query({ … })`). +- ✅ Every public function has **argument** and **return** validators. +- ✅ Svelte components import the generated `api` object from `convex/_generated/api`. +- ✅ All client calls use the `convex` instance from `$lib/convex`. +- ✅ Environment variable `VITE_CONVEX_URL` is defined in `.env`. +- ✅ Errors are caught and displayed in the UI. +- ✅ Types are imported from `convex/_generated/dataModel` when needed. + +--- + +## 12. References + +- Convex Docs – [Functions](https://docs.convex.dev/functions) +- Convex Svelte SDK – [`@convex-dev/convex-svelte`](https://github.com/convex-dev/convex-svelte) +- SvelteKit Docs – [Loading Data](https://kit.svelte.dev/docs/loading) + +--- + +_Keep these guidelines alongside the existing `svelte-rules.md` so that contributors have a single source of truth for both frontend and backend conventions._ diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..0a40e7d --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,30 @@ +name: Build Docker images + +on: + push: + branches: ["main"] + +jobs: + build-and-push-dockerfile-image: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} # Make sure to add the secrets in your repository in -> Settings -> Secrets (Actions) -> New repository secret + password: ${{ secrets.DOCKERHUB_TOKEN }} # Make sure to add the secrets in your repository in -> Settings -> Secrets (Actions) -> New repository secret + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./Dockerfile + push: true + # Make sure to replace with your own namespace and repository + tags: | + namespace/example:latest + platforms: linux/amd64 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0e0d48d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,58 @@ +# Use the official Bun image +FROM oven/bun:1 AS base + +# Set the working directory inside the container +WORKDIR /usr/src/app + +# Create a non-root user for security +RUN addgroup --system --gid 1001 sveltekit +RUN adduser --system --uid 1001 sveltekit + +# Copy package.json and bun.lockb (if available) +COPY package.json bun.lockb* ./ + +# Install dependencies (including dev dependencies for build) +RUN bun install --frozen-lockfile + +# Copy the source code +COPY . . + +# Prepare SvelteKit and build the application +RUN bun run prepare +RUN bun run build +RUN bun run db:migrate + +# Production stage +FROM oven/bun:1-slim AS production + +# Set working directory +WORKDIR /usr/src/app + +# Create non-root user +RUN addgroup --system --gid 1001 sveltekit +RUN adduser --system --uid 1001 sveltekit + +# Copy built application from base stage +COPY --from=base --chown=sveltekit:sveltekit /usr/src/app/build ./build +COPY --from=base --chown=sveltekit:sveltekit /usr/src/app/package.json ./package.json +COPY --from=base --chown=sveltekit:sveltekit /usr/src/app/node_modules ./node_modules + +# Copy any additional files needed for runtime +COPY --from=base --chown=sveltekit:sveltekit /usr/src/app/static ./static + +# Switch to non-root user +USER sveltekit + +# Expose the port that the app runs on +EXPOSE 5173 + +# Set environment variables +ENV NODE_ENV=production +ENV PORT=5173 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD bun --version || exit 1 + +# Start the application +CMD ["bun", "./build/index.js"] diff --git a/apps/web/package.json b/apps/web/package.json index c403caa..55c201c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -23,6 +23,7 @@ "postcss": "^8.5.6", "svelte": "^5.38.1", "svelte-check": "^4.3.1", + "svelte-dnd-action": "^0.9.67", "tailwindcss": "^4.1.12", "typescript": "catalog:", "vite": "^7.1.2" diff --git a/apps/web/src/lib/components/RelogioPrazo.svelte b/apps/web/src/lib/components/RelogioPrazo.svelte new file mode 100644 index 0000000..14c0b91 --- /dev/null +++ b/apps/web/src/lib/components/RelogioPrazo.svelte @@ -0,0 +1,124 @@ + + +{#if tempoInfo} + {@const info = tempoInfo} +
+ {#if info.tipo === 'concluida'} + + + Concluída em {info.dias > 0 ? `${info.dias} ${info.dias === 1 ? 'dia' : 'dias'} e ` : ''} + {info.horas} {info.horas === 1 ? 'hora' : 'horas'} + {#if !info.dentroDoPrazo && info.diasAtrasado > 0} + ({info.diasAtrasado} {info.diasAtrasado === 1 ? 'dia' : 'dias'} fora do prazo) + {/if} + + {:else if info.tipo === 'andamento'} + + + {#if info.atrasado} + {info.dias > 0 ? `${info.dias} ${info.dias === 1 ? 'dia' : 'dias'} e ` : ''} + {info.horas} {info.horas === 1 ? 'hora' : 'horas'} atrasado + {:else} + {info.dias > 0 ? `${info.dias} ${info.dias === 1 ? 'dia' : 'dias'} e ` : ''} + {info.horas} {info.horas === 1 ? 'hora' : 'horas'} para concluir + {/if} + + {/if} +
+{/if} + diff --git a/apps/web/src/routes/(dashboard)/configuracoes/setores/+page.svelte b/apps/web/src/routes/(dashboard)/configuracoes/setores/+page.svelte new file mode 100644 index 0000000..e7f03d9 --- /dev/null +++ b/apps/web/src/routes/(dashboard)/configuracoes/setores/+page.svelte @@ -0,0 +1,397 @@ + + +
+ +
+
+
+
+
+ + Configurações + +

+ Gestão de Setores +

+

+ Gerencie os setores da organização. Setores são utilizados para organizar funcionários e + definir responsabilidades em fluxos de trabalho. +

+
+
+ + + +
+
+
+ + +
+ {#if setoresQuery.isLoading} +
+ +
+ {:else if !setoresQuery.data || setoresQuery.data.length === 0} +
+ + + +

Nenhum setor cadastrado

+

Clique em "Novo Setor" para criar o primeiro setor.

+
+ {:else} +
+ + + + + + + + + + + {#each setoresQuery.data as setor (setor._id)} + + + + + + + {/each} + +
SiglaNomeCriado emAções
+ + {setor.sigla} + + {setor.nome}{formatDate(setor.createdAt)} +
+ + + + + + +
+
+
+ {/if} +
+
+ + +{#if showModal} + +{/if} + + +{#if showDeleteModal && setorToDelete} + +{/if} + diff --git a/apps/web/src/routes/(dashboard)/fluxos/+page.svelte b/apps/web/src/routes/(dashboard)/fluxos/+page.svelte new file mode 100644 index 0000000..6464732 --- /dev/null +++ b/apps/web/src/routes/(dashboard)/fluxos/+page.svelte @@ -0,0 +1,433 @@ + + +
+ +
+
+
+
+
+ + Gestão de Fluxos + +

+ Templates de Fluxo +

+

+ Crie e gerencie templates de fluxo de trabalho. Templates definem os passos e + responsabilidades que serão instanciados para projetos ou contratos. +

+
+
+ + + + + + +
+
+
+ + +
+ {#if templatesQuery.isLoading} +
+ +
+ {:else if !templatesQuery.data || templatesQuery.data.length === 0} +
+ +

Nenhum template encontrado

+

+ {statusFilter ? 'Não há templates com este status.' : 'Clique em "Novo Template" para criar o primeiro.'} +

+
+ {:else} +
+ {#each templatesQuery.data as template (template._id)} + {@const statusBadge = getStatusBadge(template.status)} +
+
+
+

{template.name}

+ {statusBadge.label} +
+ + {#if template.description} +

+ {template.description} +

+ {/if} + +
+ + + {template.stepsCount} passos + + + + {formatDate(template.createdAt)} + +
+ +
+ + + + + Editar + +
+
+
+ {/each} +
+ {/if} +
+ + +
+ + + Ver Fluxos de Trabalho + +
+
+ + +{#if showCreateModal} + +{/if} + + +{#if showDeleteModal && templateToDelete} + +{/if} + diff --git a/apps/web/src/routes/(dashboard)/fluxos/[id]-fluxo/+page.svelte b/apps/web/src/routes/(dashboard)/fluxos/[id]-fluxo/+page.svelte new file mode 100644 index 0000000..18af39c --- /dev/null +++ b/apps/web/src/routes/(dashboard)/fluxos/[id]-fluxo/+page.svelte @@ -0,0 +1,722 @@ + + +
+ {#if instanceQuery.isLoading} +
+ +
+ {:else if !instanceQuery.data} +
+ +

Fluxo não encontrado

+ Voltar para lista +
+ {:else} + {@const instance = instanceQuery.data.instance} + {@const steps = instanceQuery.data.steps} + {@const statusBadge = getInstanceStatusBadge(instance.status)} + + +
+
+
+
+
+ + + Voltar + + {statusBadge.label} +
+ +
+
+

+ {instance.templateName ?? 'Fluxo'} +

+
+ {#if instance.contratoId} +
+ Contrato + {instance.contratoId} +
+ {/if} +
+ + Gerente: {instance.managerName ?? '-'} +
+
+ + Iniciado: {formatDate(instance.startedAt)} +
+
+
+ + {#if instance.status === 'active'} + + + + {/if} +
+
+
+ + + {#if processingError} +
+ + {processingError} + +
+ {/if} + + +
+

Timeline do Fluxo

+ +
+ {#each steps as step, index (step._id)} + {@const stepStatus = getStatusBadge(step.status)} + {@const isCurrent = isStepCurrent(step._id)} + {@const overdue = step.status !== 'completed' && isOverdue(step.dueDate)} + +
+ + {#if index < steps.length - 1} +
+ {/if} + + +
+ {#if step.status === 'completed'} + + {:else if step.status === 'blocked'} + + {:else} + {index + 1} + {/if} +
+ + +
+
+
+
+

{step.stepName}

+ {stepStatus.label} + {#if overdue} + Atrasado + {/if} +
+ {#if step.stepDescription} +

{step.stepDescription}

+ {/if} +
+ + + {step.setorNome ?? 'Setor não definido'} + + {#if step.assignedToName} + + + {step.assignedToName} + + {/if} + {#if step.dueDate} + + + Prazo: {formatDate(step.dueDate)} + + {/if} +
+
+ + + {#if instance.status === 'active'} +
+ {#if step.status === 'pending'} + + + + {:else if step.status === 'in_progress'} + + + + + {:else if step.status === 'blocked'} + + + + {/if} + + + + + + + + + + +
+ {/if} +
+ + + {#if step.notes} +
+

{step.notes}

+
+ {/if} + + + {#if step.documents && step.documents.length > 0} +
+

Documentos

+
+ {#each step.documents as doc (doc._id)} +
+ + {doc.name} + + + +
+ {/each} +
+
+ {/if} + + + {#if step.startedAt || step.finishedAt} +
+ {#if step.startedAt} + Iniciado: {formatDate(step.startedAt)} + {/if} + {#if step.finishedAt} + Concluído: {formatDate(step.finishedAt)} + {/if} +
+ {/if} +
+
+ {/each} +
+
+ {/if} +
+ + +{#if showReassignModal && stepToReassign} + +{/if} + + +{#if showNotesModal && stepForNotes} + +{/if} + + +{#if showUploadModal && stepForUpload} + +{/if} + + +{#if showCancelModal} + +{/if} + diff --git a/apps/web/src/routes/(dashboard)/fluxos/[id]/editor/+page.svelte b/apps/web/src/routes/(dashboard)/fluxos/[id]/editor/+page.svelte new file mode 100644 index 0000000..fc1a89c --- /dev/null +++ b/apps/web/src/routes/(dashboard)/fluxos/[id]/editor/+page.svelte @@ -0,0 +1,800 @@ + + +
+ +
+
+
+ + + Voltar + +
+ {#if templateQuery.isLoading} +
+ {:else if templateQuery.data} +

{templateQuery.data.name}

+

+ {templateQuery.data.description ?? 'Sem descrição'} +

+ {/if} +
+
+ +
+ {#if templateQuery.data?.status === 'draft'} + + {:else if templateQuery.data?.status === 'published'} + Publicado + {:else if templateQuery.data?.status === 'archived'} + Arquivado + {/if} +
+
+
+ + +
+ +
+
+

Passos do Fluxo

+ +
+ + {#if stepsQuery.isLoading} +
+ +
+ {:else if !localSteps || localSteps.length === 0} +
+ +

Nenhum passo definido

+

Clique em "Novo Passo" para adicionar o primeiro passo

+
+ {:else if localSteps && localSteps.length > 0} +
+ {#each localSteps as step, index (step._id)} +
+
+
+
+ {index + 1} +
+
selectedStepId = step._id} + onkeydown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + selectedStepId = step._id; + } + }} + role="button" + tabindex="0" + > +

{step.name}

+ {#if step.description} +

{step.description}

+ {/if} +
+ + + {step.setorNome ?? 'Setor não definido'} + + + + {step.expectedDuration} dia{step.expectedDuration > 1 ? 's' : ''} + +
+
+
+ + +
+
+
+
+ {/each} +
+ {/if} +
+ + + +
+
+ + +{#if showNewStepModal} + +{/if} + + +{#if showSubEtapaModal} + +{/if} + diff --git a/apps/web/src/routes/(dashboard)/fluxos/instancias/+page.svelte b/apps/web/src/routes/(dashboard)/fluxos/instancias/+page.svelte new file mode 100644 index 0000000..96b96a7 --- /dev/null +++ b/apps/web/src/routes/(dashboard)/fluxos/instancias/+page.svelte @@ -0,0 +1,373 @@ + + +
+ +
+
+
+
+
+
+ + + Templates + + + Execução + +
+

+ Instâncias de Fluxo +

+

+ Acompanhe e gerencie as execuções de fluxos de trabalho. Visualize o progresso, + documentos e responsáveis de cada etapa. +

+
+
+ + + + + + +
+
+
+ + +
+ {#if instancesQuery.isLoading} +
+ +
+ {:else if !instancesQuery.data || instancesQuery.data.length === 0} +
+ +

Nenhuma instância encontrada

+

+ {statusFilter ? 'Não há instâncias com este status.' : 'Clique em "Nova Instância" para iniciar um fluxo.'} +

+
+ {:else} +
+ + + + + + + + + + + + + + {#each instancesQuery.data as instance (instance._id)} + {@const statusBadge = getStatusBadge(instance.status)} + {@const progressPercent = getProgressPercentage(instance.progress.completed, instance.progress.total)} + + + + + + + + + + {/each} + +
TemplateAlvoGerenteProgressoStatusIniciado emAções
+
{instance.templateName ?? 'Template desconhecido'}
+
+
+ {instance.targetType} + {instance.targetId} +
+
{instance.managerName ?? '-'} +
+ + + {instance.progress.completed}/{instance.progress.total} + +
+
+ {statusBadge.label} + {formatDate(instance.startedAt)} + + + Ver + +
+
+ {/if} +
+
+ + +{#if showCreateModal} + +{/if} + diff --git a/apps/web/src/routes/(dashboard)/licitacoes/+page.svelte b/apps/web/src/routes/(dashboard)/licitacoes/+page.svelte index dce4f1c..a318bc1 100644 --- a/apps/web/src/routes/(dashboard)/licitacoes/+page.svelte +++ b/apps/web/src/routes/(dashboard)/licitacoes/+page.svelte @@ -1,7 +1,8 @@ @@ -16,7 +17,16 @@ -
+ +
+

Licitações

+

+ Gerencie empresas, contratos e processos licitatórios +

+
+ + +
+ + +
+ +
diff --git a/apps/web/src/routes/(dashboard)/licitacoes/fluxos/+page.svelte b/apps/web/src/routes/(dashboard)/licitacoes/fluxos/+page.svelte new file mode 100644 index 0000000..a78a8bd --- /dev/null +++ b/apps/web/src/routes/(dashboard)/licitacoes/fluxos/+page.svelte @@ -0,0 +1,365 @@ + + +
+ +
+
+
+
+
+
+ + + Templates + + + Execução + +
+

+ Fluxos de Trabalho +

+

+ Acompanhe e gerencie os fluxos de trabalho. Visualize o progresso, + documentos e responsáveis de cada etapa. +

+
+
+ + + + + + +
+
+
+ + +
+ {#if instancesQuery.isLoading} +
+ +
+ {:else if !instancesQuery.data || instancesQuery.data.length === 0} +
+ +

Nenhum fluxo encontrado

+

+ {statusFilter ? 'Não há fluxos com este status.' : 'Clique em "Novo Fluxo" para iniciar um fluxo.'} +

+
+ {:else} +
+ + + + + + + + + + + + + + {#each instancesQuery.data as instance (instance._id)} + {@const statusBadge = getStatusBadge(instance.status)} + {@const progressPercent = getProgressPercentage(instance.progress.completed, instance.progress.total)} + + + + + + + + + + {/each} + +
TemplateContratoGerenteProgressoStatusIniciado emAções
+
{instance.templateName ?? 'Template desconhecido'}
+
+ {#if instance.contratoId} + {instance.contratoId} + {:else} + - + {/if} + {instance.managerName ?? '-'} +
+ + + {instance.progress.completed}/{instance.progress.total} + +
+
+ {statusBadge.label} + {formatDate(instance.startedAt)} + + + Ver + +
+
+ {/if} +
+
+ + +{#if showCreateModal} + +{/if} + diff --git a/apps/web/src/routes/(dashboard)/licitacoes/fluxos/[id]/+page.svelte b/apps/web/src/routes/(dashboard)/licitacoes/fluxos/[id]/+page.svelte new file mode 100644 index 0000000..f8fd33e --- /dev/null +++ b/apps/web/src/routes/(dashboard)/licitacoes/fluxos/[id]/+page.svelte @@ -0,0 +1,1251 @@ + + +
+ {#if instanceQuery.isLoading} +
+ +
+ {:else if !instanceQuery.data} +
+ +

Fluxo não encontrado

+ Voltar para lista +
+ {:else} + {@const instance = instanceQuery.data.instance} + {@const steps = instanceQuery.data.steps} + {@const statusBadge = getInstanceStatusBadge(instance.status)} + + +
+
+
+
+
+ + {statusBadge.label} +
+ +
+
+

+ {instance.templateName ?? 'Fluxo'} +

+
+ {#if instance.contratoId} +
+ Contrato + {instance.contratoId} +
+ {/if} +
+ +
+ Gerente: {instance.managerName ?? '-'} + +
+
+
+ + Iniciado: {formatDate(instance.startedAt)} +
+
+ +
+
+
+ + {#if instance.status === 'active'} + + + + {/if} +
+
+
+ + + {#if processingError} +
+ + {processingError} + +
+ {/if} + + +
+

Timeline do Fluxo

+ +
+ {#each steps as step, index (step._id)} + {@const stepStatus = getStatusBadge(step.status)} + {@const isCurrent = isStepCurrent(step._id)} + {@const overdue = step.status !== 'completed' && isOverdue(step.dueDate)} + {@const subEtapasQuery = useQuery(api.flows.listarSubEtapas, () => ({ flowInstanceStepId: step._id }))} + {@const subEtapas = subEtapasQuery.data} + {@const subEtapasCount = subEtapas?.length ?? 0} + {@const subEtapasCompleted = subEtapas?.filter((s: { status: string }) => s.status === 'completed').length ?? 0} + +
+ + {#if index < steps.length - 1} +
+ {/if} + + +
+ {#if step.status === 'completed'} + + {:else if step.status === 'blocked'} + + {:else} + {index + 1} + {/if} +
+ + +
+ +
+
+
+

{step.stepName}

+ {stepStatus.label} + +
+ {#if step.stepDescription} +

{step.stepDescription}

+ {/if} +
+ + + {step.setorNome ?? 'Setor não definido'} + + {#if step.assignedToName} + + + {step.assignedToName} + + {/if} + {#if step.dueDate} + + + Prazo: {formatDate(step.dueDate)} + + {/if} +
+
+ + + {#if instance.status === 'active'} +
+ {#if step.status === 'pending'} + + + + {:else if step.status === 'in_progress'} + + + + + {:else if step.status === 'blocked'} + + + + {/if} + + + + +
+ {/if} +
+ + +
+
+
+ +

Sub-etapas

+ {#if subEtapasCount > 0} + + {subEtapasCompleted} / {subEtapasCount} concluídas + + {/if} +
+ {#if instance.status === 'active'} + + {/if} +
+ + {#if subEtapasQuery.isLoading} +
+ +
+ {:else if subEtapas && subEtapas.length > 0} +
+ {#each subEtapas as subEtapa (subEtapa._id)} +
+
+
+
{subEtapa.name}
+ + {subEtapa.status === 'completed' ? 'Concluída' : subEtapa.status === 'in_progress' ? 'Em Andamento' : subEtapa.status === 'blocked' ? 'Bloqueada' : 'Pendente'} + +
+ {#if subEtapa.description} +
{subEtapa.description}
+ {/if} +
+
+ {#if instance.status === 'active'} + + + {/if} +
+
+ {/each} +
+ {:else} +
+ Nenhuma sub-etapa adicionada +
+ {/if} +
+ + +
+
+
+ +

Documentos

+ {#if step.documents && step.documents.length > 0} + {step.documents.length} + {/if} +
+ {#if instance.status === 'active'} + + + + {/if} +
+ + {#if step.documents && step.documents.length > 0} +
+ {#each step.documents as doc (doc._id)} +
+ +
+
{doc.name}
+
+ {#if instance.status === 'active'} + + + + {/if} +
+ {/each} +
+ {:else} +
+ Nenhum documento anexado +
+ {/if} +
+ + +
+
+
+ +

Notas e Comentários

+ {#if step.notes} + Atualizado + {/if} +
+ +
+ + {#if step.notes} +
+

{step.notes}

+ {#if step.notesUpdatedByName || step.notesUpdatedAt} +
+ + {#if step.notesUpdatedByName} + Atualizado por {step.notesUpdatedByName} + {/if} + {#if step.notesUpdatedAt} + + {formatDate(step.notesUpdatedAt)} + {/if} +
+ {/if} +
+ {:else} +
+ Nenhuma nota ou comentário adicionado +
+ {/if} +
+ + + {#if step.startedAt || step.finishedAt} +
+ {#if step.startedAt} + + + Iniciado: {formatDate(step.startedAt)} + + {/if} + {#if step.finishedAt} + + + Concluído: {formatDate(step.finishedAt)} + + {/if} +
+ {/if} +
+
+ {/each} +
+
+ {/if} + + + {#if showAlterarGestorModal} + + {/if} + + + {#if showSubEtapaModal && stepIdParaSubEtapas} + + {/if} + + + {#if toastMessage} +
+
+ {toastMessage} + +
+
+ {/if} +
+ + +{#if showReassignModal && stepToReassign} + +{/if} + + +{#if showNotesModal && stepForNotes} + +{/if} + + +{#if showUploadModal && stepForUpload} + +{/if} + + +{#if showCancelModal} + +{/if} + diff --git a/apps/web/src/routes/(dashboard)/programas-esportivos/+page.svelte b/apps/web/src/routes/(dashboard)/programas-esportivos/+page.svelte index 3da1d68..d294ee3 100644 --- a/apps/web/src/routes/(dashboard)/programas-esportivos/+page.svelte +++ b/apps/web/src/routes/(dashboard)/programas-esportivos/+page.svelte @@ -1,5 +1,5 @@ @@ -56,6 +56,23 @@

+ + +
+
+
+ +
+

Fluxos de Trabalho

+
+

+ Gerencie templates e instâncias de fluxos de trabalho para programas e projetos esportivos. +

+
+
diff --git a/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/+page.svelte b/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/+page.svelte index 515fd6a..ca05422 100644 --- a/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/+page.svelte +++ b/apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/+page.svelte @@ -1,23 +1,42 @@
-