diff --git a/backend/src/users/dto/update-user.dto.ts b/backend/src/users/dto/update-user.dto.ts index e27018a..d68b3be 100644 --- a/backend/src/users/dto/update-user.dto.ts +++ b/backend/src/users/dto/update-user.dto.ts @@ -1,4 +1,4 @@ -import { IsString, IsEnum, IsOptional } from 'class-validator'; +import { IsString, IsEnum, IsOptional, IsBoolean } from 'class-validator'; import { Role } from '@prisma/client'; export class UpdateUserDto { @@ -9,4 +9,8 @@ export class UpdateUserDto { @IsEnum(Role) @IsOptional() role?: Role; + + @IsBoolean() + @IsOptional() + isAlsoDriver?: boolean; } diff --git a/backend/src/users/users.service.ts b/backend/src/users/users.service.ts index d797852..b83beed 100644 --- a/backend/src/users/users.service.ts +++ b/backend/src/users/users.service.ts @@ -35,36 +35,52 @@ export class UsersService { this.logger.log(`Updating user ${id}: ${JSON.stringify(updateUserDto)}`); - // Handle role change and Driver record synchronization - if (updateUserDto.role && updateUserDto.role !== user.role) { - // If changing TO DRIVER role, create a Driver record if one doesn't exist - if (updateUserDto.role === Role.DRIVER && !user.driver) { - this.logger.log( - `Creating Driver record for user ${user.email} (role change to DRIVER)`, - ); - await this.prisma.driver.create({ - data: { - name: user.name || user.email, - phone: user.email, // Use email as placeholder for phone - userId: user.id, - }, - }); - } + const { isAlsoDriver, ...prismaData } = updateUserDto; + const effectiveRole = updateUserDto.role || user.role; - // If changing FROM DRIVER role to something else, remove the Driver record - if (user.role === Role.DRIVER && updateUserDto.role !== Role.DRIVER && user.driver) { - this.logger.log( - `Removing Driver record for user ${user.email} (role change from DRIVER to ${updateUserDto.role})`, - ); - await this.prisma.driver.delete({ - where: { id: user.driver.id }, - }); - } + // Handle role change to DRIVER: auto-create driver record + if (updateUserDto.role === Role.DRIVER && !user.driver) { + this.logger.log( + `Creating Driver record for user ${user.email} (role change to DRIVER)`, + ); + await this.prisma.driver.create({ + data: { + name: user.name || user.email, + phone: user.email, + userId: user.id, + }, + }); + } + + // When promoting FROM DRIVER to Admin/Coordinator, keep the driver record + // (admin can explicitly uncheck the driver box later if they want) + + // Handle "Also a Driver" toggle (independent of role) + if (isAlsoDriver === true && !user.driver) { + this.logger.log( + `Creating Driver record for user ${user.email} (isAlsoDriver toggled on)`, + ); + await this.prisma.driver.create({ + data: { + name: user.name || user.email, + phone: user.email, + userId: user.id, + }, + }); + } else if (isAlsoDriver === false && user.driver && effectiveRole !== Role.DRIVER) { + // Only allow removing driver record if user is NOT in the DRIVER role + this.logger.log( + `Soft-deleting Driver record for user ${user.email} (isAlsoDriver toggled off)`, + ); + await this.prisma.driver.update({ + where: { id: user.driver.id }, + data: { deletedAt: new Date() }, + }); } return this.prisma.user.update({ where: { id: user.id }, - data: updateUserDto, + data: prismaData, include: { driver: true }, }); } diff --git a/frontend/src/pages/UserList.tsx b/frontend/src/pages/UserList.tsx index 5ab39a4..e0012e6 100644 --- a/frontend/src/pages/UserList.tsx +++ b/frontend/src/pages/UserList.tsx @@ -1,7 +1,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import toast from 'react-hot-toast'; import { api } from '@/lib/api'; -import { Check, X, UserCheck, UserX, Shield, Trash2 } from 'lucide-react'; +import { Check, X, UserCheck, UserX, Shield, Trash2, Car } from 'lucide-react'; import { useState } from 'react'; import { Loading } from '@/components/Loading'; @@ -12,6 +12,10 @@ interface User { role: string; isApproved: boolean; createdAt: string; + driver?: { + id: string; + deletedAt: string | null; + } | null; } export function UserList() { @@ -72,6 +76,21 @@ export function UserList() { }, }); + const toggleDriverMutation = useMutation({ + mutationFn: async ({ userId, isAlsoDriver }: { userId: string; isAlsoDriver: boolean }) => { + await api.patch(`/users/${userId}`, { isAlsoDriver }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['users'] }); + queryClient.invalidateQueries({ queryKey: ['drivers'] }); + toast.success('Driver status updated'); + }, + onError: (error: any) => { + console.error('[USERS] Failed to toggle driver status:', error); + toast.error(error.response?.data?.message || 'Failed to update driver status'); + }, + }); + const handleRoleChange = (userId: string, newRole: string) => { if (confirm(`Change user role to ${newRole}?`)) { changeRoleMutation.mutate({ userId, role: newRole }); @@ -201,6 +220,9 @@ export function UserList() { Role + + Driver + Status @@ -210,7 +232,11 @@ export function UserList() { - {approvedUsers.map((user) => ( + {approvedUsers.map((user) => { + const hasDriver = !!(user.driver && !user.driver.deletedAt); + const isDriverRole = user.role === 'DRIVER'; + + return ( {user.name || 'Unknown User'} @@ -232,6 +258,24 @@ export function UserList() { {user.role} + + + Active @@ -257,7 +301,8 @@ export function UserList() { - ))} + ); + })}