From ed2a1da038d57c157bb0c3acc64577427509c2b0 Mon Sep 17 00:00:00 2001 From: Sergio Ramirez Date: Fri, 20 Jun 2025 14:43:35 -0400 Subject: [PATCH] Tabla de inventario agregada --- apps/api/src/app.module.ts | 5 +- .../0002_polite_franklin_richards.sql | 13 + .../migrations/meta/0002_snapshot.json | 1441 +++++++++++++++++ .../database/migrations/meta/_journal.json | 7 + .../features/inventory/inventory.service.ts | 15 +- apps/web/app/dashboard/inventario/page.tsx | 37 + apps/web/components/icons.tsx | 2 + apps/web/constants/data.ts | 9 +- .../feactures/inventory/actions/actions.ts | 153 ++ .../components/inventory/create-user-form.tsx | 222 +++ .../inventory/product-inventory-list.tsx | 47 + .../inventory/product-tables/cell-action.tsx | 90 + .../inventory/product-tables/columns.tsx | 41 + .../use-survey-table-filters.tsx | 59 + .../product-tables/users-table-action.tsx | 36 + .../components/inventory/update-user-form.tsx | 227 +++ .../components/inventory/user-modal.tsx | 69 + .../components/inventory/users-header.tsx | 27 + .../inventory/components/modal-profile.tsx | 57 + .../inventory/components/selectList.tsx | 85 + .../feactures/inventory/components/survey.tsx | 28 + .../inventory/components/update-user-form.tsx | 268 +++ .../inventory/components/user-profile.tsx | 75 + .../inventory/hooks/use-mutation-users.ts | 45 + .../inventory/hooks/use-query-surveys.ts | 12 + .../inventory/hooks/use-query-users.ts | 8 + .../inventory/schemas/account-plan-options.ts | 19 + .../inventory/schemas/account-plan.schema.ts | 83 + .../feactures/inventory/schemas/inventory.ts | 69 + .../inventory/schemas/surveys-options.ts | 6 + .../feactures/inventory/utils/date-utils.ts | 11 + .../feactures/inventory/utils/searchparams.ts | 16 + apps/web/public/apple.avif | Bin 0 -> 8030 bytes 33 files changed, 3272 insertions(+), 10 deletions(-) create mode 100644 apps/api/src/database/migrations/0002_polite_franklin_richards.sql create mode 100644 apps/api/src/database/migrations/meta/0002_snapshot.json create mode 100644 apps/web/app/dashboard/inventario/page.tsx create mode 100644 apps/web/feactures/inventory/actions/actions.ts create mode 100644 apps/web/feactures/inventory/components/inventory/create-user-form.tsx create mode 100644 apps/web/feactures/inventory/components/inventory/product-inventory-list.tsx create mode 100644 apps/web/feactures/inventory/components/inventory/product-tables/cell-action.tsx create mode 100644 apps/web/feactures/inventory/components/inventory/product-tables/columns.tsx create mode 100644 apps/web/feactures/inventory/components/inventory/product-tables/use-survey-table-filters.tsx create mode 100644 apps/web/feactures/inventory/components/inventory/product-tables/users-table-action.tsx create mode 100644 apps/web/feactures/inventory/components/inventory/update-user-form.tsx create mode 100644 apps/web/feactures/inventory/components/inventory/user-modal.tsx create mode 100644 apps/web/feactures/inventory/components/inventory/users-header.tsx create mode 100644 apps/web/feactures/inventory/components/modal-profile.tsx create mode 100644 apps/web/feactures/inventory/components/selectList.tsx create mode 100644 apps/web/feactures/inventory/components/survey.tsx create mode 100644 apps/web/feactures/inventory/components/update-user-form.tsx create mode 100644 apps/web/feactures/inventory/components/user-profile.tsx create mode 100644 apps/web/feactures/inventory/hooks/use-mutation-users.ts create mode 100644 apps/web/feactures/inventory/hooks/use-query-surveys.ts create mode 100644 apps/web/feactures/inventory/hooks/use-query-users.ts create mode 100644 apps/web/feactures/inventory/schemas/account-plan-options.ts create mode 100644 apps/web/feactures/inventory/schemas/account-plan.schema.ts create mode 100644 apps/web/feactures/inventory/schemas/inventory.ts create mode 100644 apps/web/feactures/inventory/schemas/surveys-options.ts create mode 100644 apps/web/feactures/inventory/utils/date-utils.ts create mode 100644 apps/web/feactures/inventory/utils/searchparams.ts create mode 100644 apps/web/public/apple.avif diff --git a/apps/api/src/app.module.ts b/apps/api/src/app.module.ts index ac53a17..d9c59be 100644 --- a/apps/api/src/app.module.ts +++ b/apps/api/src/app.module.ts @@ -18,7 +18,7 @@ import { MailModule } from './features/mail/mail.module'; import { RolesModule } from './features/roles/roles.module'; import { UserRolesModule } from './features/user-roles/user-roles.module'; import { SurveysModule } from './features/surveys/surveys.module'; - +import {InventoryModule} from './features/inventory/inventory.module' @Module({ providers: [ @@ -58,7 +58,8 @@ import { SurveysModule } from './features/surveys/surveys.module'; UserRolesModule, ConfigurationsModule, SurveysModule, - LocationModule + LocationModule, + InventoryModule ], }) export class AppModule {} diff --git a/apps/api/src/database/migrations/0002_polite_franklin_richards.sql b/apps/api/src/database/migrations/0002_polite_franklin_richards.sql new file mode 100644 index 0000000..a4b57f2 --- /dev/null +++ b/apps/api/src/database/migrations/0002_polite_franklin_richards.sql @@ -0,0 +1,13 @@ +CREATE TABLE "products" ( + "id" serial PRIMARY KEY NOT NULL, + "title" text NOT NULL, + "description" text NOT NULL, + "price" numeric NOT NULL, + "stock" integer NOT NULL, + "url_img" text NOT NULL, + "user_id" integer, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp (3) +); +--> statement-breakpoint +ALTER TABLE "products" ADD CONSTRAINT "products_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/apps/api/src/database/migrations/meta/0002_snapshot.json b/apps/api/src/database/migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..8424e82 --- /dev/null +++ b/apps/api/src/database/migrations/meta/0002_snapshot.json @@ -0,0 +1,1441 @@ +{ + "id": "e3f08a0a-764c-4a4d-8473-c666398b8722", + "prevId": "cdbb3495-688f-4b2d-ab8e-e62e42328fd5", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.activity_logs": { + "name": "activity_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "activityLogs_idx": { + "name": "activityLogs_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "activity_logs_user_id_users_id_fk": { + "name": "activity_logs_user_id_users_id_fk", + "tableFrom": "activity_logs", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.roles": { + "name": "roles", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "roles_idx": { + "name": "roles_idx", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.sessions": { + "name": "sessions", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sessions_idx": { + "name": "sessions_idx", + "columns": [ + { + "expression": "session_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.users": { + "name": "users", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fullname": { + "name": "fullname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "municipality": { + "name": "municipality", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parish": { + "name": "parish", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_two_factor_enabled": { + "name": "is_two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "two_factor_secret": { + "name": "two_factor_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_email_verified": { + "name": "is_email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "users_idx": { + "name": "users_idx", + "columns": [ + { + "expression": "username", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users_state_states_id_fk": { + "name": "users_state_states_id_fk", + "tableFrom": "users", + "tableTo": "states", + "columnsFrom": [ + "state" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "users_municipality_municipalities_id_fk": { + "name": "users_municipality_municipalities_id_fk", + "tableFrom": "users", + "tableTo": "municipalities", + "columnsFrom": [ + "municipality" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "users_parish_parishes_id_fk": { + "name": "users_parish_parishes_id_fk", + "tableFrom": "users", + "tableTo": "parishes", + "columnsFrom": [ + "parish" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_username_unique": { + "name": "users_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.user_role": { + "name": "user_role", + "schema": "auth", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_role_idx": { + "name": "user_role_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_role_user_id_users_id_fk": { + "name": "user_role_user_id_users_id_fk", + "tableFrom": "user_role", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_role_role_id_roles_id_fk": { + "name": "user_role_role_id_roles_id_fk", + "tableFrom": "user_role", + "tableTo": "roles", + "schemaTo": "auth", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "auth.verificationToken": { + "name": "verificationToken", + "schema": "auth", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.category_type": { + "name": "category_type", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "group": { + "name": "group", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "category_typeIx0": { + "name": "category_typeIx0", + "columns": [ + { + "expression": "group", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "category_typeIx1": { + "name": "category_typeIx1", + "columns": [ + { + "expression": "description", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.localities": { + "name": "localities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "state_id": { + "name": "state_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "municipality_id": { + "name": "municipality_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "parish_id": { + "name": "parish_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "localities_index_03": { + "name": "localities_index_03", + "columns": [ + { + "expression": "state_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "municipality_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parish_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "localities_index_00": { + "name": "localities_index_00", + "columns": [ + { + "expression": "state_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "localities_index_01": { + "name": "localities_index_01", + "columns": [ + { + "expression": "municipality_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "localities_index_02": { + "name": "localities_index_02", + "columns": [ + { + "expression": "parish_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "localities_state_id_states_id_fk": { + "name": "localities_state_id_states_id_fk", + "tableFrom": "localities", + "tableTo": "states", + "columnsFrom": [ + "state_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "localities_municipality_id_municipalities_id_fk": { + "name": "localities_municipality_id_municipalities_id_fk", + "tableFrom": "localities", + "tableTo": "municipalities", + "columnsFrom": [ + "municipality_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "localities_parish_id_parishes_id_fk": { + "name": "localities_parish_id_parishes_id_fk", + "tableFrom": "localities", + "tableTo": "parishes", + "columnsFrom": [ + "parish_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "localities_name_unique": { + "name": "localities_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.municipalities": { + "name": "municipalities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_id": { + "name": "state_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "municipalities_index_00": { + "name": "municipalities_index_00", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "municipalities_state_id_states_id_fk": { + "name": "municipalities_state_id_states_id_fk", + "tableFrom": "municipalities", + "tableTo": "states", + "columnsFrom": [ + "state_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.parishes": { + "name": "parishes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "municipality_id": { + "name": "municipality_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "parishes_index_00": { + "name": "parishes_index_00", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "municipality_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "parishes_municipality_id_municipalities_id_fk": { + "name": "parishes_municipality_id_municipalities_id_fk", + "tableFrom": "parishes", + "tableTo": "municipalities", + "columnsFrom": [ + "municipality_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.states": { + "name": "states", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "states_index_00": { + "name": "states_index_00", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.products": { + "name": "products", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "price": { + "name": "price", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "stock": { + "name": "stock", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url_img": { + "name": "url_img", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "products_user_id_users_id_fk": { + "name": "products_user_id_users_id_fk", + "tableFrom": "products", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.answers_surveys": { + "name": "answers_surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "survey_id": { + "name": "survey_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "answers": { + "name": "answers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "answers_index_00": { + "name": "answers_index_00", + "columns": [ + { + "expression": "answers", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "answers_index_01": { + "name": "answers_index_01", + "columns": [ + { + "expression": "survey_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "answers_index_02": { + "name": "answers_index_02", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "answers_surveys_survey_id_surveys_id_fk": { + "name": "answers_surveys_survey_id_surveys_id_fk", + "tableFrom": "answers_surveys", + "tableTo": "surveys", + "columnsFrom": [ + "survey_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "answers_surveys_user_id_users_id_fk": { + "name": "answers_surveys_user_id_users_id_fk", + "tableFrom": "answers_surveys", + "tableTo": "users", + "schemaTo": "auth", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.surveys": { + "name": "surveys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_audience": { + "name": "target_audience", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "closing_date": { + "name": "closing_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "published": { + "name": "published", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "questions": { + "name": "questions", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "surveys_index_00": { + "name": "surveys_index_00", + "columns": [ + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "auth.gender": { + "name": "gender", + "schema": "auth", + "values": [ + "FEMENINO", + "MASCULINO" + ] + }, + "public.nationality": { + "name": "nationality", + "schema": "public", + "values": [ + "VENEZOLANO", + "EXTRANJERO" + ] + }, + "auth.status": { + "name": "status", + "schema": "auth", + "values": [ + "ACTIVE", + "INACTIVE" + ] + } + }, + "schemas": { + "auth": "auth" + }, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "auth.user_access_view": { + "columns": { + "userId": { + "name": "userId", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "role_name": { + "name": "role_name", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n u.id AS user_id,\n u.username,\n u.email,\n u.fullname,\n r.id AS role_id,\n r.name AS role_name\nFROM\n auth.users u\nLEFT JOIN\n auth.user_role ur ON u.id = ur.user_id \nLEFT JOIN\n auth.roles r ON ur.role_id = r.id", + "name": "user_access_view", + "schema": "auth", + "isExisting": false, + "materialized": false + }, + "public.v_surveys": { + "columns": { + "survey_id": { + "name": "survey_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "closing_date": { + "name": "closing_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "target_audience": { + "name": "target_audience", + "type": "varchar", + "primaryKey": false, + "notNull": false + } + }, + "definition": "select id as survey_id, title, description, created_at, closing_date, target_audience from surveys\nwhere published = true", + "name": "v_surveys", + "schema": "public", + "isExisting": false, + "materialized": false + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/api/src/database/migrations/meta/_journal.json b/apps/api/src/database/migrations/meta/_journal.json index 0f874c3..2ef8b9a 100644 --- a/apps/api/src/database/migrations/meta/_journal.json +++ b/apps/api/src/database/migrations/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1747665408016, "tag": "0001_massive_kylun", "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1750442271575, + "tag": "0002_polite_franklin_richards", + "breakpoints": true } ] } \ No newline at end of file diff --git a/apps/api/src/features/inventory/inventory.service.ts b/apps/api/src/features/inventory/inventory.service.ts index ff1f222..115fcdc 100644 --- a/apps/api/src/features/inventory/inventory.service.ts +++ b/apps/api/src/features/inventory/inventory.service.ts @@ -1,13 +1,11 @@ import { DRIZZLE_PROVIDER } from 'src/database/drizzle-provider'; -import { Env, validateString } from '@/common/utils'; import { Inject, Injectable, HttpException, HttpStatus, NotFoundException, UnauthorizedException } from '@nestjs/common'; import { NodePgDatabase } from 'drizzle-orm/node-postgres'; import * as schema from 'src/database/index'; import { products } from 'src/database/index'; import { eq, like, or, SQL, sql, and, not } from 'drizzle-orm'; -import * as bcrypt from 'bcryptjs'; import { CreateProductDto } from './dto/create-product.dto'; -import { UpdateUserDto } from './dto/update-product.dto'; +import { UpdateProductDto } from './dto/update-product.dto'; import { Product } from './entities/inventory.entity'; import { PaginationDto } from '../../common/dto/pagination.dto'; @@ -53,8 +51,9 @@ export class InventoryService { id: products.id, title: products.title, description: products.description, - valuePerUnit: products.valuePerUnit, + price: products.price, urlImg: products.urlImg, + stock: products.stock, // price: products.price, // quantity: products.quantity, // isActive: products.isActive @@ -79,7 +78,7 @@ export class InventoryService { previousPage: page > 1 ? page - 1 : null, }; - // console.log(data); + console.log(data); return { data, meta }; } @@ -90,8 +89,9 @@ export class InventoryService { id: products.id, title: products.title, description: products.description, - valuePerUnit: products.valuePerUnit, + price: products.price, urlImg: products.urlImg, + stock: products.stock }) .from(products) // .leftJoin(usersRole, eq(usersRole.userId, users.id)) @@ -124,8 +124,9 @@ export class InventoryService { .values({ title: createProductDto.title, description: createProductDto.description, - valuePerUnit: createProductDto.valuePerUnit, + price: createProductDto.price, urlImg: createProductDto.urlImg, + stock: createProductDto.stock }) .returning(); diff --git a/apps/web/app/dashboard/inventario/page.tsx b/apps/web/app/dashboard/inventario/page.tsx new file mode 100644 index 0000000..2534d48 --- /dev/null +++ b/apps/web/app/dashboard/inventario/page.tsx @@ -0,0 +1,37 @@ +import PageContainer from '@/components/layout/page-container'; +import UsersAdminList from '@/feactures/inventory/components/inventory/product-inventory-list'; +import { UsersHeader } from '@/feactures/inventory/components/inventory/users-header'; +import UsersTableAction from '@/feactures/inventory/components/inventory/product-tables/users-table-action'; +import { searchParamsCache, serialize } from '@/feactures/inventory/utils/searchparams'; +import { SearchParams } from 'nuqs'; + +type pageProps = { + searchParams: Promise; +}; + + +export default async function SurveyAdminPage(props: pageProps) { + const searchParams = await props.searchParams; + searchParamsCache.parse(searchParams); + const key = serialize({ ...searchParams }); + + const page = Number(searchParamsCache.get('page')) || 1; + const search = searchParamsCache.get('q'); + const pageLimit = Number(searchParamsCache.get('limit')) || 10; + const type = searchParamsCache.get('type'); + + return ( + +
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/apps/web/components/icons.tsx b/apps/web/components/icons.tsx index 74a63bb..082e650 100644 --- a/apps/web/components/icons.tsx +++ b/apps/web/components/icons.tsx @@ -1,6 +1,7 @@ import { AlertTriangle, ArrowRight, + Blocks, Check, ChevronLeft, ChevronRight, @@ -40,6 +41,7 @@ export type Icon = LucideIcon; export const Icons = { dashboard: LayoutDashboardIcon, + blocks: Blocks, logo: Command, login: LogIn, close: X, diff --git a/apps/web/constants/data.ts b/apps/web/constants/data.ts index 72ce81c..de2edfe 100644 --- a/apps/web/constants/data.ts +++ b/apps/web/constants/data.ts @@ -10,7 +10,14 @@ export const GeneralItems: NavItem[] = [ isActive: false, items: [], // No child items }, - + { + title: 'Inventario', + url: '/dashboard/inventario/', + icon: 'blocks', + shortcut: ['p', 'p'], + isActive: false, + items: [], // No child items + }, ]; diff --git a/apps/web/feactures/inventory/actions/actions.ts b/apps/web/feactures/inventory/actions/actions.ts new file mode 100644 index 0000000..ec0e850 --- /dev/null +++ b/apps/web/feactures/inventory/actions/actions.ts @@ -0,0 +1,153 @@ +'use server'; +import { safeFetchApi } from '@/lib/fetch.api'; +import { + surveysApiResponseSchema, + CreateUser, + productMutate, + UpdateUser +} from '../schemas/inventory'; + +import { auth } from '@/lib/auth'; + + +export const getProfileAction = async () => { + const session = await auth() + const id = session?.user?.id + + const [error, response] = await safeFetchApi( + productMutate, + `/users/${id}`, + 'GET' + ); + if (error) throw new Error(error.message); + return response; +}; + +export const updateProfileAction = async (payload: UpdateUser) => { + const { id, ...payloadWithoutId } = payload; + + const [error, data] = await safeFetchApi( + productMutate, + `/users/profile/${id}`, + 'PATCH', + payloadWithoutId, + ); + + console.log(payload); + if (error) { + if (error.message === 'Email already exists') { + throw new Error('Ese correo ya está en uso'); + } + // console.error('Error:', error); + throw new Error('Error al crear el usuario'); + } + return data; +}; + +export const getInventoryAction = async (params: { + page?: number; + limit?: number; + search?: string; + sortBy?: string; + sortOrder?: 'asc' | 'desc'; +}) => { + + const searchParams = new URLSearchParams({ + page: (params.page || 1).toString(), + limit: (params.limit || 10).toString(), + ...(params.search && { search: params.search }), + ...(params.sortBy && { sortBy: params.sortBy }), + ...(params.sortOrder && { sortOrder: params.sortOrder }), + }); + + const [error, response] = await safeFetchApi( + surveysApiResponseSchema, + `/inventory?${searchParams}`, + 'GET', + ); + + if (error) { + console.error(error); + + throw new Error(error.message); + } + + + // const transformedData = response?.data ? transformSurvey(response?.data) : undefined; + + return { + data: response?.data || [], + meta: response?.meta || { + page: 1, + limit: 10, + totalCount: 0, + totalPages: 1, + hasNextPage: false, + hasPreviousPage: false, + nextPage: null, + previousPage: null, + }, + }; +} + +export const createUserAction = async (payload: CreateUser) => { + const { id, confirmPassword, ...payloadWithoutId } = payload; + + const [error, data] = await safeFetchApi( + productMutate, + '/users', + 'POST', + payloadWithoutId, + ); + + if (error) { + if (error.message === 'Username already exists') { + throw new Error('Ese usuario ya existe'); + } + if (error.message === 'Email already exists') { + throw new Error('Ese correo ya está en uso'); + } + // console.error('Error:', error); + throw new Error('Error al crear el usuario'); + } + + return payloadWithoutId; +}; + +export const updateUserAction = async (payload: UpdateUser) => { + try { + const { id, ...payloadWithoutId } = payload; + + const [error, data] = await safeFetchApi( + productMutate, + `/users/${id}`, + 'PATCH', + payloadWithoutId, + ); + + // console.log(data); + if (error) { + console.error(error); + + throw new Error(error?.message || 'Error al actualizar el usuario'); + } + return data; + } catch (error) { + console.error(error); + } +} + +export const deleteUserAction = async (id: Number) => { + const [error] = await safeFetchApi( + productMutate, + `/users/${id}`, + 'DELETE' + ) + + console.log(error); + + + // if (error) throw new Error(error.message || 'Error al eliminar el usuario') + + return true; +} \ No newline at end of file diff --git a/apps/web/feactures/inventory/components/inventory/create-user-form.tsx b/apps/web/feactures/inventory/components/inventory/create-user-form.tsx new file mode 100644 index 0000000..86cbc86 --- /dev/null +++ b/apps/web/feactures/inventory/components/inventory/create-user-form.tsx @@ -0,0 +1,222 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { Button } from '@repo/shadcn/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@repo/shadcn/form'; +import { Input } from '@repo/shadcn/input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@repo/shadcn/select'; +import { useForm } from 'react-hook-form'; +import { useCreateUser } from "../../hooks/use-mutation-users"; +import { CreateUser, createUser } from '../../schemas/inventory'; + +const ROLES = { + // 1: 'Superadmin', + 2: 'Administrador', + 3: 'autoridad', + 4: 'Gerente', + 5: 'Usuario', + 6: 'Productor', + 7: 'Organización' +} + +interface CreateUserFormProps { + onSuccess?: () => void; + onCancel?: () => void; + defaultValues?: Partial; +} + +export function CreateUserForm({ + onSuccess, + onCancel, + defaultValues, +}: CreateUserFormProps) { + const { + mutate: saveAccountingAccounts, + isPending: isSaving, + isError, + } = useCreateUser(); + + // const { data: AccoutingAccounts } = useSurveyMutation(); + + const defaultformValues = { + username: defaultValues?.username || '', + fullname: defaultValues?.fullname || '', + email: defaultValues?.email || '', + password: '', + confirmPassword: '', + id: defaultValues?.id, + phone: defaultValues?.phone || '', + role: defaultValues?.role, + } + + const form = useForm({ + resolver: zodResolver(createUser), + defaultValues: defaultformValues, + mode: 'onChange', // Enable real-time validation + }); + + const onSubmit = async (data: CreateUser) => { + + const formData = data + + saveAccountingAccounts(formData, { + onSuccess: () => { + form.reset(); + onSuccess?.(); + }, + onError: (e) => { + form.setError('root', { + type: 'manual', + message: e.message, + }); + }, + }); + }; + + return ( +
+ + {form.formState.errors.root && ( +
+ {form.formState.errors.root.message} +
+ )} +
+ ( + + Usuario + + + + + + )} + /> + + ( + + Nombre completo + + + + + + )} + /> + + ( + + Correo + + + + + + )} + /> + + ( + + Teléfono + + + + + + )} + /> + + ( + + Contraseña + + + + + + )} + /> + + ( + + Confirmar Contraseña + + + + + + )} + /> + + ( + + Rol + + + + )} + /> +
+ +
+ + +
+
+ + ); +} diff --git a/apps/web/feactures/inventory/components/inventory/product-inventory-list.tsx b/apps/web/feactures/inventory/components/inventory/product-inventory-list.tsx new file mode 100644 index 0000000..3fcb913 --- /dev/null +++ b/apps/web/feactures/inventory/components/inventory/product-inventory-list.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { DataTable } from '@repo/shadcn/table/data-table'; +import { DataTableSkeleton } from '@repo/shadcn/table/data-table-skeleton'; +import { columns } from './product-tables/columns'; +import { useProductQuery } from '../../hooks/use-query-users'; + +interface SurveysAdminListProps { + initialPage: number; + initialSearch?: string | null; + initialLimit: number; + initialType?: string | null; +} + +export default function UsersAdminList({ + initialPage, + initialSearch, + initialLimit, + initialType, +}: SurveysAdminListProps) { + const filters = { + page: initialPage, + limit: initialLimit, + ...(initialSearch && { search: initialSearch }), + ...(initialType && { type: initialType }), + }; + + const {data, isLoading} = useProductQuery(filters) + + + // const {data, isLoading} = useUsersQuery(filters) + + console.log(data?.data); + + if (isLoading) { + return ; + } + + return ( + + ); +} diff --git a/apps/web/feactures/inventory/components/inventory/product-tables/cell-action.tsx b/apps/web/feactures/inventory/components/inventory/product-tables/cell-action.tsx new file mode 100644 index 0000000..53fac03 --- /dev/null +++ b/apps/web/feactures/inventory/components/inventory/product-tables/cell-action.tsx @@ -0,0 +1,90 @@ +'use client'; +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { AlertModal } from '@/components/modal/alert-modal'; +import { Button } from '@repo/shadcn/button'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@repo/shadcn/tooltip'; +import { Edit, Trash, User } from 'lucide-react'; +import { InventoryTable } from '@/feactures/inventory/schemas/inventory'; +import { useDeleteUser } from '@/feactures/users/hooks/use-mutation-users'; +import { AccountPlanModal } from '../user-modal'; + +interface CellActionProps { + data: InventoryTable; +} + +export const CellAction: React.FC = ({ data }) => { + const [loading, setLoading] = useState(false); + const [open, setOpen] = useState(false); + const [edit, setEdit] = useState(false); + const { mutate: deleteUser } = useDeleteUser(); + const router = useRouter(); + + const onConfirm = async () => { + try { + setLoading(true); + deleteUser(data.id!); + setOpen(false); + } catch (error) { + console.error('Error:', error); + } finally { + setLoading(false); + } + }; + + return ( + <> + setOpen(false)} + onConfirm={onConfirm} + loading={loading} + title="¿Estás seguro que desea deshabilitar este usuario?" + description="Esta acción no se puede deshacer." + /> + + + +
+ + + + + + +

Editar

+
+
+
+ + + + + + + +

Deshabilitar

+
+
+
+
+ + ); +}; diff --git a/apps/web/feactures/inventory/components/inventory/product-tables/columns.tsx b/apps/web/feactures/inventory/components/inventory/product-tables/columns.tsx new file mode 100644 index 0000000..a9b47bf --- /dev/null +++ b/apps/web/feactures/inventory/components/inventory/product-tables/columns.tsx @@ -0,0 +1,41 @@ +import { Badge } from "@repo/shadcn/badge"; + +import { ColumnDef } from '@tanstack/react-table'; +import { CellAction } from './cell-action'; +import { InventoryTable } from '../../../schemas/inventory'; + +export const columns: ColumnDef[] = [ + { + accessorKey: 'urlImg', + header: 'img', + cell: ({ row }) => { + const status = row.getValue("urlImg") as string | undefined; + return ( + Image + ) + }, + }, + { + accessorKey: 'title', + header: 'Producto', + }, + { + accessorKey: "description", + header: "Descripcion", + }, + { + accessorKey: 'price', + header: 'Precio', + cell: ({ row }) => `${row.original.price}$` + }, + { + accessorKey: 'stock', + header: 'Stock', + }, + + { + id: 'actions', + header: 'Acciones', + cell: ({ row }) => , + }, +]; diff --git a/apps/web/feactures/inventory/components/inventory/product-tables/use-survey-table-filters.tsx b/apps/web/feactures/inventory/components/inventory/product-tables/use-survey-table-filters.tsx new file mode 100644 index 0000000..65162d4 --- /dev/null +++ b/apps/web/feactures/inventory/components/inventory/product-tables/use-survey-table-filters.tsx @@ -0,0 +1,59 @@ +'use client'; + +import { PUBLISHED_TYPES } from '@/feactures/surveys/schemas/surveys-options'; +import { searchParams } from '@repo/shadcn/lib/searchparams'; +import { useQueryState } from 'nuqs'; +import { useCallback, useMemo } from 'react'; + + +export const TYPE_OPTIONS = Object.entries(PUBLISHED_TYPES).map( + ([value, label]) => ({ + value, + label, + }), +); + + +export function useSurveyTableFilters() { + const [searchQuery, setSearchQuery] = useQueryState( + 'q', + searchParams.q + .withOptions({ + shallow: false, + throttleMs: 500, // Add 500ms delay + // Removed dedupingInterval as it's not a valid option + }) + .withDefault(''), + ); + + const [typeFilter, setTypeFilter] = useQueryState( + 'published', + searchParams.q.withOptions({ shallow: false }).withDefault(''), + ); + + const [page, setPage] = useQueryState( + 'page', + searchParams.page.withDefault(1), + ); + + const resetFilters = useCallback(() => { + setSearchQuery(null); + setTypeFilter(null); + setPage(1); + }, [setSearchQuery, setPage]); + + const isAnyFilterActive = useMemo(() => { + return !!searchQuery || !!typeFilter; + }, [searchQuery]); + + return { + searchQuery, + setSearchQuery, + page, + setPage, + resetFilters, + isAnyFilterActive, + typeFilter, + setTypeFilter + }; +} diff --git a/apps/web/feactures/inventory/components/inventory/product-tables/users-table-action.tsx b/apps/web/feactures/inventory/components/inventory/product-tables/users-table-action.tsx new file mode 100644 index 0000000..bc714d0 --- /dev/null +++ b/apps/web/feactures/inventory/components/inventory/product-tables/users-table-action.tsx @@ -0,0 +1,36 @@ +'use client'; + +import { DataTableFilterBox } from '@repo/shadcn/table/data-table-filter-box'; +import { DataTableSearch } from '@repo/shadcn/table/data-table-search'; +import { + TYPE_OPTIONS, + useSurveyTableFilters, +} from './use-survey-table-filters'; + +export default function UserTableAction() { + const { + typeFilter, + searchQuery, + setPage, + setTypeFilter, + setSearchQuery, + } = useSurveyTableFilters(); + + return ( +
+ + {/* */} +
+ ); +} diff --git a/apps/web/feactures/inventory/components/inventory/update-user-form.tsx b/apps/web/feactures/inventory/components/inventory/update-user-form.tsx new file mode 100644 index 0000000..b96d14f --- /dev/null +++ b/apps/web/feactures/inventory/components/inventory/update-user-form.tsx @@ -0,0 +1,227 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { Button } from '@repo/shadcn/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@repo/shadcn/form'; +import { Input } from '@repo/shadcn/input'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@repo/shadcn/select'; +import { useForm } from 'react-hook-form'; +import { useUpdateUser } from "@/feactures/users/hooks/use-mutation-users"; +import { UpdateUser, updateUser } from '@/feactures/users/schemas/users'; + +const ROLES = { + // 1: 'Superadmin', + 2: 'Administrador', + 3: 'autoridad', + 4: 'Gerente', + 5: 'Usuario', + 6: 'Productor', + 7: 'Organización' +} + +interface UserFormProps { + onSuccess?: () => void; + onCancel?: () => void; + defaultValues?: Partial; +} + +export function UpdateUserForm({ + onSuccess, + onCancel, + defaultValues, +}: UserFormProps) { + const { + mutate: saveAccountingAccounts, + isPending: isSaving, + isError, + } = useUpdateUser(); + + const defaultformValues = { + username: defaultValues?.username || '', + fullname: defaultValues?.fullname || '', + email: defaultValues?.email || '', + password: '', + id: defaultValues?.id, + phone: defaultValues?.phone || '', + role: undefined, + isActive: defaultValues?.isActive + } + + // console.log(defaultValues); + + const form = useForm({ + resolver: zodResolver(updateUser), + defaultValues: defaultformValues, + mode: 'onChange', // Enable real-time validation + }); + + const onSubmit = async (data: UpdateUser) => { + + const formData = data + + saveAccountingAccounts(formData, { + onSuccess: () => { + form.reset(); + onSuccess?.(); + }, + onError: () => { + form.setError('root', { + type: 'manual', + message: 'Error al guardar la cuenta contable', + }); + }, + }); + }; + + return ( +
+ + {form.formState.errors.root && ( +
+ {form.formState.errors.root.message} +
+ )} +
+ ( + + Usuario + + + + + + )} + /> + + ( + + Nombre completo + + + + + + )} + /> + + ( + + Correo + + + + + + )} + /> + + ( + + Teléfono + + + + + + )} + /> + + ( + + Nueva Contraseña + + + + + + )} + /> + + ( + + Rol + + + + )} + /> + + ( + + Estatus + + + + )} + /> +
+ +
+ + +
+
+ + ); +} diff --git a/apps/web/feactures/inventory/components/inventory/user-modal.tsx b/apps/web/feactures/inventory/components/inventory/user-modal.tsx new file mode 100644 index 0000000..b4230f8 --- /dev/null +++ b/apps/web/feactures/inventory/components/inventory/user-modal.tsx @@ -0,0 +1,69 @@ +'use client'; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@repo/shadcn/dialog'; +import { AccountPlan } from '@/feactures/users/schemas/account-plan.schema'; +import { CreateUserForm } from './create-user-form'; +import { UpdateUserForm } from './update-user-form'; + +interface AccountPlanModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + defaultValues?: Partial; +} + +export function AccountPlanModal({ + open, + onOpenChange, + defaultValues, +}: AccountPlanModalProps) { + const handleSuccess = () => { + onOpenChange(false); + }; + + const handleCancel = () => { + onOpenChange(false); + }; + + return ( + { + if (!open) { + onOpenChange(false); + } + }} + > + + + + {defaultValues?.id + ? 'Actualizar usuario' + : 'Crear usuario'} + + + Complete los campos para {defaultValues?.id ? 'actualizar' : 'crear'} un usuario + + + {defaultValues?.id ? ( + + ): ( + + )} + + + ); +} diff --git a/apps/web/feactures/inventory/components/inventory/users-header.tsx b/apps/web/feactures/inventory/components/inventory/users-header.tsx new file mode 100644 index 0000000..891c9dc --- /dev/null +++ b/apps/web/feactures/inventory/components/inventory/users-header.tsx @@ -0,0 +1,27 @@ +'use client'; +import { useRouter } from 'next/navigation'; +import { Button } from '@repo/shadcn/button'; +import { Heading } from '@repo/shadcn/heading'; +import { Plus } from 'lucide-react'; +import { useState } from 'react'; +import { AccountPlanModal } from './user-modal'; + +export function UsersHeader() { + const [open, setOpen] = useState(false); + // const router = useRouter(); + return ( + <> +
+ + +
+ + + + ); +} \ No newline at end of file diff --git a/apps/web/feactures/inventory/components/modal-profile.tsx b/apps/web/feactures/inventory/components/modal-profile.tsx new file mode 100644 index 0000000..4f1853a --- /dev/null +++ b/apps/web/feactures/inventory/components/modal-profile.tsx @@ -0,0 +1,57 @@ +'use client'; + +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@repo/shadcn/dialog'; +import { AccountPlan } from '@/feactures/users/schemas/account-plan.schema'; +import { ModalForm } from './update-user-form'; + +interface AccountPlanModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + defaultValues?: Partial; +} + +export function AccountPlanModal({ + open, + onOpenChange, + defaultValues, +}: AccountPlanModalProps) { + const handleSuccess = () => { + onOpenChange(false); + }; + + const handleCancel = () => { + onOpenChange(false); + }; + + return ( + { + if (!open) { + onOpenChange(false); + } + }} + > + + + Actualizar Perfil + + Complete los campos para actualizar sus datos.
Los campos vacios no seran actualizados. +
+
+ + +
+
+ ); +} diff --git a/apps/web/feactures/inventory/components/selectList.tsx b/apps/web/feactures/inventory/components/selectList.tsx new file mode 100644 index 0000000..4150cd5 --- /dev/null +++ b/apps/web/feactures/inventory/components/selectList.tsx @@ -0,0 +1,85 @@ +// 'use client'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@repo/shadcn/select'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@repo/shadcn/form'; +import { UpdateUser, updateUser } from '@/feactures/users/schemas/users'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; + +interface SelectListProps { + label: string + // values: + values: Array + form: any + name: string + handleChange: any +} + +export function SelectList({ label, values, form, name, handleChange }: SelectListProps) { + // const { label, values, form, name } = props; + // handleChange + + // const defaultformValues = { + // username: '', + // fullname: '', + // email: '', + // password: '', + // id: 0, + // phone: '', + // role: undefined, + // isActive: false + // } + + // const form = useForm({ + // resolver: zodResolver(updateUser), + // defaultValues: defaultformValues, + // mode: 'onChange', // Enable real-time validation + // }); + + + return ( + + {label} + + + + )} + /> +} \ No newline at end of file diff --git a/apps/web/feactures/inventory/components/survey.tsx b/apps/web/feactures/inventory/components/survey.tsx new file mode 100644 index 0000000..b4b5eb9 --- /dev/null +++ b/apps/web/feactures/inventory/components/survey.tsx @@ -0,0 +1,28 @@ +'use client' + +import { SurveyResponse } from '@/feactures/surveys/components/survey-response'; +import { useSurveysByIdQuery } from '@/feactures/surveys/hooks/use-query-surveys'; + +import { notFound, useParams } from 'next/navigation'; + + +export default function SurveyPage() { + const params = useParams(); + const surveyId = params?.id as string | undefined; + + + if (!surveyId || surveyId === '') { + notFound(); + } + + const { data: survey, isLoading } = useSurveysByIdQuery(Number(surveyId)); + console.log('🎯 useSurveysByIdQuery ejecutado, data:', survey, 'isLoading:', isLoading); + + if (!survey?.data || !survey?.data.published) { + notFound(); + } + + return ( + + ); +} \ No newline at end of file diff --git a/apps/web/feactures/inventory/components/update-user-form.tsx b/apps/web/feactures/inventory/components/update-user-form.tsx new file mode 100644 index 0000000..6398864 --- /dev/null +++ b/apps/web/feactures/inventory/components/update-user-form.tsx @@ -0,0 +1,268 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { Button } from '@repo/shadcn/button'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@repo/shadcn/form'; +import { Input } from '@repo/shadcn/input'; +// import { +// Select, +// SelectContent, +// SelectItem, +// SelectTrigger, +// SelectValue, +// } from '@repo/shadcn/select'; +import { SelectSearchable } from '@repo/shadcn/select-searchable' +import { useForm } from 'react-hook-form'; +import { useUpdateProfile } from "@/feactures/users/hooks/use-mutation-users"; +import { UpdateUser, updateUser } from '@/feactures/users/schemas/users'; +import { toast } from 'sonner'; + +import React from 'react'; +import { useStateQuery, useMunicipalityQuery, useParishQuery } from '@/feactures/location/hooks/use-query-location'; + +interface UserFormProps { + onSuccess?: () => void; + onCancel?: () => void; + defaultValues?: Partial; +} + +export function ModalForm({ + onSuccess, + onCancel, + defaultValues, +}: UserFormProps) { + const { + mutate: saveAccountingAccounts, + isPending: isSaving, + isError, + } = useUpdateProfile(); + + const [state, setState] = React.useState(0); + const [municipality, setMunicipality] = React.useState(0); + const [parish, setParish] = React.useState(0); + + const [disabledMunicipality, setDisabledMunicipality] = React.useState(true); + const [disabledParish, setDisabledParish] = React.useState(true); + + const { data : dataState } = useStateQuery() + const { data : dataMunicipality } = useMunicipalityQuery(state) + const { data : dataParish } = useParishQuery(municipality) + + const stateOptions = dataState?.data || [{id:0,name:'Sin estados'}] + + const municipalityOptions = Array.isArray(dataMunicipality?.data) && dataMunicipality.data.length > 0 + ? dataMunicipality.data + : [{id:0,stateId:0,name:'Sin Municipios'}] + // const parishOptions = dataParish?.data || [{id:0,municipalityId:0,name:'Sin Parroquias'}] + const parishOptions = Array.isArray(dataParish?.data) && dataParish.data.length > 0 + ? dataParish.data + : [{id:0,stateId:0,name:'Sin Parroquias'}] + + + const defaultformValues = { + username: defaultValues?.username || '', + fullname: defaultValues?.fullname || '', + email: defaultValues?.email || '', + password: '', + id: defaultValues?.id, + phone: defaultValues?.phone || '', + role: undefined, + isActive: defaultValues?.isActive, + state: defaultValues?.state, + municipality: defaultValues?.municipality, + parish: defaultValues?.parish + } + + + + // console.log(defaultValues); + + const form = useForm({ + resolver: zodResolver(updateUser), + defaultValues: defaultformValues, + mode: 'onChange', // Enable real-time validation + }); + + const onSubmit = async (data: UpdateUser) => { + + const formData = data + + saveAccountingAccounts(formData, { + onSuccess: () => { + form.reset(); + onSuccess?.(); + toast.success('Actualizado exitosamente!'); + }, + onError: (e) => { + form.setError('root', { + type: 'manual', + message: e.message, + }); + // toast.error(e.message); + }, + }); + }; + + return ( +
+ + {form.formState.errors.root && ( +
+ {form.formState.errors.root.message} +
+ )} +
+ ( + + Nombre completo + + + + + + )} + /> + + ( + + Correo + + + + + + )} + /> + + ( + + Teléfono + + + + + + )} + /> + + ( + + Estado + + ({ + value: item.id.toString(), + label: item.name, + })) || [] + } + onValueChange={(value : any) => + {field.onChange(Number(value)); setState(value); setDisabledMunicipality(false); setDisabledParish(true)} + } + placeholder="Selecciona un estado" + defaultValue={field.value?.toString()} + // disabled={readOnly} + /> + + + )} + /> + + ( + + Municipio + + ({ + value: item.id.toString(), + label: item.name, + })) || [] + } + onValueChange={(value : any) => + {field.onChange(Number(value)); setMunicipality(value); setDisabledParish(false)} + } + placeholder="Selecciona un Municipio" + defaultValue={field.value?.toString()} + disabled={disabledMunicipality} + /> + + + )} + /> + + ( + + Parroquia + + ({ + value: item.id.toString(), + label: item.name, + })) || [] + } + onValueChange={(value : any) => + field.onChange(Number(value)) + } + placeholder="Selecciona una Parroquia" + defaultValue={field.value?.toString()} + disabled={disabledParish} + /> + + + )} + /> + + ( + + Nueva Contraseña + + + + + + )} + /> +
+ +
+ + +
+
+ + ); +} diff --git a/apps/web/feactures/inventory/components/user-profile.tsx b/apps/web/feactures/inventory/components/user-profile.tsx new file mode 100644 index 0000000..72c621c --- /dev/null +++ b/apps/web/feactures/inventory/components/user-profile.tsx @@ -0,0 +1,75 @@ +'use client'; +import { useUserByProfile } from '@/feactures/users/hooks/use-query-users'; +import { Button } from '@repo/shadcn/button'; +import { Edit, Edit2 } from 'lucide-react'; +import { useState } from 'react'; +import { AccountPlanModal } from './modal-profile'; + +export function Profile() { + const [open, setOpen] = useState(false); + + const { data } = useUserByProfile(); + + // console.log("🎯 data:", data); + + return ( +
+ + + + +

Datos del usuario

+
+
+

Usuario:

+

{data?.data.username || 'Sin Nombre de Usuario'}

+
+ +
+

Rol:

+

{data?.data.role || 'Sin Rol'}

+
+
+ +

Información personal

+
+
+

Nombre completo:

+

{data?.data.fullname || 'Sin nombre y apellido'}

+
+ +
+

Correo:

+

{data?.data.email || 'Sin correo'}

+
+ +
+

Teléfono:

+

{data?.data.phone || 'Sin teléfono'}

+
+
+ +

Información de ubicación

+
+
+

Estado:

+

{data?.data.state || 'Sin Estado'}

+
+ +
+

Municipio:

+

{data?.data.municipality || 'Sin Municipio'}

+
+ +
+

Parroquia:

+

{data?.data.parish || 'Sin Parroquia'}

+
+
+ +
+ ); +} + diff --git a/apps/web/feactures/inventory/hooks/use-mutation-users.ts b/apps/web/feactures/inventory/hooks/use-mutation-users.ts new file mode 100644 index 0000000..dbbf733 --- /dev/null +++ b/apps/web/feactures/inventory/hooks/use-mutation-users.ts @@ -0,0 +1,45 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { CreateUser, UpdateUser } from "../schemas/inventory"; +import { updateUserAction, createUserAction, deleteUserAction, updateProfileAction } from "../actions/actions"; + +// Create mutation +export function useCreateUser() { + const queryClient = useQueryClient(); + const mutation = useMutation({ + mutationFn: (data: CreateUser) => createUserAction(data), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }), + // onError: (e) => console.error('Error:', e), + }) + return mutation +} + +// Update mutation +export function useUpdateUser() { + const queryClient = useQueryClient(); + const mutation = useMutation({ + mutationFn: (data: UpdateUser) => updateUserAction(data), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }), + onError: (e) => console.error('Error:', e) + }) + return mutation; +} + +export function useUpdateProfile() { + const queryClient = useQueryClient(); + const mutation = useMutation({ + mutationFn: (data: UpdateUser) => updateProfileAction(data), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }), + // onError: (e) => console.error('Error:', e) + }) + return mutation; +} + +// Delete mutation +export function useDeleteUser() { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: (id: number) => deleteUserAction(id), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['users'] }), + onError: (e) => console.error('Error:', e) + }) +} \ No newline at end of file diff --git a/apps/web/feactures/inventory/hooks/use-query-surveys.ts b/apps/web/feactures/inventory/hooks/use-query-surveys.ts new file mode 100644 index 0000000..6610191 --- /dev/null +++ b/apps/web/feactures/inventory/hooks/use-query-surveys.ts @@ -0,0 +1,12 @@ +'use client' +import { useSafeQuery } from "@/hooks/use-safe-query"; +import { getUsersAction,getProfileAction} from "../actions/actions"; + +// Hook for users +export function useUsersQuery(params = {}) { + return useSafeQuery(['users',params], () => getUsersAction(params)) +} + +export function useUserByProfile() { + return useSafeQuery(['users'], () => getProfileAction()) +} diff --git a/apps/web/feactures/inventory/hooks/use-query-users.ts b/apps/web/feactures/inventory/hooks/use-query-users.ts new file mode 100644 index 0000000..2572cfd --- /dev/null +++ b/apps/web/feactures/inventory/hooks/use-query-users.ts @@ -0,0 +1,8 @@ +'use client' +import { useSafeQuery } from "@/hooks/use-safe-query"; +import { getInventoryAction} from "../actions/actions"; + +// Hook for users +export function useProductQuery(params = {}) { + return useSafeQuery(['product',params], () => getInventoryAction(params)) +} \ No newline at end of file diff --git a/apps/web/feactures/inventory/schemas/account-plan-options.ts b/apps/web/feactures/inventory/schemas/account-plan-options.ts new file mode 100644 index 0000000..b4d997d --- /dev/null +++ b/apps/web/feactures/inventory/schemas/account-plan-options.ts @@ -0,0 +1,19 @@ +export const ACCOUNT_TYPES = { + activo: 'Activo', + pasivo: 'Pasivo', + patrimonio: 'Patrimonio', + ingreso: 'Ingreso', + gasto: 'Gasto', + costo: 'Costo', + cuenta_orden: 'Cuenta de Orden', +} as const; + +export const ACCOUNT_LEVELS = { + 1: 'Nivel 1 - Cuenta Principal', + 2: 'Nivel 2 - Subcuenta', + 3: 'Nivel 3 - Cuenta Detallada', + 4: 'Nivel 4 - Cuenta Auxiliar', +} as const; + +export type AccountType = keyof typeof ACCOUNT_TYPES; +export type AccountLevel = keyof typeof ACCOUNT_LEVELS; \ No newline at end of file diff --git a/apps/web/feactures/inventory/schemas/account-plan.schema.ts b/apps/web/feactures/inventory/schemas/account-plan.schema.ts new file mode 100644 index 0000000..8edc268 --- /dev/null +++ b/apps/web/feactures/inventory/schemas/account-plan.schema.ts @@ -0,0 +1,83 @@ +import { z } from 'zod'; + +export const accountPlanSchema = z + .object({ + id: z.number().optional(), + savingBankId: z.number(), + code: z + .string() + .min(1, 'El código es requerido') + .max(50, 'El código no puede tener más de 50 caracteres') + .regex(/^[\d.]+$/, 'El código debe contener solo números y puntos'), + name: z + .string() + .min(1, 'El nombre es requerido') + .max(100, 'El nombre no puede tener más de 100 caracteres'), + type: z.enum( + [ + 'activo', + 'pasivo', + 'patrimonio', + 'ingreso', + 'gasto', + 'costo', + 'cuenta_orden', + ], + { + required_error: 'El tipo de cuenta es requerido', + invalid_type_error: 'Tipo de cuenta inválido', + }, + ), + description: z.string().optional().nullable(), + level: z + .number() + .min(1, 'El nivel debe ser mayor a 0') + .max(4, 'El nivel no puede ser mayor a 4'), + parent_account_id: z.number().nullable(), + created_at: z.string().optional(), + updated_at: z.string().optional(), + }) + .refine( + (data) => { + if (data.level > 1 && !data.parent_account_id) { + return false; + } + return true; + }, + { + message: 'Las cuentas de nivel superior a 1 requieren una cuenta padre', + path: ['parent_account_id'], + }, + ); + +export type AccountPlan = z.infer; + +// Response schemas for the API +export const accountPlanResponseSchema = z.object({ + message: z.string(), + data: accountPlanSchema, +}); + +export const accountPlanDeleteResponseSchema = z.object({ + message: z.string(), +}); + +export const accountPlanListResponseSchema = z.object({ + message: z.string(), + data: z.array(accountPlanSchema), +}); + +export const accountPlanPaginationResponseSchema = z.object({ + message: z.string(), + data: z.array(accountPlanSchema), + meta: z.object({ + page: z.number(), + limit: z.number(), + totalCount: z.number(), + totalPages: z.number(), + hasNextPage: z.boolean(), + hasPreviousPage: z.boolean(), + nextPage: z.number().nullable(), + previousPage: z.number().nullable(), + }), +}); diff --git a/apps/web/feactures/inventory/schemas/inventory.ts b/apps/web/feactures/inventory/schemas/inventory.ts new file mode 100644 index 0000000..640dd85 --- /dev/null +++ b/apps/web/feactures/inventory/schemas/inventory.ts @@ -0,0 +1,69 @@ +import { title } from 'process'; +import { z } from 'zod'; + +export type InventoryTable = z.infer; +export type CreateUser = z.infer; +export type UpdateUser = z.infer; + +export const product = z.object({ + id: z.number().optional(), + title: z.string(), + description: z.string(), + // price: z.number(), + // quantity: z.number(), + // category: z.string(), + // image: z.string().optional(), + stock: z.number(), + price: z.string(), + urlImg: z.string(), + userId: z.number().optional(), +}); + +export const createUser = z.object({ + id: z.number().optional(), + username: z.string().min(5, { message: "Debe de tener 5 o más caracteres" }), + password: z.string().min(8, { message: "Debe de tener 8 o más caracteres" }), + email: z.string().email({ message: "Correo no válido" }), + fullname: z.string(), + phone: z.string(), + confirmPassword: z.string(), + role: z.number() +}) +.refine((data) => data.password === data.confirmPassword, { + message: 'La contraseña no coincide', + path: ['confirmPassword'], +}) + +export const updateUser = z.object({ + id: z.number(), + username: z.string().min(5, { message: "Debe de tener 5 o más caracteres" }).or(z.literal('')), + password: z.string().min(6, { message: "Debe de tener 6 o más caracteres" }).or(z.literal('')), + email: z.string().email({ message: "Correo no válido" }).or(z.literal('')), + fullname: z.string().optional(), + phone: z.string().optional(), + role: z.number().optional(), + isActive: z.boolean().optional(), + state: z.number().optional().nullable(), + municipality: z.number().optional().nullable(), + parish: z.number().optional().nullable(), +}) + +export const surveysApiResponseSchema = z.object({ + message: z.string(), + data: z.array(product), + meta: z.object({ + page: z.number(), + limit: z.number(), + totalCount: z.number(), + totalPages: z.number(), + hasNextPage: z.boolean(), + hasPreviousPage: z.boolean(), + nextPage: z.number().nullable(), + previousPage: z.number().nullable(), + }), +}) + +export const productMutate = z.object({ + message: z.string(), + data: product, +}) \ No newline at end of file diff --git a/apps/web/feactures/inventory/schemas/surveys-options.ts b/apps/web/feactures/inventory/schemas/surveys-options.ts new file mode 100644 index 0000000..06c4c94 --- /dev/null +++ b/apps/web/feactures/inventory/schemas/surveys-options.ts @@ -0,0 +1,6 @@ +export const PUBLISHED_TYPES = { + published: 'Publicada', + draft: 'Borrador', +} as const; + +export type PublishedType = keyof typeof PUBLISHED_TYPES; diff --git a/apps/web/feactures/inventory/utils/date-utils.ts b/apps/web/feactures/inventory/utils/date-utils.ts new file mode 100644 index 0000000..ec39a0e --- /dev/null +++ b/apps/web/feactures/inventory/utils/date-utils.ts @@ -0,0 +1,11 @@ +export function formatDate(date: Date): string { + return new Intl.DateTimeFormat('es-ES', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }).format(date); +} + + diff --git a/apps/web/feactures/inventory/utils/searchparams.ts b/apps/web/feactures/inventory/utils/searchparams.ts new file mode 100644 index 0000000..2d79317 --- /dev/null +++ b/apps/web/feactures/inventory/utils/searchparams.ts @@ -0,0 +1,16 @@ +import { + createSearchParamsCache, + createSerializer, + parseAsInteger, + parseAsString, +} from 'nuqs/server'; + +export const searchParams = { + page: parseAsInteger.withDefault(1), + limit: parseAsInteger.withDefault(10), + q: parseAsString, + type: parseAsString, +}; + +export const searchParamsCache = createSearchParamsCache(searchParams); +export const serialize = createSerializer(searchParams); diff --git a/apps/web/public/apple.avif b/apps/web/public/apple.avif new file mode 100644 index 0000000000000000000000000000000000000000..60b2ba014a9c68482b3307578e5b05ced2c59dff GIT binary patch literal 8030 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 literal 0 HcmV?d00001