Merge pull request #15 from killer-cf/fix-page-with-lint-errors
Fix page with lint errors
This commit is contained in:
19
.cursor/mcp.json
Normal file
19
.cursor/mcp.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"svelte": {
|
||||||
|
"url": "https://mcp.svelte.dev/mcp"
|
||||||
|
},
|
||||||
|
"context7": {
|
||||||
|
"url": "https://mcp.context7.com/mcp"
|
||||||
|
},
|
||||||
|
"convex": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": [
|
||||||
|
"-y",
|
||||||
|
"convex@latest",
|
||||||
|
"mcp",
|
||||||
|
"start"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
.cursor/rules/svelte_rules.mdc
Normal file
27
.cursor/rules/svelte_rules.mdc
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
alwaysApply: true
|
||||||
|
---
|
||||||
|
|
||||||
|
You are able to use the Svelte MCP server, where you have access to comprehensive Svelte 5 and SvelteKit documentation. Here's how to use the available tools effectively:
|
||||||
|
|
||||||
|
## Available MCP Tools:
|
||||||
|
|
||||||
|
### 1. list-sections
|
||||||
|
|
||||||
|
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths.
|
||||||
|
When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
|
||||||
|
|
||||||
|
### 2. get-documentation
|
||||||
|
|
||||||
|
Retrieves full documentation content for specific sections. Accepts single or multiple sections.
|
||||||
|
After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
|
||||||
|
|
||||||
|
### 3. svelte-autofixer
|
||||||
|
|
||||||
|
Analyzes Svelte code and returns issues and suggestions.
|
||||||
|
You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
|
||||||
|
|
||||||
|
### 4. playground-link
|
||||||
|
|
||||||
|
Generates a Svelte Playground link with the provided code.
|
||||||
|
After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: Guidelines for TypeScript usage, including type safety rules and Convex query typing
|
description: Guidelines for TypeScript usage, including type safety rules and Convex query typing
|
||||||
globs: **/*.ts,**/*.tsx,**/*.svelte
|
globs: **/*.ts,**/*.svelte
|
||||||
|
alwaysApply: false
|
||||||
---
|
---
|
||||||
|
|
||||||
# TypeScript Guidelines
|
# TypeScript Guidelines
|
||||||
@@ -8,6 +9,7 @@ globs: **/*.ts,**/*.tsx,**/*.svelte
|
|||||||
## Type Safety Rules
|
## Type Safety Rules
|
||||||
|
|
||||||
### Avoid `any` Type
|
### Avoid `any` Type
|
||||||
|
|
||||||
- **NEVER** use the `any` type in production code
|
- **NEVER** use the `any` type in production code
|
||||||
- The only exception is in test files (files matching `*.test.ts`, `*.test.tsx`, `*.spec.ts`, `*.spec.tsx`)
|
- The only exception is in test files (files matching `*.test.ts`, `*.test.tsx`, `*.spec.ts`, `*.spec.tsx`)
|
||||||
- Instead of `any`, use:
|
- Instead of `any`, use:
|
||||||
@@ -20,44 +22,48 @@ globs: **/*.ts,**/*.tsx,**/*.svelte
|
|||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
**❌ Bad:**
|
**❌ Bad:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function processData(data: any) {
|
function processData(data: any) {
|
||||||
return data.value;
|
return data.value;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**✅ Good:**
|
**✅ Good:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function processData(data: { value: string }) {
|
function processData(data: { value: string }) {
|
||||||
return data.value;
|
return data.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Or with generics
|
// Or with generics
|
||||||
function processData<T extends { value: unknown }>(data: T) {
|
function processData<T extends { value: unknown }>(data: T) {
|
||||||
return data.value;
|
return data.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Or with unknown and type guards
|
// Or with unknown and type guards
|
||||||
function processData(data: unknown) {
|
function processData(data: unknown) {
|
||||||
if (typeof data === 'object' && data !== null && 'value' in data) {
|
if (typeof data === 'object' && data !== null && 'value' in data) {
|
||||||
return (data as { value: string }).value;
|
return (data as { value: string }).value;
|
||||||
}
|
}
|
||||||
throw new Error('Invalid data');
|
throw new Error('Invalid data');
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**✅ Exception (tests only):**
|
**✅ Exception (tests only):**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// test.ts or *.spec.ts
|
// test.ts or *.spec.ts
|
||||||
it('should handle any input', () => {
|
it('should handle any input', () => {
|
||||||
const input: any = getMockData();
|
const input: any = getMockData();
|
||||||
expect(process(input)).toBeDefined();
|
expect(process(input)).toBeDefined();
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## Convex Query Typing
|
## Convex Query Typing
|
||||||
|
|
||||||
### Frontend Query Usage
|
### Frontend Query Usage
|
||||||
|
|
||||||
- **DO NOT** create manual type definitions for Convex query results in the frontend
|
- **DO NOT** create manual type definitions for Convex query results in the frontend
|
||||||
- Convex queries already return properly typed results based on their `returns` validator
|
- Convex queries already return properly typed results based on their `returns` validator
|
||||||
- The TypeScript types are automatically inferred from the query's return validator
|
- The TypeScript types are automatically inferred from the query's return validator
|
||||||
@@ -66,17 +72,19 @@ it('should handle any input', () => {
|
|||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
**❌ Bad:**
|
**❌ Bad:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Don't manually type the result
|
// Don't manually type the result
|
||||||
type UserListResult = Array<{
|
type UserListResult = Array<{
|
||||||
_id: Id<"users">;
|
_id: Id<'users'>;
|
||||||
name: string;
|
name: string;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const users: UserListResult = useQuery(api.users.list);
|
const users: UserListResult = useQuery(api.users.list);
|
||||||
```
|
```
|
||||||
|
|
||||||
**✅ Good:**
|
**✅ Good:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Let TypeScript infer the type from the query
|
// Let TypeScript infer the type from the query
|
||||||
const users = useQuery(api.users.list);
|
const users = useQuery(api.users.list);
|
||||||
@@ -84,24 +92,26 @@ const users = useQuery(api.users.list);
|
|||||||
|
|
||||||
// You can still use it with type inference
|
// You can still use it with type inference
|
||||||
if (users !== undefined) {
|
if (users !== undefined) {
|
||||||
users.forEach(user => {
|
users.forEach((user) => {
|
||||||
// TypeScript knows user._id is Id<"users"> and user.name is string
|
// TypeScript knows user._id is Id<"users"> and user.name is string
|
||||||
console.log(user.name);
|
console.log(user.name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**✅ Good (with explicit type if needed for clarity):**
|
**✅ Good (with explicit type if needed for clarity):**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Only if you need to export or explicitly annotate for documentation
|
// Only if you need to export or explicitly annotate for documentation
|
||||||
import type { FunctionReturnType } from "convex/server";
|
import type { FunctionReturnType } from 'convex/server';
|
||||||
import type { api } from "./convex/_generated/api";
|
import type { api } from './convex/_generated/api';
|
||||||
|
|
||||||
type UserListResult = FunctionReturnType<typeof api.users.list>;
|
type UserListResult = FunctionReturnType<typeof api.users.list>;
|
||||||
const users = useQuery(api.users.list);
|
const users = useQuery(api.users.list);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Best Practices
|
### Best Practices
|
||||||
|
|
||||||
- Trust Convex's type inference - it's based on your schema and validators
|
- Trust Convex's type inference - it's based on your schema and validators
|
||||||
- If you need type annotations, use `FunctionReturnType` from Convex's type utilities
|
- If you need type annotations, use `FunctionReturnType` from Convex's type utilities
|
||||||
- Only create manual types if you're doing complex transformations that need intermediate types
|
- Only create manual types if you're doing complex transformations that need intermediate types
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1
bun.lock
1
bun.lock
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 0,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"": {
|
"": {
|
||||||
"name": "sgse-app",
|
"name": "sgse-app",
|
||||||
|
|||||||
90
package.json
90
package.json
@@ -1,47 +1,47 @@
|
|||||||
{
|
{
|
||||||
"name": "sgse-app",
|
"name": "sgse-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"convex": "^1.28.0",
|
"convex": "^1.28.0",
|
||||||
"typescript": "^5.9.2",
|
"typescript": "^5.9.2",
|
||||||
"better-auth": "1.3.27",
|
"better-auth": "1.3.27",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"@eslint/js": "^9.39.1"
|
"@eslint/js": "^9.39.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"check": "biome check --write .",
|
"check": "biome check --write .",
|
||||||
"dev": "turbo dev",
|
"dev": "turbo dev",
|
||||||
"build": "turbo build",
|
"build": "turbo build",
|
||||||
"check-types": "turbo check-types",
|
"check-types": "turbo check-types",
|
||||||
"dev:native": "turbo -F native dev",
|
"dev:native": "turbo -F native dev",
|
||||||
"dev:web": "turbo -F web dev",
|
"dev:web": "turbo -F web dev",
|
||||||
"dev:server": "turbo -F @sgse-app/backend dev",
|
"dev:server": "turbo -F @sgse-app/backend dev",
|
||||||
"dev:setup": "turbo -F @sgse-app/backend dev:setup"
|
"dev:setup": "turbo -F @sgse-app/backend dev:setup"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"globals": "^16.5.0",
|
"eslint-plugin-svelte": "^3.13.0",
|
||||||
"jiti": "^2.6.1",
|
"globals": "^16.5.0",
|
||||||
"turbo": "^2.5.8",
|
"jiti": "^2.6.1",
|
||||||
"typescript-eslint": "^8.46.3",
|
"prettier": "^3.6.2",
|
||||||
"prettier": "^3.6.2",
|
"prettier-plugin-tailwindcss": "^0.7.1",
|
||||||
"eslint-plugin-svelte": "^3.13.0",
|
"turbo": "^2.5.8",
|
||||||
"prettier-plugin-tailwindcss": "^0.7.1"
|
"typescript-eslint": "^8.46.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/svelte-form": "^1.23.8",
|
"@tanstack/svelte-form": "^1.23.8",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"lucide-svelte": "^0.552.0",
|
"lucide-svelte": "^0.552.0",
|
||||||
"prettier-plugin-svelte": "^3.4.0",
|
"prettier-plugin-svelte": "^3.4.0",
|
||||||
"svelte-chartjs": "^3.1.5",
|
"svelte-chartjs": "^3.1.5",
|
||||||
"svelte-sonner": "^1.0.5"
|
"svelte-sonner": "^1.0.5"
|
||||||
},
|
},
|
||||||
"packageManager": "bun@1.3.0"
|
"packageManager": "bun@1.3.0"
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,16 @@ import { getCurrentUserFunction } from './auth';
|
|||||||
export const CATALOGO_RECURSOS = [
|
export const CATALOGO_RECURSOS = [
|
||||||
{
|
{
|
||||||
recurso: 'funcionarios',
|
recurso: 'funcionarios',
|
||||||
acoes: ['dashboard', 'ver', 'listar', 'criar', 'editar', 'excluir']
|
acoes: [
|
||||||
|
'dashboard',
|
||||||
|
'ver',
|
||||||
|
'listar',
|
||||||
|
'criar',
|
||||||
|
'editar',
|
||||||
|
'excluir',
|
||||||
|
'aprovar_ausencias',
|
||||||
|
'aprovar_ferias'
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
recurso: 'simbolos',
|
recurso: 'simbolos',
|
||||||
|
|||||||
@@ -1,35 +1,125 @@
|
|||||||
import { v } from "convex/values";
|
import { v } from 'convex/values';
|
||||||
import { query } from "./_generated/server";
|
import { query, mutation } from './_generated/server';
|
||||||
|
import type { Id } from './_generated/dataModel';
|
||||||
|
import { getCurrentUserFunction } from './auth';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listar todas as roles
|
* Listar todas as roles
|
||||||
*/
|
*/
|
||||||
export const listar = query({
|
export const listar = query({
|
||||||
args: {},
|
args: {},
|
||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
return await ctx.db.query("roles").collect();
|
return await ctx.db.query('roles').collect();
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Buscar role por ID
|
* Buscar role por ID
|
||||||
*/
|
*/
|
||||||
export const buscarPorId = query({
|
export const buscarPorId = query({
|
||||||
args: {
|
args: {
|
||||||
roleId: v.id("roles"),
|
roleId: v.id('roles')
|
||||||
},
|
},
|
||||||
returns: v.union(
|
returns: v.union(
|
||||||
v.object({
|
v.object({
|
||||||
_id: v.id("roles"),
|
_id: v.id('roles'),
|
||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
descricao: v.string(),
|
descricao: v.string(),
|
||||||
nivel: v.number(),
|
nivel: v.number(),
|
||||||
setor: v.optional(v.string()),
|
setor: v.optional(v.string())
|
||||||
}),
|
}),
|
||||||
v.null()
|
v.null()
|
||||||
),
|
),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
return await ctx.db.get(args.roleId);
|
return await ctx.db.get(args.roleId);
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const slugify = (value: string) =>
|
||||||
|
value
|
||||||
|
.normalize('NFD')
|
||||||
|
.replace(/[\u0300-\u036f]/g, '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9]+/g, '_')
|
||||||
|
.replace(/^_+|_+$/g, '')
|
||||||
|
.replace(/_{2,}/g, '_');
|
||||||
|
|
||||||
|
export const criar = mutation({
|
||||||
|
args: {
|
||||||
|
nome: v.string(),
|
||||||
|
descricao: v.string(),
|
||||||
|
nivel: v.number(),
|
||||||
|
setor: v.optional(v.string()),
|
||||||
|
copiarDeRoleId: v.optional(v.id('roles'))
|
||||||
|
},
|
||||||
|
returns: v.union(
|
||||||
|
v.object({ sucesso: v.literal(true), roleId: v.id('roles') }),
|
||||||
|
v.object({ sucesso: v.literal(false), erro: v.string() })
|
||||||
|
),
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
const usuarioAtual = await getCurrentUserFunction(ctx);
|
||||||
|
if (!usuarioAtual) {
|
||||||
|
return { sucesso: false as const, erro: 'nao_autenticado' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleAtual = await ctx.db.get(usuarioAtual.roleId);
|
||||||
|
if (!roleAtual || roleAtual.nivel > 1) {
|
||||||
|
return { sucesso: false as const, erro: 'sem_permissao' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const nomeNormalizado = slugify(args.nome);
|
||||||
|
if (!nomeNormalizado) {
|
||||||
|
return { sucesso: false as const, erro: 'nome_invalido' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const existente = await ctx.db
|
||||||
|
.query('roles')
|
||||||
|
.withIndex('by_nome', (q) => q.eq('nome', nomeNormalizado))
|
||||||
|
.unique();
|
||||||
|
|
||||||
|
if (existente) {
|
||||||
|
return { sucesso: false as const, erro: 'nome_ja_utilizado' };
|
||||||
|
}
|
||||||
|
|
||||||
|
let permissoesParaCopiar: Array<Id<'permissoes'>> = [];
|
||||||
|
|
||||||
|
if (args.copiarDeRoleId) {
|
||||||
|
const roleOrigem = await ctx.db.get(args.copiarDeRoleId);
|
||||||
|
if (!roleOrigem) {
|
||||||
|
return { sucesso: false as const, erro: 'role_origem_nao_encontrada' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissoesOrigem = await ctx.db
|
||||||
|
.query('rolePermissoes')
|
||||||
|
.withIndex('by_role', (q) => q.eq('roleId', args.copiarDeRoleId!))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
permissoesParaCopiar = permissoesOrigem.map((item) => item.permissaoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const nivelAjustado = Math.min(Math.max(Math.round(args.nivel), 0), 10);
|
||||||
|
const setor = args.setor?.trim();
|
||||||
|
|
||||||
|
const roleId = await ctx.db.insert('roles', {
|
||||||
|
nome: nomeNormalizado,
|
||||||
|
descricao: args.descricao.trim() || args.nome.trim(),
|
||||||
|
nivel: nivelAjustado,
|
||||||
|
setor: setor && setor.length > 0 ? setor : undefined,
|
||||||
|
customizado: true,
|
||||||
|
criadoPor: usuarioAtual._id,
|
||||||
|
editavel: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (permissoesParaCopiar.length > 0) {
|
||||||
|
for (const permissaoId of permissoesParaCopiar) {
|
||||||
|
await ctx.db.insert('rolePermissoes', {
|
||||||
|
roleId,
|
||||||
|
permissaoId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { sucesso: true as const, roleId };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -562,15 +562,12 @@ export const obterPerfil = query({
|
|||||||
),
|
),
|
||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
const usuarioAutenticado = await getCurrentUserFunction(ctx);
|
const usuarioAutenticado = await getCurrentUserFunction(ctx);
|
||||||
console.log('Usuario autenticado:', usuarioAutenticado);
|
|
||||||
if (!usuarioAutenticado) {
|
if (!usuarioAutenticado) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const usuarioAtual = usuarioAutenticado;
|
const usuarioAtual = usuarioAutenticado;
|
||||||
|
|
||||||
console.log('✅ Usuário encontrado:', usuarioAtual.nome);
|
|
||||||
|
|
||||||
// Buscar fotoPerfil URL se existir
|
// Buscar fotoPerfil URL se existir
|
||||||
let fotoPerfilUrl = null;
|
let fotoPerfilUrl = null;
|
||||||
if (usuarioAtual.fotoPerfil) {
|
if (usuarioAtual.fotoPerfil) {
|
||||||
|
|||||||
Reference in New Issue
Block a user