Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,7 @@ build/
docs/

# session code
private/
private/

#prisma
/generated/prisma
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22.15.0
4 changes: 3 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
"main": "index.js",
"dependencies": {
"@carriage-web/shared": "workspace:*",
"@tailwindcss/vite": "^4.2.2"
"react-scripts": "^5.0.1"
},
"scripts": {
"dev": "vite",
"start": "vite",
"build": "vite build",
"preview": "vite preview",
"type-check": "tsc --project tsconfig.json --pretty --noEmit"
Expand All @@ -34,6 +35,7 @@
"@mui/utils": "^7.3.6",
"@mui/x-date-pickers": "^7.23.1",
"@react-aria/utils": "^3.25.2",
"@tailwindcss/vite": "^4.2.2",
"@types/crypto-js": "^4.2.2",
"@types/google.maps": "^3.58.1",
"@types/node": "^22.5.5",
Expand Down
30 changes: 7 additions & 23 deletions frontend/src/components/EmployeeCards/EmployeeCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { useNavigate } from 'react-router-dom';
import Card, { CardInfo } from '../Card/Card';
import styles from './employeecards.module.css';
import { phone, wheel, user } from '../../icons/userInfo/index';
import { AdminType } from '@carriage-web/shared/types/admin';
import { DriverType } from '@carriage-web/shared/types/driver';
import { EmployeeType } from '@carriage-web/shared/types/employee';

const formatPhone = (phoneNumber: string | undefined) => {
if (phoneNumber !== undefined) {
Expand All @@ -17,34 +16,19 @@ const formatPhone = (phoneNumber: string | undefined) => {
}
};

type Employee = AdminType | DriverType;

function isAdmin(employee: Employee): employee is AdminType {
return 'isDriver' in employee;
}

function isDriver(employee: Employee): employee is DriverType {
return 'availability' in employee && !('isDriver' in employee);
}

type EmployeeCardProps = {
id: string;
employee: Employee;
employee: EmployeeType;
};

const EmployeeCard = ({ id, employee }: EmployeeCardProps) => {
const navigate = useNavigate();
const netId = employee.email.split('@')[0];
const fmtPhone = formatPhone(employee.phoneNumber);

// Determine if employee is admin, driver, or both
const adminEmployee = isAdmin(employee);
const driverEmployee = isDriver(employee);
const isBoth = adminEmployee && employee.isDriver;

const roles = (): string => {
if (isBoth) return 'Admin • Driver';
if (adminEmployee) return 'Admin';
if (employee.isAdmin && employee.isDriver) return 'Admin • Driver';
if (employee.isAdmin) return 'Admin';
return 'Driver';
};

Expand All @@ -68,8 +52,8 @@ const EmployeeCard = ({ id, employee }: EmployeeCardProps) => {
<p>{fmtPhone}</p>
</CardInfo>
<CardInfo
icon={adminEmployee ? user : wheel}
alt={adminEmployee ? 'admin' : 'wheel'}
icon={employee.isAdmin ? user : wheel}
alt={employee.isAdmin ? 'admin' : 'wheel'}
>
<p>{roles()}</p>
</CardInfo>
Expand All @@ -79,7 +63,7 @@ const EmployeeCard = ({ id, employee }: EmployeeCardProps) => {
};

type EmployeeCardsProps = {
employees: Employee[];
employees: EmployeeType[];
};

const EmployeeCards = ({ employees }: EmployeeCardsProps) => {
Expand Down
54 changes: 30 additions & 24 deletions frontend/src/components/EmployeeModal/EmployeeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import { useEmployees } from '../../context/EmployeesContext';
import { useToast, ToastStatus } from '../../context/toastContext';
import axios from '../../util/axios';
import { extractNetIdFromEmail } from 'util/userUtils';
import { EmployeeType } from '@carriage-web/shared/types/employee';

type AdminData = {
type: string[];
adminRoles: string[];
isDriver: boolean;
};

Expand All @@ -37,23 +38,26 @@ type EmployeeEntity = {
photoLink?: string;
};

// both for formating to current api data expections
function extractAdminData(employeeData: EmployeeEntity) {
// both for formatting to current api data expectations
function extractAdminData(
employeeData: EmployeeEntity
): Omit<EmployeeType, 'id'> {
return {
firstName: employeeData.firstName,
lastName: employeeData.lastName,
type: (employeeData.admin?.type || []) as (
| 'sds-admin'
| 'redrunner-admin'
)[],
adminRoles: employeeData.admin?.adminRoles || [],
isAdmin: true,
isDriver: employeeData.admin?.isDriver || false,
phoneNumber: employeeData.phoneNumber,
email: employeeData.email,
photoLink: employeeData.photoLink,
availability: employeeData.driver?.availability || [],
};
}

function extractDriverData(employeeData: EmployeeEntity) {
function extractDriverData(
employeeData: EmployeeEntity
): Partial<EmployeeType> {
return {
firstName: employeeData.firstName,
lastName: employeeData.lastName,
Expand All @@ -62,6 +66,7 @@ function extractDriverData(employeeData: EmployeeEntity) {
joinDate: employeeData.driver?.startDate,
email: employeeData.email,
photoLink: employeeData.photoLink,
isDriver: true,
};
}

Expand Down Expand Up @@ -93,13 +98,11 @@ const EmployeeModal = ({
// Initialize form and roles when modal opens or existing employee changes
React.useEffect(() => {
if (existingEmployee && isOpen) {
// Initialize roles
// Initialize roles, normalizing Prisma enum values (SDS_ADMIN → sds-admin)
const normalizeRole = (r: string) => r.toLowerCase().replace(/_/g, '-');
const roles: string[] = [];
if (existingEmployee.admin) {
// Add admin roles
if (existingEmployee.admin.type) {
roles.push(...existingEmployee.admin.type);
}
if (existingEmployee.admin?.adminRoles) {
roles.push(...existingEmployee.admin.adminRoles.map(normalizeRole));
}
if (existingEmployee.driver) {
roles.push('driver');
Expand Down Expand Up @@ -131,8 +134,9 @@ const EmployeeModal = ({

const closeModal = () => {
methods.clearErrors();
setImageBase64(''); // Reset image state
setIsUploadingImage(false); // Reset upload state
setImageBase64('');
setIsUploadingImage(false);
setSelectedRole([]);
setIsOpen(false);
};

Expand Down Expand Up @@ -178,7 +182,9 @@ const EmployeeModal = ({
switch (endpoint) {
case '/api/drivers':
// Use optimistic create from context
await createDriver(extractDriverData(employeeData));
await createDriver(
extractDriverData(employeeData) as Omit<EmployeeType, 'id'>
);
res = employeeData; // The context will handle server response and ID assignment
break;
case '/api/admins':
Expand Down Expand Up @@ -330,7 +336,7 @@ const EmployeeModal = ({

if (hasAdmin) {
admin_data = {
type: selectedRoles.filter((role) => role !== 'driver'),
adminRoles: selectedRoles.filter((role) => role !== 'driver'),
isDriver: hasDriver,
};
}
Expand Down Expand Up @@ -436,8 +442,13 @@ const EmployeeModal = ({
phone={existingEmployee?.phoneNumber}
/>

<RoleSelector
selectedRoles={selectedRoles}
setSelectedRoles={setSelectedRole}
/>

{(selectedRoles.includes('driver') ||
existingEmployee?.driver?.availability) && (
existingEmployee?.driver != null) && (
<>
<StartDate existingDate={existingEmployee?.driver?.startDate} />
<WorkingHours
Expand All @@ -447,11 +458,6 @@ const EmployeeModal = ({
</>
)}

<RoleSelector
selectedRoles={selectedRoles}
setSelectedRoles={setSelectedRole}
/>

<Button className={styles.submit} type="submit">
{existingEmployee ? 'Save' : 'Add'}
</Button>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/ResponsiveRideCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ const ResponsiveRideCard: FC<ResponsiveRideCardProps> = ({
<p className={styles.labelText}>Driver</p>
</span>
<p>
{ride.driver !== undefined
{ride.driver?.firstName
? `${ride.driver.firstName} ${ride.driver.lastName}`
: 'Not Assigned'}
</p>
Expand Down Expand Up @@ -202,7 +202,7 @@ const ResponsiveRideCard: FC<ResponsiveRideCardProps> = ({
defaultZoom={13}
gestureHandling="greedy"
disableDefaultUI
mapId={process.env.VITE_GOOGLE_MAPS_MAP_ID}
mapId={import.meta.env.VITE_GOOGLE_MAPS_MAP_ID}
>
<AdvancedMarker
position={{
Expand Down
Loading
Loading