diff --git a/src/components/dashboard/Dashboard.tsx b/src/components/dashboard/Dashboard.tsx
index 5d52f5e..535e3bc 100644
--- a/src/components/dashboard/Dashboard.tsx
+++ b/src/components/dashboard/Dashboard.tsx
@@ -25,7 +25,7 @@ export function Dashboard() {
{isViewingTeammate
- ? `Viewing ${selectedTeammate.displayName}'s timesheet`
+ ? `Viewing ${selectedTeammate.name || selectedTeammate.displayName || selectedTeammate.number}'s timesheet`
: 'Track your time and sync to Business Central'}
diff --git a/src/components/timesheet/TeammateSelector.tsx b/src/components/timesheet/TeammateSelector.tsx
index a88549f..97d68e7 100644
--- a/src/components/timesheet/TeammateSelector.tsx
+++ b/src/components/timesheet/TeammateSelector.tsx
@@ -11,7 +11,18 @@ import {
import { useTeammateStore, useCompanyStore } from '@/hooks';
import { useAuth } from '@/services/auth';
import { cn } from '@/utils';
-import type { BCEmployee } from '@/types';
+import { bcClient } from '@/services/bc';
+import type { BCResource } from '@/types';
+
+// Resources expose `name` (and sometimes `displayName`); pick the best label.
+function teammateLabel(t: BCResource): string {
+ return t.name || t.displayName || t.number;
+}
+
+function teammateInitial(t: BCResource): string {
+ const label = teammateLabel(t);
+ return label?.[0] || '?';
+}
export function TeammateSelector() {
const [isOpen, setIsOpen] = useState(false);
@@ -52,31 +63,32 @@ export function TeammateSelector() {
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isOpen]);
- const handleSelect = (teammate: BCEmployee | null) => {
+ const handleSelect = (teammate: BCResource | null) => {
selectTeammate(teammate);
setIsOpen(false);
setSearchQuery('');
};
- // Filter teammates by search query, excluding current user
+ // Filter teammates by search query
const filteredTeammates = teammates.filter((teammate) => {
- // Filter by search
if (searchQuery) {
const query = searchQuery.toLowerCase();
- const matchesName = teammate.displayName.toLowerCase().includes(query);
- const matchesEmail = teammate.email?.toLowerCase().includes(query);
- if (!matchesName && !matchesEmail) return false;
+ const matchesName = teammateLabel(teammate).toLowerCase().includes(query);
+ const matchesUserId = teammate.timeSheetOwnerUserId?.toLowerCase().includes(query);
+ const matchesNumber = teammate.number.toLowerCase().includes(query);
+ if (!matchesName && !matchesUserId && !matchesNumber) return false;
}
return true;
});
- // Separate current user from other teammates
- const currentUserEmail = account?.username?.toLowerCase();
+ // Identify the current user's resource by deriving the BC User ID from their UPN
+ // (matches the convention used in bcClient.deriveBCUserId).
+ const currentUserBCId = account?.username ? bcClient.deriveBCUserId(account.username) : undefined;
const currentUserTeammate = filteredTeammates.find(
- (t) => t.email?.toLowerCase() === currentUserEmail
+ (t) => t.timeSheetOwnerUserId?.toUpperCase() === currentUserBCId
);
const otherTeammates = filteredTeammates.filter(
- (t) => t.email?.toLowerCase() !== currentUserEmail
+ (t) => t.timeSheetOwnerUserId?.toUpperCase() !== currentUserBCId
);
// Don't show if no teammates available (only self)
@@ -84,7 +96,7 @@ export function TeammateSelector() {
return null;
}
- const displayName = selectedTeammate ? selectedTeammate.displayName : 'My Timesheet';
+ const displayName = selectedTeammate ? teammateLabel(selectedTeammate) : 'My Timesheet';
return (
@@ -160,7 +172,7 @@ export function TeammateSelector() {
My Timesheet
{currentUserTeammate && (
- ({currentUserTeammate.displayName})
+ ({teammateLabel(currentUserTeammate)})
)}
@@ -198,19 +210,11 @@ export function TeammateSelector() {
aria-selected={isSelected}
>
- {teammate.givenName?.[0] ||
- teammate.surname?.[0] ||
- teammate.displayName?.[0] ||
- '?'}
- {teammate.givenName?.[0] && teammate.surname?.[0]
- ? teammate.surname[0]
- : ''}
+ {teammateInitial(teammate)}
Read-only
@@ -623,7 +623,11 @@ Thank you!`)}`}
{isViewingTeammate ? (
<>
- No time entries found for {selectedTeammate.displayName} this week.
+ No time entries found for{' '}
+ {selectedTeammate.name ||
+ selectedTeammate.displayName ||
+ selectedTeammate.number}{' '}
+ this week.
Time entries will appear here once added to their timesheet.
diff --git a/src/hooks/useTeammateStore.ts b/src/hooks/useTeammateStore.ts
index 9472edc..a127443 100644
--- a/src/hooks/useTeammateStore.ts
+++ b/src/hooks/useTeammateStore.ts
@@ -1,15 +1,15 @@
import { create } from 'zustand';
-import type { BCEmployee } from '@/types';
+import type { BCResource } from '@/types';
import { bcClient } from '@/services/bc/bcClient';
interface TeammateStore {
- teammates: BCEmployee[];
- selectedTeammate: BCEmployee | null;
+ teammates: BCResource[];
+ selectedTeammate: BCResource | null;
isLoading: boolean;
error: string | null;
fetchTeammates: () => Promise;
- selectTeammate: (teammate: BCEmployee | null) => void;
+ selectTeammate: (teammate: BCResource | null) => void;
clearSelection: () => void;
isViewingTeammate: () => boolean;
}
@@ -23,15 +23,19 @@ export const useTeammateStore = create((set, get) => ({
fetchTeammates: async () => {
set({ isLoading: true, error: null });
try {
- const employees = await bcClient.getEmployees("status eq 'Active'");
- set({ teammates: employees, isLoading: false });
+ // Resources are the entities that own timesheets, so look them up directly
+ // (employees -> resources mapping is unreliable in BC).
+ const resources = await bcClient.getResources();
+ // Only show resources that actually use timesheets — others have nothing to display.
+ const withTimesheet = resources.filter((r) => r.useTimeSheet);
+ set({ teammates: withTimesheet, isLoading: false });
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to fetch teammates';
set({ error: message, isLoading: false, teammates: [] });
}
},
- selectTeammate: (teammate: BCEmployee | null) => {
+ selectTeammate: (teammate: BCResource | null) => {
set({ selectedTeammate: teammate });
},
diff --git a/src/hooks/useTimeEntriesStore.ts b/src/hooks/useTimeEntriesStore.ts
index daf16d5..bb0dff8 100644
--- a/src/hooks/useTimeEntriesStore.ts
+++ b/src/hooks/useTimeEntriesStore.ts
@@ -1,5 +1,5 @@
import { create } from 'zustand';
-import type { TimeEntry, WeekData, BCEmployee, BCTimeSheet, TimesheetDisplayStatus } from '@/types';
+import type { TimeEntry, WeekData, BCResource, BCTimeSheet, TimesheetDisplayStatus } from '@/types';
import {
timeEntryService,
NoResourceError,
@@ -26,7 +26,7 @@ interface TimeEntriesStore {
// Entry operations
fetchWeekEntries: (userId: string, weekStart?: Date) => Promise;
- fetchTeammateEntries: (teammate: BCEmployee, weekStart?: Date) => Promise;
+ fetchTeammateEntries: (teammate: BCResource, weekStart?: Date) => Promise;
addEntry: (
entry: Omit<
TimeEntry,
@@ -136,7 +136,7 @@ export const useTimeEntriesStore = create((set, get) => ({
}
},
- fetchTeammateEntries: async (teammate: BCEmployee, weekStart?: Date) => {
+ fetchTeammateEntries: async (teammate: BCResource, weekStart?: Date) => {
const week = weekStart || get().currentWeekStart;
set({ isLoading: true, error: null, currentWeekStart: week });
diff --git a/src/services/bc/timeEntryService.ts b/src/services/bc/timeEntryService.ts
index 480ddaa..f52e26d 100644
--- a/src/services/bc/timeEntryService.ts
+++ b/src/services/bc/timeEntryService.ts
@@ -5,7 +5,7 @@ import type {
BCTimeSheet,
BCTimeSheetLine,
BCTimeSheetDetail,
- BCEmployee,
+ BCResource,
} from '@/types';
import { format, startOfWeek } from 'date-fns';
@@ -578,17 +578,12 @@ export const timeEntryService = {
/**
* Get entries for a teammate from Business Central.
+ * The teammate is a BC Resource — we already have its number, so we can fetch
+ * the timesheet directly without an email→resource lookup.
*/
- async getTeammateEntries(weekStart: Date, teammate: BCEmployee): Promise {
+ async getTeammateEntries(weekStart: Date, teammate: BCResource): Promise {
try {
- // Get resource by employee email
- const resource = teammate.email ? await bcClient.getResourceByEmail(teammate.email) : null;
-
- if (!resource) {
- return [];
- }
-
- const timesheet = await this.getTimesheet(resource.number, weekStart);
+ const timesheet = await this.getTimesheet(teammate.number, weekStart);
const [lines, details] = await Promise.all([
bcClient.getTimeSheetLines(timesheet.number),
bcClient.getAllTimeSheetDetails(timesheet.number),
@@ -596,13 +591,15 @@ export const timeEntryService = {
return bcDataToTimeEntries(lines, details, timesheet, teammate.id);
} catch (error) {
- // Log error for debugging but don't expose to user
- // This can fail for various reasons: no timesheet, no resource, network issues
+ // Common case: teammate has no timesheet for this week — return empty.
+ if (error instanceof NoTimesheetError) {
+ return [];
+ }
if (process.env.NODE_ENV === 'development') {
console.error('Failed to get teammate entries:', {
weekStart,
teammateId: teammate.id,
- teammateEmail: teammate.email,
+ teammateNumber: teammate.number,
error,
});
}