diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts index 435c5c1..71ab30e 100644 --- a/apps/web/next.config.ts +++ b/apps/web/next.config.ts @@ -59,12 +59,16 @@ const nextConfig: NextConfig = { }, }; } + const commonDist = path.resolve(__dirname, '../../packages/common/dist'); config.resolve.alias = { ...config.resolve.alias, '@': path.resolve(__dirname, 'src'), '@worksight/assets': path.resolve(__dirname, '../../packages/assets/dist'), - '@worksight/common': path.resolve(__dirname, '../../packages/common/dist') - } + '@worksight/common/data': path.resolve(commonDist, 'data'), + '@worksight/common/types': path.resolve(commonDist, 'types'), + '@worksight/common/utils': path.resolve(commonDist, 'utils'), + '@worksight/common': commonDist, + }; return config; }, diff --git a/apps/web/src/app/admin/surveys/page.tsx b/apps/web/src/app/admin/surveys/page.tsx index 7575478..9a2a728 100644 --- a/apps/web/src/app/admin/surveys/page.tsx +++ b/apps/web/src/app/admin/surveys/page.tsx @@ -41,103 +41,23 @@ import { } from 'lucide-react'; import Link from 'next/link'; import { useEffect, useMemo, useState } from 'react'; - -interface Survey { - id: string; - title: string; - description: string; - status: 'draft' | 'active' | 'paused' | 'completed'; - questionCount: number; - responseCount: number; - createdAt: string; - lastModified: string; - createdBy: string; - category: 'burnout' | 'satisfaction' | 'wellness' | 'feedback'; - targetAudience: 'all' | 'managers' | 'employees' | 'specific'; -} - -const mockSurveys: Survey[] = [ - { - id: '1', - title: 'Burnout Assessment 2025', - description: 'Comprehensive burnout evaluation for all employees', - status: 'active', - questionCount: 15, - responseCount: 247, - createdAt: '2025-01-15', - lastModified: '2025-01-20', - createdBy: 'Admin User', - category: 'burnout', - targetAudience: 'all', - }, - { - id: '2', - title: 'Job Satisfaction Survey', - description: 'Quarterly job satisfaction and engagement survey', - status: 'active', - questionCount: 12, - responseCount: 156, - createdAt: '2025-01-10', - lastModified: '2025-01-18', - createdBy: 'HR Manager', - category: 'satisfaction', - targetAudience: 'employees', - }, - { - id: '3', - title: 'Manager Feedback Survey', - description: 'Leadership effectiveness and team dynamics assessment', - status: 'draft', - questionCount: 8, - responseCount: 0, - createdAt: '2025-01-22', - lastModified: '2025-01-22', - createdBy: 'Admin User', - category: 'feedback', - targetAudience: 'managers', - }, - { - id: '4', - title: 'Wellness Check Q4 2024', - description: 'Mental health and wellness assessment', - status: 'completed', - questionCount: 10, - responseCount: 312, - createdAt: '2024-10-01', - lastModified: '2024-12-31', - createdBy: 'Wellness Team', - category: 'wellness', - targetAudience: 'all', - }, - { - id: '5', - title: 'Remote Work Experience', - description: 'Evaluation of remote work setup and productivity', - status: 'paused', - questionCount: 14, - responseCount: 89, - createdAt: '2025-01-05', - lastModified: '2025-01-19', - createdBy: 'Operations Lead', - category: 'feedback', - targetAudience: 'all', - }, -]; +import { getMvpSurveys, type MvpSurvey } from '@/lib/mvp-data'; function SurveyManagementContent() { const { logout } = useAuth(); - const [surveys, setSurveys] = useState([]); + const [surveys, setSurveys] = useState([]); const [searchTerm, setSearchTerm] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [categoryFilter, setCategoryFilter] = useState('all'); const [isLoading, setIsLoading] = useState(true); useEffect(() => { - // Simulate API call - setTimeout(() => { - setSurveys(mockSurveys); - setIsLoading(false); - }, 1000); + const fixtureSurveys = getMvpSurveys(); + if (fixtureSurveys.length === 0) { + throw new Error('Common survey fixtures empty; refusing silent empty fallback'); + } + setSurveys(fixtureSurveys); + setIsLoading(false); }, []); const handleLogout = async () => { diff --git a/apps/web/src/app/admin/users/page.tsx b/apps/web/src/app/admin/users/page.tsx index 453602b..bd36f97 100644 --- a/apps/web/src/app/admin/users/page.tsx +++ b/apps/web/src/app/admin/users/page.tsx @@ -2,7 +2,6 @@ import { useAuth } from '@/auth'; import { getRoleColor, getUserRoleDisplay } from '@/auth/admin'; -import { UserRole } from '@/auth/types'; import { AdminRoute } from '@/components/admin'; import { SessionTimer } from '@/components/features'; import { AppSidebar } from '@/components/main/sidebar'; @@ -35,6 +34,7 @@ import { validateUserArray, validateUserFilters, } from '@/schemas/user'; +import { getUsersWithMetrics } from '@/lib/mvp-data'; import { AlertTriangle, CheckCircle, @@ -49,74 +49,6 @@ import { } from 'lucide-react'; import { useEffect, useMemo, useState } from 'react'; -const mockUsers: UserWithMetrics[] = [ - { - id: '1', - name: 'John Doe', - email: 'john.doe@company.com', - role: UserRole.EMPLOYEE, - department: 'Engineering', - team: 'Frontend', - burnoutScore: 3.2, - lastActive: '2 hours ago', - surveyCompleted: true, - riskLevel: 'low', - tasksCompleted: 24, - }, - { - id: '2', - name: 'Jane Smith', - email: 'jane.smith@company.com', - role: UserRole.TEAM_LEAD, - department: 'Engineering', - team: 'Backend', - burnoutScore: 7.8, - lastActive: '30 minutes ago', - surveyCompleted: true, - riskLevel: 'high', - tasksCompleted: 31, - }, - { - id: '3', - name: 'Bob Wilson', - email: 'bob.wilson@company.com', - role: UserRole.MANAGER, - department: 'Product', - team: 'Design', - burnoutScore: 5.4, - lastActive: '1 day ago', - surveyCompleted: false, - riskLevel: 'medium', - tasksCompleted: 18, - }, - { - id: '4', - name: 'Alice Johnson', - email: 'alice.johnson@company.com', - role: UserRole.EMPLOYEE, - department: 'Marketing', - team: 'Content', - burnoutScore: 2.1, - lastActive: '5 minutes ago', - surveyCompleted: true, - riskLevel: 'low', - tasksCompleted: 42, - }, - { - id: '5', - name: 'Charlie Brown', - email: 'charlie.brown@company.com', - role: UserRole.ADMIN, - department: 'IT', - team: 'DevOps', - burnoutScore: 6.7, - lastActive: '1 hour ago', - surveyCompleted: true, - riskLevel: 'medium', - tasksCompleted: 15, - }, -]; - function UserManagementContent() { const { logout } = useAuth(); const [users, setUsers] = useState([]); @@ -127,23 +59,24 @@ function UserManagementContent() { const [validationErrors, setValidationErrors] = useState([]); useEffect(() => { - // Simulate API call with validation - setTimeout(() => { - // Validate mock data using Zod - const validationResult = validateUserArray(mockUsers); + // Load employees from @worksight/common fixtures + const fixtureUsers = getUsersWithMetrics(); + const validationResult = validateUserArray(fixtureUsers); + + if (!validationResult.allValid) { + const errors = validationResult.invalid.map( + (item) => + `User at index ${item.index}: ${item.errors?.map((e: { message: string }) => e.message).join(', ')}` + ); + setValidationErrors(errors); + } - if (!validationResult.allValid) { - const errors = validationResult.invalid.map( - (item) => - `User at index ${item.index}: ${item.errors?.map((e: { message: string }) => e.message).join(', ')}` - ); - setValidationErrors(errors); - } + if (validationResult.valid.length === 0 && fixtureUsers.length > 0) { + throw new Error('Common employee fixtures failed validation; refusing empty fallback'); + } - // Use only valid users - setUsers(validationResult.valid.map((item) => item.data!)); - setIsLoading(false); - }, 1000); + setUsers(validationResult.valid.map((item) => item.data!)); + setIsLoading(false); }, []); // Validate filters when they change diff --git a/apps/web/src/app/dashboard/tasks/page.tsx b/apps/web/src/app/dashboard/tasks/page.tsx index e4197a8..6e34b05 100644 --- a/apps/web/src/app/dashboard/tasks/page.tsx +++ b/apps/web/src/app/dashboard/tasks/page.tsx @@ -55,6 +55,8 @@ import { } from 'lucide-react'; import { useCallback, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; +import { assignmentLookup } from '@/lib/mvp-data'; +import type { Assignment } from '@worksight/common/types'; interface Task { id: string; @@ -68,63 +70,33 @@ interface Task { type ViewMode = 'kanban' | 'table'; -// Mock tasks data - in real app this would come from your backend -const initialTasks: Task[] = [ - { - id: '1', - title: 'Implement survey results storage', - description: 'Create Zustand store for survey results with persistence', - status: 'completed', - priority: 'high', - dueDate: '2025-08-24', - storyPoints: 8, - }, - { - id: '2', - title: 'Fix ESLint warnings', - description: 'Resolve remaining TypeScript and linting issues', - status: 'in-progress', - priority: 'medium', - dueDate: '2025-08-25', - storyPoints: 3, - }, - { - id: '3', - title: 'Dashboard navigation improvements', - description: 'Implement client-side routing for dashboard sections', - status: 'in-progress', - priority: 'high', - dueDate: '2025-08-26', - storyPoints: 5, - }, - { - id: '4', - title: 'Mobile responsive design', - description: 'Ensure dashboard works well on mobile devices', - status: 'pending', - priority: 'medium', - dueDate: '2025-08-28', - storyPoints: 13, - }, - { - id: '5', - title: 'User authentication improvements', - description: 'Add password reset and email verification', - status: 'pending', - priority: 'low', - dueDate: '2025-08-30', - storyPoints: 8, - }, - { - id: '6', - title: 'Add task drag and drop', - description: 'Implement kanban board with draggable tasks', - status: 'in-progress', - priority: 'high', - dueDate: '2025-08-25', - storyPoints: 8, - }, -]; +function mapDashboardStatus(status: Assignment['status']): Task['status'] { + if (status === 'completed') return 'completed'; + if (status === 'in_progress') return 'in-progress'; + return 'pending'; +} + +function mapDashboardPriority(priority: Assignment['priority']): Task['priority'] { + if (priority === 'critical' || priority === 'high') return 'high'; + if (priority === 'medium') return 'medium'; + return 'low'; +} + +function getInitialTasksFromCommon(): Task[] { + const assignments = assignmentLookup.all(); + if (assignments.length === 0) { + throw new Error('Common assignment fixtures empty; refusing silent empty fallback'); + } + return assignments.map((assignment) => ({ + id: assignment.id, + title: assignment.title ?? assignment.external_id ?? 'Untitled task', + description: [assignment.epic, assignment.sprint, assignment.type].filter(Boolean).join(' · '), + status: mapDashboardStatus(assignment.status), + priority: mapDashboardPriority(assignment.priority), + dueDate: assignment.updated_at.toISOString().slice(0, 10), + storyPoints: assignment.points ?? 1, + })); +} const statusConfig = { pending: { icon: Clock, color: 'text-orange-500', bg: 'bg-orange-50', label: 'Pending' }, @@ -147,7 +119,7 @@ const statusOrder: Task['status'][] = ['pending', 'in-progress', 'completed']; export default function TasksPage() { const [viewMode, setViewMode] = useState('kanban'); - const [tasks, setTasks] = useState(initialTasks); + const [tasks, setTasks] = useState(() => getInitialTasksFromCommon()); const [activeTask, setActiveTask] = useState(null); const [showNewTaskDialog, setShowNewTaskDialog] = useState(false); const [editingTaskId, setEditingTaskId] = useState(null); diff --git a/apps/web/src/app/tasks/page.tsx b/apps/web/src/app/tasks/page.tsx index c870fb4..680f24e 100644 --- a/apps/web/src/app/tasks/page.tsx +++ b/apps/web/src/app/tasks/page.tsx @@ -45,60 +45,9 @@ import { import { CSS } from '@dnd-kit/utilities'; import { AlertCircle, CheckSquare, Clock, GripVertical, LogOut, Plus, Search } from 'lucide-react'; import { useCallback, useMemo, useState } from 'react'; +import { getMvpTasks, type MvpTask } from '@/lib/mvp-data'; -interface Task { - id: string; - title: string; - description: string; - status: 'todo' | 'in-progress' | 'completed'; - priority: 'low' | 'medium' | 'high'; - dueDate: string; - estimatedHours: number; - order: number; -} - -const mockTasks: Task[] = [ - { - id: '1', - title: 'Implement user authentication', - description: 'Set up Supabase auth with OAuth providers and offline fallback', - status: 'completed', - priority: 'high', - dueDate: '2025-08-20', - estimatedHours: 8, - order: 0, - }, - { - id: '2', - title: 'Design task management interface', - description: 'Create a clean, intuitive interface for managing work tasks', - status: 'in-progress', - priority: 'medium', - dueDate: '2025-08-21', - estimatedHours: 6, - order: 1, - }, - { - id: '3', - title: 'Add burnout tracking metrics', - description: 'Implement features to track and analyze work burnout patterns', - status: 'todo', - priority: 'high', - dueDate: '2025-08-23', - estimatedHours: 12, - order: 2, - }, - { - id: '4', - title: 'Setup CI/CD pipeline', - description: 'Configure automated testing and deployment workflows', - status: 'todo', - priority: 'low', - dueDate: '2025-08-25', - estimatedHours: 4, - order: 3, - }, -]; +type Task = MvpTask; interface SortableTaskProps { task: Task; @@ -260,7 +209,13 @@ function SortableTask({ task, onTaskUpdate }: SortableTaskProps) { function TasksContent() { const { user, logout } = useAuth(); const [searchTerm, setSearchTerm] = useState(''); - const [tasks, setTasks] = useState(mockTasks.sort((a, b) => a.order - b.order)); + const [tasks, setTasks] = useState(() => { + const fixtureTasks = getMvpTasks(); + if (fixtureTasks.length === 0) { + throw new Error('Common assignment fixtures empty; refusing silent empty fallback'); + } + return [...fixtureTasks].sort((a, b) => a.order - b.order); + }); const sensors = useSensors( useSensor(PointerSensor), diff --git a/apps/web/src/components/dashboard/stats.tsx b/apps/web/src/components/dashboard/stats.tsx index 2956dc3..d3bd1a3 100644 --- a/apps/web/src/components/dashboard/stats.tsx +++ b/apps/web/src/components/dashboard/stats.tsx @@ -5,14 +5,15 @@ import { isOfflineMode } from '@/auth/offline'; import { SurveyResultsCard } from '@/components/dashboard/survey-results-card'; import { Badge } from '@/components/ui/badge'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; -import { Employees } from '@/data/employees'; import { + findEmployeeByEmail, getAfterHoursActivities, getDataSourceUsageStats, + getEmployeeCount, getEmployeeProductivityStats, getTeamMetaStats, getWeekendActivities, -} from '@/data/work-tracking'; +} from '@/lib/mvp-data'; import { useEffect, useState } from 'react'; export function DashboardStats() { @@ -24,12 +25,10 @@ export function DashboardStats() { setIsOffline(isOfflineMode()); }, []); - // Get current user's stats if they're an employee - const currentEmployee = user - ? Object.entries(Employees).find(([_, emp]) => emp.email === user.email) - : null; + // Get current user's stats if they're an employee (common fixtures) + const currentEmployee = user?.email ? findEmployeeByEmail(user.email) : null; const currentEmployeeStats = currentEmployee - ? getEmployeeProductivityStats(currentEmployee[0]) + ? getEmployeeProductivityStats(currentEmployee.id) : null; const teamMetaStats = getTeamMetaStats(); @@ -121,7 +120,7 @@ export function DashboardStats() { Team Members -
{Object.keys(Employees).length}
+
{getEmployeeCount()}

4sight employees

diff --git a/apps/web/src/data/employees.ts b/apps/web/src/data/employees.ts index b7c180a..69d97b9 100644 --- a/apps/web/src/data/employees.ts +++ b/apps/web/src/data/employees.ts @@ -1,3 +1,9 @@ +import { getEmployeesRecord } from '@/lib/mvp-data'; + +/** + * Compatibility shim: employees now come from @worksight/common via mvp-data. + * Prefer importing from `@/lib/mvp-data` or `@worksight/common/data` directly. + */ export type EmployeeProfile = { [key: string]: { internal_id: string; @@ -12,52 +18,7 @@ export type EmployeeProfile = { }; }; -export const Employees: EmployeeProfile = { - '08b6fc43-77e6-4fcf-8ed8-dafc16b4b025': { - internal_id: 'E001', - email: 'sjc.71415@gmail.com', - name: 'John Carlo Santos', - role: 'fullstack', - manager_id: '', - date_joined: new Date('2025-08-24'), - department: 'Infra', - created_at: new Date('2025-08-24T16:51:38.176858+00:00'), - updated_at: new Date('2025-08-24T16:51:38.176858+00:00'), - }, - '58f36d76-f382-41b4-ad3e-f8192958d12b': { - internal_id: 'E002', - email: 'dagsmagalona@gmail.com', - name: 'Adriel M. Magalona', - role: 'frontend', - manager_id: '', - date_joined: new Date('2025-08-24'), - department: 'Engineering', - created_at: new Date('2025-08-24T16:51:38.176858+00:00'), - updated_at: new Date('2025-08-24T16:51:38.176858+00:00'), - }, - '71400e28-3c2a-4694-8124-8fbb9a0b66d8': { - internal_id: 'E003', - email: 'kielethanlanzanas@gmail.com', - name: 'Kiel Ethan L. Lanzanas', - role: 'data-ml', - manager_id: '', - date_joined: new Date('2025-08-24'), - department: 'Data', - created_at: new Date('2025-08-24T16:51:38.176858+00:00'), - updated_at: new Date('2025-08-24T16:51:38.176858+00:00'), - }, - '88165ccb-2c80-455a-9ace-466a30448f67': { - internal_id: 'E004', - email: 'ebenerado@gmail.com', - name: 'Ellah D. Benerado', - role: 'data-ml', - manager_id: '', - date_joined: new Date('2025-08-24'), - department: 'Data', - created_at: new Date('2025-08-24T16:51:38.176858+00:00'), - updated_at: new Date('2025-08-24T16:51:38.176858+00:00'), - }, -}; +export const Employees: EmployeeProfile = getEmployeesRecord(); // Also export with the old name for backward compatibility export const employees = Employees; diff --git a/apps/web/src/lib/mvp-data.ts b/apps/web/src/lib/mvp-data.ts new file mode 100644 index 0000000..7fbe514 --- /dev/null +++ b/apps/web/src/lib/mvp-data.ts @@ -0,0 +1,323 @@ +/** + * MVP data bridge: map @worksight/common fixtures/utils into web view models. + * Keeps dashboard/admin pages free of duplicated local mocks. + */ +import { UserRole } from '@/auth/types'; +import type { UserWithMetrics } from '@/schemas/user'; +import { + Activities, + Assignments, + DataSources, + Employees, + SurveyQuestionnaire, + SurveyResponseList, + Surveys, + Teams, +} from '@worksight/common/data'; +import type { + Activity, + Assignment, + EmployeeProfile, + Survey as CommonSurvey, +} from '@worksight/common/types'; +import { + ActivityLookup, + AssignmentLookup, + EmployeeLookup, + SourceLookup, + SurveyMetadataLookup, + SurveyQuestionLookup, + SurveyResponsesLookup, + TeamLookup, +} from '@worksight/common/utils'; + +export const employeeLookup = new EmployeeLookup(Employees); +export const teamLookup = new TeamLookup(Teams); +export const assignmentLookup = new AssignmentLookup(Assignments); +export const activityLookup = new ActivityLookup(Activities); +export const sourceLookup = new SourceLookup(DataSources); +export const surveyLookup = new SurveyMetadataLookup(Surveys); +export const surveyQuestionLookup = new SurveyQuestionLookup(SurveyQuestionnaire); +export const surveyResponseMetaLookup = new SurveyResponsesLookup(SurveyResponseList); + +const ROLE_MAP: Record = { + employee: UserRole.EMPLOYEE, + team_lead: UserRole.TEAM_LEAD, + manager: UserRole.MANAGER, + admin: UserRole.ADMIN, + super_admin: UserRole.SUPER_ADMIN, +}; + +function assertFixturesPopulated(): void { + if (Employees.length === 0) { + throw new Error('@worksight/common Employees fixture is empty'); + } + if (Assignments.length === 0) { + throw new Error('@worksight/common Assignments fixture is empty'); + } + if (Surveys.length === 0) { + throw new Error('@worksight/common Surveys fixture is empty'); + } +} + +assertFixturesPopulated(); + +function departmentLabel(employee: EmployeeProfile): string { + const first = employee.department.find((d) => d && d.length > 0); + return first || 'unassigned'; +} + +function teamLabel(employee: EmployeeProfile): string { + if (employee.team) { + const team = teamLookup.getById(employee.team); + if (team) return team.name; + } + const managed = teamLookup.getByManager(employee.id); + if (managed[0]) return managed[0].name; + const memberTeam = Teams.find((t) => t.member_ids.includes(employee.id)); + return memberTeam?.name ?? departmentLabel(employee); +} + +function mapRole(role: string): UserRole { + return ROLE_MAP[role] ?? UserRole.EMPLOYEE; +} + +function relativeTime(date: Date): string { + const deltaMs = Date.now() - date.getTime(); + const minutes = Math.max(1, Math.floor(deltaMs / 60_000)); + if (minutes < 60) return `${minutes} minutes ago`; + const hours = Math.floor(minutes / 60); + if (hours < 48) return `${hours} hours ago`; + const days = Math.floor(hours / 24); + return `${days} days ago`; +} + +function riskFromBurnout(score: number): 'low' | 'medium' | 'high' { + if (score >= 7) return 'high'; + if (score >= 4) return 'medium'; + return 'low'; +} + +/** Admin users table rows from common employees + task/survey lookupt. */ +export function getUsersWithMetrics(): UserWithMetrics[] { + return employeeLookup + .all() + .filter((e) => e.role !== 'guest') + .map((employee) => { + const stats = assignmentLookup.getStats(employee.id); + const burnoutScore = Math.min( + 10, + Math.round(((100 - stats.workLifeBalanceScore) / 10) * 10) / 10 + ); + const surveyCompleted = surveyResponseMetaLookup + .all() + .some((r) => r.employee_id === employee.id || r.employee_id === employee.internal_id); + + return { + id: employee.id, + name: employee.name, + email: employee.email, + role: mapRole(employee.role), + department: departmentLabel(employee), + team: teamLabel(employee), + burnoutScore, + lastActive: relativeTime(employee.updated_at), + surveyCompleted, + riskLevel: riskFromBurnout(burnoutScore), + tasksCompleted: stats.completedTasks, + }; + }); +} + +export type MvpTask = { + id: string; + title: string; + description: string; + status: 'todo' | 'in-progress' | 'completed'; + priority: 'low' | 'medium' | 'high'; + dueDate: string; + estimatedHours: number; + order: number; + assigneeId?: string; +}; + +function mapAssignmentStatus(status: Assignment['status']): MvpTask['status'] { + if (status === 'in_progress') return 'in-progress'; + if (status === 'completed') return 'completed'; + return 'todo'; +} + +function mapAssignmentPriority(priority: Assignment['priority']): MvpTask['priority'] { + if (priority === 'critical' || priority === 'high') return 'high'; + if (priority === 'medium') return 'medium'; + return 'low'; +} + +/** Task board rows from common Assignments. */ +export function getMvpTasks(): MvpTask[] { + return assignmentLookup.all().map((assignment, index) => ({ + id: assignment.id, + title: assignment.title ?? assignment.external_id ?? 'Untitled task', + description: [assignment.epic, assignment.sprint, assignment.type] + .filter(Boolean) + .join(' · '), + status: mapAssignmentStatus(assignment.status), + priority: mapAssignmentPriority(assignment.priority), + dueDate: assignment.updated_at.toISOString().slice(0, 10), + estimatedHours: Math.max(1, Math.round((assignment.points ?? 1) * 0.5)), + order: index, + assigneeId: assignment.employee_id, + })); +} + +export type MvpSurvey = { + id: string; + title: string; + description: string; + status: 'draft' | 'active' | 'paused' | 'completed'; + questionCount: number; + responseCount: number; + createdAt: string; + lastModified: string; + createdBy: string; + category: 'burnout' | 'satisfaction' | 'wellness' | 'feedback'; + targetAudience: 'all' | 'managers' | 'employees' | 'specific'; +}; + +function surveyTitle(survey: CommonSurvey): string { + return `Wellness Survey (${survey.num_questions} questions)`; +} + +/** Admin survey list from common Surveys + questions + response metadata. */ +export function getMvpSurveys(): MvpSurvey[] { + const creators = new Map(employeeLookup.all().map((e) => [e.id, e.name])); + const responseCounts = surveyResponseMetaLookup.getStats().submissionsPerSurvey; + + return surveyLookup.all().map((survey) => { + const questionCount = + surveyQuestionLookup.getStats(survey.id).totalQuestions || survey.num_questions; + return { + id: survey.id, + title: surveyTitle(survey), + description: 'Burnout and wellness assessment from @worksight/common fixtures', + status: 'active' as const, + questionCount, + responseCount: responseCounts[survey.id] ?? SurveyResponseList.length, + createdAt: survey.created_at.toISOString().slice(0, 10), + lastModified: survey.created_at.toISOString().slice(0, 10), + createdBy: creators.get(survey.created_by) ?? survey.created_by, + category: 'burnout' as const, + targetAudience: 'all' as const, + }; + }); +} + +export function findEmployeeByEmail(email: string): EmployeeProfile | null { + return employeeLookup.all().find((e) => e.email === email) ?? null; +} + +export function getEmployeeProductivityStats(employeeId: string) { + return assignmentLookup.getStats(employeeId); +} + +export function getAfterHoursActivities(): Activity[] { + return activityLookup.getAfterHoursActivities(); +} + +export function getWeekendActivities(): Activity[] { + return activityLookup.getWeekendActivities(); +} + +export function getTeamMetaStats() { + const allActivities = activityLookup.all(); + const metaActivities = allActivities.filter( + (a) => + a.description.toLowerCase().includes('meta') || + a.description.toLowerCase().includes('ironic') || + a.description.toLowerCase().includes('4th wall') || + a.description.toLowerCase().includes('recursive') + ); + + const allAssignments = assignmentLookup.all(); + const metaAssignments = allAssignments.filter( + (a) => + a.epic === 'FOURTH-WALL-BREAKS' || + (a.title?.toLowerCase().includes('meta') ?? false) || + (a.title?.toLowerCase().includes('ironic') ?? false) + ); + + return { + totalMetaActivities: metaActivities.length, + totalMetaAssignments: metaAssignments.length, + metaActivityPercentage: + allActivities.length > 0 ? (metaActivities.length / allActivities.length) * 100 : 0, + selfAwarenessLevel: metaActivities.length + metaAssignments.length, + fourthWallIntegrity: Math.max(0, 100 - metaActivities.length * 5), + }; +} + +export function getDataSourceUsageStats() { + const sources = sourceLookup.all(); + const sourceStats = {} as Record< + string, + { + name: string; + assignments: number; + activities: number; + total: number; + types: string[]; + } + >; + + for (const source of sources) { + const assignments = assignmentLookup.filter({ source_id: source.id }).count(); + const activities = activityLookup.filter({ source_id: source.id }).count(); + if (assignments === 0 && activities === 0) continue; + sourceStats[source.id] = { + name: source.name, + assignments, + activities, + total: assignments + activities, + types: source.type, + }; + } + + return sourceStats; +} + +export function getEmployeeCount(): number { + return employeeLookup.count(); +} + +/** Legacy record shape used by offline auth / api helpers. */ +export function getEmployeesRecord(): Record< + string, + { + internal_id: string; + email: string; + name: string; + role: string; + manager_id: string; + date_joined: Date; + department: string; + created_at: Date; + updated_at: Date; + } +> { + return Object.fromEntries( + employeeLookup.all().map((e) => [ + e.id, + { + internal_id: e.internal_id, + email: e.email, + name: e.name, + role: e.role, + manager_id: e.manager_id ?? '', + date_joined: e.date_joined, + department: departmentLabel(e), + created_at: e.created_at, + updated_at: e.updated_at, + }, + ]) + ); +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 85c0b83..68b68b6 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -31,6 +31,9 @@ "@worksight/common": [ "../../packages/common/src" ], + "@worksight/common/*": [ + "../../packages/common/src/*" + ], "@/*": [ "./src/*" ] diff --git a/docs/handoffs/2026-07-25-wire-web-common.md b/docs/handoffs/2026-07-25-wire-web-common.md index 2acc661..456905f 100644 --- a/docs/handoffs/2026-07-25-wire-web-common.md +++ b/docs/handoffs/2026-07-25-wire-web-common.md @@ -1,29 +1,29 @@ # HANDOFF — Wire web to common (#16) -**Status:** Planned +**Status:** In progress (MVP slice) **Branch:** `feat/mvp-wire-web` **Issue(s):** #16 **Last updated:** 2026-07-25 ## Bottom line -Dashboard/UI uses `@worksight/common` **data + types + utils** (employees, tasks, survey, burnout, datasources) instead of duplicated local mocks. +Dashboard/UI uses `@worksight/common` **data + types + utils** (employees, tasks, survey) instead of duplicated local mocks. ## Current state - Package exports: `@worksight/common`, `/data`, `/types`, `/utils` -- Fixtures in `packages/common/src/data/*.ts` -- Web landing is marketing-only; limited common import (api uses `Roles` from types) - -## Hook points -- `apps/web/src/app/**` dashboard routes -- `apps/web/src/store(s)/**`, `data/`, `hooks/` -- `next.config.ts` transpilePackages already aware of common +- **Slice landed:** `apps/web/src/lib/mvp-data.ts` bridges common → dashboard stats, `/tasks`, `/dashboard/tasks`, `/admin/users`, `/admin/surveys` +- Local `@/data/employees` re-exports common via compatibility shim +- Managers (E001–E004) added to common `Employees` so assignments/teams resolve ## How to verify ```bash +pnpm --filter @worksight/common build +pnpm --filter @worksight/web type-check +pnpm --filter @worksight/web build pnpm --filter @worksight/web dev -# open dashboard; employees/tasks populated from common fixtures +# open /dashboard, /dashboard/tasks, /admin/users, /admin/surveys ``` ## Done means -- [ ] ≥ employees + tasks + survey views import from common -- [ ] No silent empty fallbacks when fixtures exist +- [x] ≥ employees + tasks + survey views import from common +- [x] No silent empty fallbacks when fixtures exist +- [ ] Remaining local fixtures (`work-tracking`, `surveys`, survey form) fully retired diff --git a/packages/common/src/data/employees.ts b/packages/common/src/data/employees.ts index 1e1a2f1..aa88e4b 100644 --- a/packages/common/src/data/employees.ts +++ b/packages/common/src/data/employees.ts @@ -1,6 +1,52 @@ import { EmployeeProfile, Team } from '../types'; export const Employees: EmployeeProfile[] = [ + // Managers (referenced by Teams + Assignments) + { + id: '08b6fc43-77e6-4fcf-8ed8-dafc16b4b025', + internal_id: 'E001', + email: 'sjc.71415@gmail.com', + name: 'John Carlo Santos', + role: 'manager', + date_joined: new Date('2025-08-24'), + department: ['backend'], + created_at: new Date('2025-08-24T16:51:38.176858+00:00'), + updated_at: new Date('2025-08-24T16:51:38.176858+00:00'), + }, + { + id: '58f36d76-f382-41b4-ad3e-f8192958d12b', + internal_id: 'E002', + email: 'dagsmagalona@gmail.com', + name: 'Adriel M. Magalona', + role: 'manager', + date_joined: new Date('2025-08-24'), + department: ['sysadmin'], + created_at: new Date('2025-08-24T16:51:38.176858+00:00'), + updated_at: new Date('2025-08-24T16:51:38.176858+00:00'), + }, + { + id: '71400e28-3c2a-4694-8124-8fbb9a0b66d8', + internal_id: 'E003', + email: 'kielethanlanzanas@gmail.com', + name: 'Kiel Ethan L. Lanzanas', + role: 'manager', + date_joined: new Date('2025-08-24'), + department: ['data'], + created_at: new Date('2025-08-24T16:51:38.176858+00:00'), + updated_at: new Date('2025-08-24T16:51:38.176858+00:00'), + }, + { + id: '88165ccb-2c80-455a-9ace-466a30448f67', + internal_id: 'E004', + email: 'ebenerado@gmail.com', + name: 'Ellah D. Benerado', + role: 'manager', + date_joined: new Date('2025-08-24'), + department: ['data'], + created_at: new Date('2025-08-24T16:51:38.176858+00:00'), + updated_at: new Date('2025-08-24T16:51:38.176858+00:00'), + }, + // Team members under John Carlo Santos (Infra Manager) { id: 'e5a1b2c3-d4e5-f6g7-h8i9-j0k1l2m3n4o5',