Crear-editar productos, depuracion de archivos users

This commit is contained in:
2025-06-27 13:14:50 -04:00
parent ed2a1da038
commit 2840bbec11
23 changed files with 333 additions and 1193 deletions

View File

@@ -18,12 +18,19 @@ export class CreateProductDto {
price: string;
@ApiProperty()
@IsString({
@IsInt({
message: 'stock must be a number',
})
@IsOptional()
stock: number;
@ApiProperty()
@IsInt({
message: 'stock must be a number',
})
@IsOptional()
userId: number;
@ApiProperty()
@IsString({
message: 'urlImg must be a string',

View File

@@ -5,4 +5,15 @@ export class Product {
price: string;
stock: number;
urlImg: string;
UserId?: number;
}
export class CreateProduct {
id: number;
title: string;
description: string;
price: string;
stock: string;
urlImg: string;
UserId: number;
}

View File

@@ -3,7 +3,7 @@ import { InventoryService } from './inventory.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { Roles } from '../../common/decorators/roles.decorator';
// import { Roles } from '../../common/decorators/roles.decorator';
import { PaginationDto } from '../../common/dto/pagination.dto';
@ApiTags('inventory')
@@ -42,22 +42,19 @@ export class UsersController {
@Body() createUserDto: CreateProductDto,
@Query('roleId') roleId?: string,
) {
const data = await this.inventoryService.create(
createUserDto,
roleId ? parseInt(roleId) : undefined,
);
const data = await this.inventoryService.create(createUserDto)
return { message: 'User created successfully', data };
}
// @Patch(':id')
@Patch(':id')
// @Roles('admin')
// @ApiOperation({ summary: 'Update a user' })
// @ApiResponse({ status: 200, description: 'User updated successfully.' })
// @ApiResponse({ status: 404, description: 'User not found.' })
// async update(@Param('id') id: string, @Body() UpdateProductDto: UpdateProductDto) {
// const data = await this.inventoryService.update(id, UpdateProductDto);
// return { message: 'User updated successfully', data };
// }
@ApiOperation({ summary: 'Update a product' })
@ApiResponse({ status: 200, description: 'Product updated successfully.' })
@ApiResponse({ status: 404, description: 'Product not found.' })
async update(@Param('id') id: string, @Body() UpdateProductDto: UpdateProductDto) {
const data = await this.inventoryService.update(id, UpdateProductDto);
return { message: 'User updated successfully', data };
}
// @Delete(':id')
// @Roles('admin')

View File

@@ -6,7 +6,7 @@ import { products } from 'src/database/index';
import { eq, like, or, SQL, sql, and, not } from 'drizzle-orm';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { Product } from './entities/inventory.entity';
import { Product, CreateProduct } from './entities/inventory.entity';
import { PaginationDto } from '../../common/dto/pagination.dto';
@Injectable()
@@ -54,13 +54,8 @@ export class InventoryService {
price: products.price,
urlImg: products.urlImg,
stock: products.stock,
// price: products.price,
// quantity: products.quantity,
// isActive: products.isActive
})
.from(products)
// .leftJoin(usersRole, eq(usersRole.userId, users.id))
// .leftJoin(roles, eq(roles.id, usersRole.roleId))
.where(searchCondition)
.orderBy(orderBy)
.limit(limit)
@@ -77,9 +72,6 @@ export class InventoryService {
nextPage: page < totalPages ? page + 1 : null,
previousPage: page > 1 ? page - 1 : null,
};
console.log(data);
return { data, meta };
}
@@ -94,16 +86,10 @@ export class InventoryService {
stock: products.stock
})
.from(products)
// .leftJoin(usersRole, eq(usersRole.userId, users.id))
// .leftJoin(roles, eq(roles.id, usersRole.roleId))
// .leftJoin(schema.states, eq(schema.states.id, users.state))
// .leftJoin(schema.municipalities, eq(schema.municipalities.id, users.municipality))
// .leftJoin(schema.parishes, eq(schema.parishes.id, users.parish))
.where(eq(products.id, parseInt(id)));
if (find.length === 0) {
throw new HttpException('User does not exist', HttpStatus.BAD_REQUEST);
throw new HttpException('Product does not exist', HttpStatus.BAD_REQUEST);
}
return find[0];
@@ -111,14 +97,13 @@ export class InventoryService {
// Rest of the service remains the same
async create(
createProductDto: CreateProductDto,
roleId: number = 2,
): Promise<Product> {
createProductDto: CreateProductDto
): Promise<any> {
// Start a transaction
return await this.drizzle.transaction(async (tx) => {
// Create the user
const [newProduct] = await tx
.insert(products)
.values({
@@ -126,72 +111,33 @@ export class InventoryService {
description: createProductDto.description,
price: createProductDto.price,
urlImg: createProductDto.urlImg,
stock: createProductDto.stock
stock: createProductDto.stock,
userId: createProductDto.userId
})
.returning();
// Assign role to user
// await tx.insert(usersRole).values({
// userId: newProduct.id,
// roleId: roleId,
// });
// Return the created user with role
// const [userWithRole] = await tx
// .select({
// id: users.id,
// username: users.username,
// email: users.email,
// fullname: users.fullname,
// phone: users.phone,
// isActive: users.isActive,
// role: roles.name,
// })
// .from(users)
// .leftJoin(usersRole, eq(usersRole.userId, users.id))
// .leftJoin(roles, eq(roles.id, usersRole.roleId))
// .where(eq(users.id, newProduct.id));
return this.findOne(String(newProduct.id));
return newProduct
});
}
// async update(id: string, updateUserDto: UpdateUserDto): Promise<User> {
// const userId = parseInt(id);
async update(id: string, updateProductDto: UpdateProductDto): Promise<Product> {
const productId = parseInt(id);
// // Check if user exists
// await this.findOne(id);
// Check if exists
await this.findOne(id);
// // Prepare update data
// const updateData: any = {};
// if (updateUserDto.username) updateData.username = updateUserDto.username;
// if (updateUserDto.email) updateData.email = updateUserDto.email;
// if (updateUserDto.fullname) updateData.fullname = updateUserDto.fullname;
// if (updateUserDto.password) {
// updateData.password = await bcrypt.hash(updateUserDto.password, 10);
// }
// if (updateUserDto.phone) updateData.phone = updateUserDto.phone;
// if (updateUserDto.isActive) updateData.isActive = updateUserDto.isActive;
// Prepare update data
const updateData: any = {};
if (updateProductDto.title) updateData.title = updateProductDto.title;
if (updateProductDto.description) updateData.description = updateProductDto.description;
if (updateProductDto.price) updateData.price = updateProductDto.price;
if (updateProductDto.stock) updateData.stock = updateProductDto.stock;
if (updateProductDto.urlImg) updateData.urlImg = updateProductDto.urlImg;
// const updateDataRole: any = {};
// if (updateUserDto.role) updateDataRole.roleId = updateUserDto.role;
// // Update user
// await this.drizzle
// .update(users)
// .set(updateData)
// .where(eq(users.id, userId));
// await this.drizzle
// .update(usersRole)
// .set(updateDataRole)
// .where(eq(usersRole.userId, userId));
// // Return updated user
// return this.findOne(id);
// }
const [updatedProduct] = await this.drizzle.update(products).set(updateData).where(eq(products.id, productId)).returning();
return updatedProduct
// Return updated user
// return this.findOne(id);
}
// async remove(id: string): Promise<{ message: string, data: User }> {

View File

@@ -1,49 +1,14 @@
'use server';
import { safeFetchApi } from '@/lib/fetch.api';
import {
surveysApiResponseSchema,
CreateUser,
ApiResponseSchema,
InventoryTable,
productMutate,
UpdateUser
editInventory
} 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;
@@ -61,7 +26,7 @@ export const getInventoryAction = async (params: {
});
const [error, response] = await safeFetchApi(
surveysApiResponseSchema,
ApiResponseSchema,
`/inventory?${searchParams}`,
'GET',
);
@@ -71,8 +36,6 @@ export const getInventoryAction = async (params: {
throw new Error(error.message);
}
// const transformedData = response?.data ? transformSurvey(response?.data) : undefined;
return {
@@ -90,37 +53,35 @@ export const getInventoryAction = async (params: {
};
}
export const createUserAction = async (payload: CreateUser) => {
const { id, confirmPassword, ...payloadWithoutId } = payload;
export const createProductAction = async (payload: InventoryTable) => {
const session = await auth()
const userId = session?.user?.id
const { id, ...payloadWithoutId } = payload;
payloadWithoutId.userId = userId
const [error, data] = await safeFetchApi(
productMutate,
'/users',
'/inventory',
'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);
console.error(error);
throw new Error('Error al crear el usuario');
}
return payloadWithoutId;
};
export const updateUserAction = async (payload: UpdateUser) => {
export const updateUserAction = async (payload: InventoryTable) => {
try {
const { id, ...payloadWithoutId } = payload;
const [error, data] = await safeFetchApi(
productMutate,
`/users/${id}`,
`/inventory/${id}`,
'PATCH',
payloadWithoutId,
);

View File

@@ -1,5 +1,4 @@
'use client';
import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@repo/shadcn/button';
import {
@@ -11,38 +10,30 @@ import {
FormMessage,
} from '@repo/shadcn/form';
import { Input } from '@repo/shadcn/input';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@repo/shadcn/select';
// 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';
import { useCreateUser } from "@/feactures/inventory/hooks/use-mutation";
import { EditInventory, editInventory } from '@/feactures/inventory/schemas/inventory';
import { parse } from 'path';
const ROLES = {
// 1: 'Superadmin',
2: 'Administrador',
3: 'autoridad',
4: 'Gerente',
5: 'Usuario',
6: 'Productor',
7: 'Organización'
}
interface CreateUserFormProps {
interface CreateFormProps {
onSuccess?: () => void;
onCancel?: () => void;
defaultValues?: Partial<CreateUser>;
defaultValues?: Partial<EditInventory>;
}
export function CreateUserForm({
export function CreateForm({
onSuccess,
onCancel,
defaultValues,
}: CreateUserFormProps) {
}: CreateFormProps) {
const {
mutate: saveAccountingAccounts,
isPending: isSaving,
@@ -52,23 +43,20 @@ export function CreateUserForm({
// 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,
title: defaultValues?.title || '',
description: defaultValues?.description || '',
price: defaultValues?.price || '',
stock: defaultValues?.stock || 0,
urlImg: defaultValues?.urlImg || ''
}
const form = useForm<CreateUser>({
resolver: zodResolver(createUser),
const form = useForm<EditInventory>({
resolver: zodResolver(editInventory),
defaultValues: defaultformValues,
mode: 'onChange', // Enable real-time validation
});
const onSubmit = async (data: CreateUser) => {
const onSubmit = async (data: EditInventory) => {
const formData = data
@@ -97,10 +85,10 @@ export function CreateUserForm({
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="username"
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Usuario</FormLabel>
<FormLabel>Nombre/Título</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -111,10 +99,10 @@ export function CreateUserForm({
<FormField
control={form.control}
name="fullname"
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Nombre completo</FormLabel>
<FormLabel>Descripción</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -125,12 +113,14 @@ export function CreateUserForm({
<FormField
control={form.control}
name="email"
name="price"
render={({ field }) => (
<FormItem>
<FormLabel>Correo</FormLabel>
<FormLabel>Precio</FormLabel>
<FormControl>
<Input {...field} />
<Input {...field}
// value={field.value?.toString() ?? ''}
/>
</FormControl>
<FormMessage />
</FormItem>
@@ -139,12 +129,12 @@ export function CreateUserForm({
<FormField
control={form.control}
name="phone"
name="stock"
render={({ field }) => (
<FormItem>
<FormLabel>Teléfono</FormLabel>
<FormLabel>Cantidad/Stock</FormLabel>
<FormControl>
<Input {...field} value={field.value?.toString() ?? ''}/>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
@@ -153,59 +143,17 @@ export function CreateUserForm({
<FormField
control={form.control}
name="password"
name="urlImg"
render={({ field }) => (
<FormItem>
<FormLabel>Contraseña</FormLabel>
<FormLabel>Imagen</FormLabel>
<FormControl>
<Input type="password" {...field}/>
<Input {...field}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='confirmPassword'
render={({ field }) => (
<FormItem>
<FormLabel>Confirmar Contraseña</FormLabel>
<FormControl>
<Input type="password" {...field}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="role"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Rol</FormLabel>
<Select
onValueChange={(value) => field.onChange(Number(value))}
defaultValue={String(field.value)}
>
<FormControl>
<SelectTrigger className="w-full">
<SelectValue placeholder="Selecciona un rol" />
</SelectTrigger>
</FormControl>
<SelectContent className="w-full min-w-[200px]">
{Object.entries(ROLES).map(([value, label]) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex justify-end gap-4">

View File

@@ -7,21 +7,22 @@ import {
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';
// import { AccountPlan } from '@/feactures/users/schemas/account-plan.schema';
import { EditInventory, editInventory } from '../../schemas/inventory';
import { CreateForm } from './create-product-form';
import { UpdateForm } from './update-product-form';
interface AccountPlanModalProps {
interface ModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
defaultValues?: Partial<AccountPlan>;
defaultValues?: Partial<EditInventory>;
}
export function AccountPlanModal({
open,
onOpenChange,
defaultValues,
}: AccountPlanModalProps) {
}: ModalProps) {
const handleSuccess = () => {
onOpenChange(false);
};
@@ -43,21 +44,21 @@ export function AccountPlanModal({
<DialogHeader>
<DialogTitle>
{defaultValues?.id
? 'Actualizar usuario'
: 'Crear usuario'}
? 'Actualizar producto'
: 'Registrar producto'}
</DialogTitle>
<DialogDescription>
Complete los campos para {defaultValues?.id ? 'actualizar' : 'crear'} un usuario
Complete los campos para {defaultValues?.id ? 'actualizar' : 'registrar'} un producto
</DialogDescription>
</DialogHeader>
{defaultValues?.id ? (
<UpdateUserForm
<UpdateForm
onSuccess={handleSuccess}
onCancel={handleCancel}
defaultValues={defaultValues}
/>
): (
<CreateUserForm
<CreateForm
onSuccess={handleSuccess}
onCancel={handleCancel}
defaultValues={defaultValues}

View File

@@ -27,10 +27,7 @@ export default function UsersAdminList({
const {data, isLoading} = useProductQuery(filters)
// const {data, isLoading} = useUsersQuery(filters)
console.log(data?.data);
// console.log(data?.data);
if (isLoading) {
return <DataTableSkeleton columnCount={6} rowCount={initialLimit} />;

View File

@@ -12,7 +12,7 @@ import {
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';
import { AccountPlanModal } from '../inventory-modal';
interface CellActionProps {
data: InventoryTable;

View File

@@ -0,0 +1,168 @@
'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/inventory/hooks/use-mutation";
import { EditInventory, editInventory } from '@/feactures/inventory/schemas/inventory';
interface UpdateFormProps {
onSuccess?: () => void;
onCancel?: () => void;
defaultValues?: Partial<EditInventory>;
}
export function UpdateForm({
onSuccess,
onCancel,
defaultValues,
}: UpdateFormProps) {
const {
mutate: saveAccountingAccounts,
isPending: isSaving,
isError,
} = useUpdateUser();
const defaultformValues = {
id: defaultValues?.id,
title: defaultValues?.title || '',
description: defaultValues?.description || '',
price: defaultValues?.price || '',
stock: defaultValues?.stock || 0,
urlImg: defaultValues?.urlImg || '',
}
// console.log(defaultValues);
const form = useForm<EditInventory>({
resolver: zodResolver(editInventory),
defaultValues: defaultformValues,
mode: 'onChange', // Enable real-time validation
});
const onSubmit = async (data: EditInventory) => {
const formData = data
saveAccountingAccounts(formData, {
onSuccess: () => {
form.reset();
onSuccess?.();
},
onError: () => {
form.setError('root', {
type: 'manual',
message: 'Error al guardar la cuenta contable',
});
},
});
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{form.formState.errors.root && (
<div className="text-destructive text-sm">
{form.formState.errors.root.message}
</div>
)}
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Nombre/Título</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Descripción</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="price"
render={({ field }) => (
<FormItem>
<FormLabel>Precio</FormLabel>
<FormControl>
<Input {...field} value={field.value?.toString() ?? ''}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="stock"
render={({ field }) => (
<FormItem>
<FormLabel>Cantidad/Stock</FormLabel>
<FormControl>
<Input {...field}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="urlImg"
render={({ field }) => (
<FormItem>
<FormLabel>Imagen</FormLabel>
<FormControl>
<Input {...field}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex justify-end gap-4">
<Button variant="outline" type="button" onClick={onCancel}>
Cancelar
</Button>
<Button type="submit" disabled={isSaving}>
{isSaving ? 'Guardando...' : 'Guardar'}
</Button>
</div>
</form>
</Form>
);
}

View File

@@ -1,227 +0,0 @@
'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<UpdateUser>;
}
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<UpdateUser>({
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 {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{form.formState.errors.root && (
<div className="text-destructive text-sm">
{form.formState.errors.root.message}
</div>
)}
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>Usuario</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="fullname"
render={({ field }) => (
<FormItem>
<FormLabel>Nombre completo</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Correo</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>Teléfono</FormLabel>
<FormControl>
<Input {...field} value={field.value?.toString() ?? ''}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='password'
render={({ field }) => (
<FormItem>
<FormLabel>Nueva Contraseña</FormLabel>
<FormControl>
<Input type="password" {...field}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="role"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Rol</FormLabel>
<Select onValueChange={(value) => field.onChange(Number(value))}
// defaultValue={String(field.value)}
>
<FormControl>
<SelectTrigger className="w-full">
<SelectValue placeholder="Selecciona un rol" />
</SelectTrigger>
</FormControl>
<SelectContent className="w-full min-w-[200px]">
{Object.entries(ROLES).map(([value, label]) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="isActive"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Estatus</FormLabel>
<Select defaultValue={String(field.value)} onValueChange={(value) => field.onChange(Boolean(value))}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Seleccione un estatus" />
</SelectTrigger>
<SelectContent>
<SelectItem value="true">Activo</SelectItem>
<SelectItem value="false">Inactivo</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex justify-end gap-4">
<Button variant="outline" type="button" onClick={onCancel}>
Cancelar
</Button>
<Button type="submit" disabled={isSaving}>
{isSaving ? 'Guardando...' : 'Guardar'}
</Button>
</div>
</form>
</Form>
);
}

View File

@@ -1,10 +1,10 @@
'use client';
import { useRouter } from 'next/navigation';
// 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';
import { AccountPlanModal } from './inventory-modal';
export function UsersHeader() {
const [open, setOpen] = useState(false);
@@ -17,7 +17,7 @@ export function UsersHeader() {
description="Gestione aquí los productos que usted registre en la plataforma"
/>
<Button onClick={() => setOpen(true)} size="sm">
<Plus className="mr-2 h-4 w-4" /> Agregar Usuario
<Plus className="mr-2 h-4 w-4" /> Agregar Producto
</Button>
</div>

View File

@@ -1,57 +0,0 @@
'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<AccountPlan>;
}
export function AccountPlanModal({
open,
onOpenChange,
defaultValues,
}: AccountPlanModalProps) {
const handleSuccess = () => {
onOpenChange(false);
};
const handleCancel = () => {
onOpenChange(false);
};
return (
<Dialog
open={open}
onOpenChange={(open) => {
if (!open) {
onOpenChange(false);
}
}}
>
<DialogContent className="sm:max-w-[600px] z-50 backdrop-blur-lg bg-background/80">
<DialogHeader>
<DialogTitle>Actualizar Perfil</DialogTitle>
<DialogDescription>
Complete los campos para actualizar sus datos.<br/>Los campos vacios no seran actualizados.
</DialogDescription>
</DialogHeader>
<ModalForm
onSuccess={handleSuccess}
onCancel={handleCancel}
defaultValues={defaultValues}
/>
</DialogContent>
</Dialog>
);
}

View File

@@ -1,85 +0,0 @@
// '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<object>
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<UpdateUser>({
// resolver: zodResolver(updateUser),
// defaultValues: defaultformValues,
// mode: 'onChange', // Enable real-time validation
// });
return <FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>{label}</FormLabel>
<Select onValueChange={handleChange}
// defaultValue={String(field.value)}
>
<FormControl>
<SelectTrigger className="w-full">
<SelectValue placeholder="Selecciona una opción" />
</SelectTrigger>
</FormControl>
<SelectContent className="w-full min-w-[200px]">
{values.map((item: any) => (
<SelectItem key={item.id} value={item.id}>
{item.name}
</SelectItem>
))}
{/* <SelectItem key={0} value="0">Hola1</SelectItem>
<SelectItem key={1} value="1">Hola2</SelectItem> */}
{/* {Object.entries(values).map(([id, label]) => (
<SelectItem key={id} value={id}>
{label}
</SelectItem>
))} */}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
}

View File

@@ -1,28 +0,0 @@
'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 (
<SurveyResponse survey={survey?.data} />
);
}

View File

@@ -1,268 +0,0 @@
'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<UpdateUser>;
}
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<UpdateUser>({
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 {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{form.formState.errors.root && (
<div className="text-destructive text-sm">
{form.formState.errors.root.message}
</div>
)}
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="fullname"
render={({ field }) => (
<FormItem>
<FormLabel>Nombre completo</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Correo</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>Teléfono</FormLabel>
<FormControl>
<Input {...field} value={field.value?.toString() ?? ''}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="state"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Estado</FormLabel>
<SelectSearchable
options={
stateOptions?.map((item) => ({
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}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="municipality"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Municipio</FormLabel>
<SelectSearchable
options={
municipalityOptions?.map((item) => ({
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}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="parish"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>Parroquia</FormLabel>
<SelectSearchable
options={
parishOptions?.map((item) => ({
value: item.id.toString(),
label: item.name,
})) || []
}
onValueChange={(value : any) =>
field.onChange(Number(value))
}
placeholder="Selecciona una Parroquia"
defaultValue={field.value?.toString()}
disabled={disabledParish}
/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='password'
render={({ field }) => (
<FormItem>
<FormLabel>Nueva Contraseña</FormLabel>
<FormControl>
<Input type="password" {...field}/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="flex justify-end gap-4">
<Button variant="outline" type="button" onClick={onCancel}>
Cancelar
</Button>
<Button type="submit" disabled={isSaving}>
{isSaving ? 'Guardando...' : 'Guardar'}
</Button>
</div>
</form>
</Form>
);
}

View File

@@ -1,75 +0,0 @@
'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 (
<div>
<Button onClick={() => setOpen(true)} size="sm">
<Edit2 className="mr-2 h-4 w-4" /> Editar Perfil
</Button>
<AccountPlanModal open={open} onOpenChange={setOpen} defaultValues={data?.data}/>
<h2 className='mt-3 mb-1'>Datos del usuario</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<section className='border bg-muted p-2 rounded-md'>
<p className='font-bold text-lg'>Usuario:</p>
<p>{data?.data.username || 'Sin Nombre de Usuario'}</p>
</section>
<section className='border bg-muted p-2 rounded-md'>
<p className='font-bold text-lg'>Rol:</p>
<p>{data?.data.role || 'Sin Rol'}</p>
</section>
</div>
<h2 className='mt-3 mb-1'>Información personal</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<section className='border bg-muted p-2 rounded-md'>
<p className='font-bold text-lg'>Nombre completo:</p>
<p>{data?.data.fullname || 'Sin nombre y apellido'}</p>
</section>
<section className='border bg-muted p-2 rounded-md'>
<p className='font-bold text-lg'>Correo:</p>
<p>{data?.data.email || 'Sin correo'}</p>
</section>
<section className='border bg-muted p-2 rounded-md'>
<p className='font-bold text-lg'>Teléfono:</p>
<p>{data?.data.phone || 'Sin teléfono'}</p>
</section>
</div>
<h2 className='mt-3 mb-1'>Información de ubicación</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<section className='border bg-muted p-2 rounded-md'>
<p className='font-bold text-lg'>Estado:</p>
<p>{data?.data.state || 'Sin Estado'}</p>
</section>
<section className='border bg-muted p-2 rounded-md'>
<p className='font-bold text-lg'>Municipio:</p>
<p>{data?.data.municipality || 'Sin Municipio'}</p>
</section>
<section className='border bg-muted p-2 rounded-md'>
<p className='font-bold text-lg'>Parroquia:</p>
<p>{data?.data.parish || 'Sin Parroquia'}</p>
</section>
</div>
</div>
);
}

View File

@@ -1,45 +0,0 @@
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)
})
}

View File

@@ -0,0 +1,35 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { EditInventory } from "../schemas/inventory";
import { updateUserAction, createProductAction } from "../actions/actions";
// Create mutation
export function useCreateUser() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (data: EditInventory) => createProductAction(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['product'] }),
// onError: (e) => console.error('Error:', e),
})
return mutation
}
// Update mutation
export function useUpdateUser() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: (data: EditInventory) => updateUserAction(data),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['product'] }),
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)
// })
// }

View File

@@ -1,12 +0,0 @@
'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())
}

View File

@@ -1,54 +1,31 @@
import { title } from 'process';
import { z } from 'zod';
export type InventoryTable = z.infer<typeof product>;
export type CreateUser = z.infer<typeof createUser>;
export type UpdateUser = z.infer<typeof updateUser>;
export type EditInventory = z.infer<typeof editInventory>;
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({
export const editInventory = 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'],
title: z.string().min(5, { message: "Debe de tener 5 o más caracteres" }),
description: z.string().min(10, { message: "Debe de tener 10 o más caracteres" }),
stock: z.string().transform(val => Number(val)),
price: z.string(),
urlImg: z.string(),
userId: z.number().optional(),
})
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({
export const ApiResponseSchema = z.object({
message: z.string(),
data: z.array(product),
meta: z.object({

View File

@@ -1,83 +0,0 @@
// 'use client';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@repo/shadcn/select';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@repo/shadcn/form';
interface SelectListProps {
label: string
// values:
values: Array<object>
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<UpdateUser>({
// resolver: zodResolver(updateUser),
// defaultValues: defaultformValues,
// mode: 'onChange', // Enable real-time validation
// });
return <FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>{label}</FormLabel>
<Select onValueChange={handleChange}
// defaultValue={String(field.value)}
>
<FormControl>
<SelectTrigger className="w-full">
<SelectValue placeholder="Selecciona una opción" />
</SelectTrigger>
</FormControl>
<SelectContent className="w-full min-w-[200px]">
{values.map((item: any) => (
<SelectItem key={item.id} value={item.id}>
{item.name}
</SelectItem>
))}
{/* <SelectItem key={0} value="0">Hola1</SelectItem>
<SelectItem key={1} value="1">Hola2</SelectItem> */}
{/* {Object.entries(values).map(([id, label]) => (
<SelectItem key={id} value={id}>
{label}
</SelectItem>
))} */}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
}

View File

@@ -1,28 +0,0 @@
'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 (
<SurveyResponse survey={survey?.data} />
);
}