@burdenoff/microfe-crewfoundry 2026.704.1 → 2026.704.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/crewfoundry/components/forms/FormModal.js.map +1 -1
- package/dist/crewfoundry/pages/AdminAdminPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsDirectoryPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsFilesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsTeamsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyCompanyDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyCompanyProfilePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardEnhancedDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardExecutiveDashboardPage.js.map +1 -1
- package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.js +2 -2
- package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrEmployeesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPagePage.js +1 -1
- package/dist/crewfoundry/pages/LearningLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js +1 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsCreateProjectPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamSettingsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HrPayrollPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrPayrollPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n Separator,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { CheckCircle, Clock, DollarSign, Download, Plus, TrendingUp } from 'lucide-react';\nimport { useCrewFoundryStore, type PayrollRecord } from '../store/crewfoundryStore';\nimport { FormModal, useFormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\n\nconst STATUS_OPTIONS = [\n { label: 'Draft', value: 'draft' },\n { label: 'Processing', value: 'processing' },\n { label: 'Approved', value: 'approved' },\n { label: 'Paid', value: 'paid' },\n { label: 'Failed', value: 'failed' },\n];\n\nexport function HrPayrollPagePage() {\n const { payrollRecords, employees, addPayrollRecord, updatePayrollRecord } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const [selectedPeriod, setSelectedPeriod] = useState('January 2026');\n const createForm = useFormModal<void>();\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 10 });\n\n const employeeOptions = useMemo(\n () =>\n employees.map((employee) => ({\n label: `${employee.firstName} ${employee.lastName}`,\n value: employee.id,\n })),\n [employees]\n );\n\n const payrollFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n options: employeeOptions,\n colSpan: 2,\n },\n { name: 'period', label: 'Pay period', type: 'text', required: true, placeholder: 'e.g. February 2026' },\n { name: 'paymentMethod', label: 'Payment method', type: 'text', required: true, placeholder: 'e.g. Bank Transfer' },\n { name: 'grossPay', label: 'Gross pay', type: 'number', required: true },\n { name: 'deductions', label: 'Deductions', type: 'number', required: true },\n { name: 'netPay', label: 'Net pay (auto if blank)', type: 'number' },\n { name: 'payDate', label: 'Pay date', type: 'date' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n ],\n [employeeOptions]\n );\n\n const periodRecords = payrollRecords.filter((r) => r.period === selectedPeriod);\n const totalGross = periodRecords.reduce((s, r) => s + r.grossPay, 0);\n const totalNet = periodRecords.reduce((s, r) => s + r.netPay, 0);\n const totalDeductions = periodRecords.reduce((s, r) => s + r.deductions, 0);\n const paidCount = periodRecords.filter((r) => r.status === 'paid').length;\n const processingCount = periodRecords.filter((r) => r.status === 'processing').length;\n const recordPageItems = periodRecords.slice(offset, offset + pageSize);\n\n const periods = [...new Set(payrollRecords.map((r) => r.period))];\n\n const handlePeriodChange = (period: string) => {\n setSelectedPeriod(period);\n setPage(1);\n };\n\n const recordColumns: ResponsiveTableColumn<PayrollRecord>[] = [\n {\n key: 'employee',\n header: 'Employee',\n priority: 'primary',\n cell: (record) => <span className=\"font-medium\">{record.employeeName}</span>,\n },\n {\n key: 'department',\n header: 'Department',\n cell: (record) => <span className=\"text-text-secondary\">{record.department}</span>,\n },\n {\n key: 'gross',\n header: 'Gross Pay',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => <span>${record.grossPay.toLocaleString()}</span>,\n },\n {\n key: 'deductions',\n header: 'Deductions',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => (\n <span className=\"text-status-error-text\">-${record.deductions.toLocaleString()}</span>\n ),\n },\n {\n key: 'net',\n header: 'Net Pay',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => <span className=\"font-medium\">${record.netPay.toLocaleString()}</span>,\n },\n {\n key: 'method',\n header: 'Payment Method',\n cell: (record) => <span className=\"text-text-secondary\">{record.paymentMethod}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n cell: (record) => (\n <div className=\"flex items-center gap-2\">\n <Badge\n variant={\n record.status === 'paid'\n ? 'default'\n : record.status === 'processing'\n ? 'secondary'\n : 'outline'\n }\n className=\"text-[10px]\"\n >\n {record.status}\n </Badge>\n {record.status !== 'paid' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-6 px-2 text-[10px]\"\n onClick={() => handleMarkPaid(record)}\n >\n Mark paid\n </Button>\n )}\n </div>\n ),\n },\n ];\n\n const handleCreate = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const grossPay = getNumber(values, 'grossPay');\n const deductions = getNumber(values, 'deductions');\n const netPayInput = getNumber(values, 'netPay');\n const payload: PayrollRecord = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n department: employee?.department ?? '',\n period: getText(values, 'period'),\n grossPay,\n deductions,\n netPay: netPayInput > 0 ? netPayInput : Math.max(grossPay - deductions, 0),\n status: (getText(values, 'status') as PayrollRecord['status']) || 'draft',\n payDate: getText(values, 'payDate'),\n paymentMethod: getText(values, 'paymentMethod'),\n };\n await addPayrollRecord(payload);\n };\n\n const handleMarkPaid = async (record: PayrollRecord) => {\n try {\n await updatePayrollRecord(record.id, {\n status: 'paid',\n payDate: new Date().toISOString().slice(0, 10),\n });\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to update payroll record', 'error');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Payroll Management</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Process and manage employee payroll</p>\n <PagePurpose className=\"mt-1\">Run and review payroll — track salaries, deductions, and pay runs so your team gets paid accurately and on time.</PagePurpose>\n </div>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled\n aria-label=\"Export payroll (coming soon)\"\n title=\"Payroll export is coming soon\"\n >\n <Download className=\"h-4 w-4 mr-1\" />\n Export\n <Badge variant=\"secondary\" className=\"ml-1 text-[9px]\">\n Soon\n </Badge>\n </Button>\n <Button size=\"sm\" onClick={() => createForm.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Record\n </Button>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Gross Payroll</CardTitle>\n <DollarSign className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">${totalGross.toLocaleString()}</div>\n <p className=\"text-xs text-text-secondary\">{selectedPeriod}</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Net Payroll</CardTitle>\n <TrendingUp className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">${totalNet.toLocaleString()}</div>\n <p className=\"text-xs text-text-secondary\">After deductions</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Paid</CardTitle>\n <CheckCircle className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{paidCount}</div>\n <p className=\"text-xs text-text-secondary\">employees paid</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Processing</CardTitle>\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{processingCount}</div>\n <p className=\"text-xs text-text-secondary\">in progress</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"flex flex-wrap gap-2\">\n {periods.map((period) => (\n <Button\n key={period}\n variant={selectedPeriod === period ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => handlePeriodChange(period)}\n >\n {period}\n </Button>\n ))}\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Payroll Records</CardTitle>\n <CardDescription>\n Individual employee payroll details for {selectedPeriod}\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <ResponsiveTable<PayrollRecord>\n data={recordPageItems}\n rowKey={(record) => record.id}\n columns={recordColumns}\n emptyState={\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No payroll records for {selectedPeriod}.\n </p>\n }\n />\n {periodRecords.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={periodRecords.length}\n onPageChange={setPage}\n />\n )}\n {periodRecords.length > 0 && (\n <>\n <Separator />\n <div className=\"flex flex-wrap items-center justify-between gap-4 text-sm font-semibold\">\n <span>Total for {selectedPeriod}</span>\n <div className=\"flex flex-wrap gap-x-6 gap-y-1\">\n <span>\n <span className=\"text-text-secondary\">Gross </span>${totalGross.toLocaleString()}\n </span>\n <span className=\"text-status-error-text\">\n <span className=\"text-text-secondary\">Deductions </span>-$\n {totalDeductions.toLocaleString()}\n </span>\n <span>\n <span className=\"text-text-secondary\">Net </span>${totalNet.toLocaleString()}\n </span>\n </div>\n </div>\n </>\n )}\n </CardContent>\n </Card>\n\n <FormModal\n open={createForm.open}\n onOpenChange={createForm.onOpenChange}\n title=\"Add payroll record\"\n description=\"Create a payroll entry for a team member.\"\n fields={payrollFields}\n submitLabel=\"Add record\"\n onSubmit={handleCreate}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HrPayrollPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrPayrollPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n Separator,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { CheckCircle, Clock, DollarSign, Download, Plus, TrendingUp } from 'lucide-react';\nimport { useCrewFoundryStore, type PayrollRecord } from '../store/crewfoundryStore';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\n\nconst STATUS_OPTIONS = [\n { label: 'Draft', value: 'draft' },\n { label: 'Processing', value: 'processing' },\n { label: 'Approved', value: 'approved' },\n { label: 'Paid', value: 'paid' },\n { label: 'Failed', value: 'failed' },\n];\n\nexport function HrPayrollPagePage() {\n const { payrollRecords, employees, addPayrollRecord, updatePayrollRecord } =\n useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const [selectedPeriod, setSelectedPeriod] = useState('January 2026');\n const createForm = useFormModal<void>();\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 10 });\n\n const employeeOptions = useMemo(\n () =>\n employees.map((employee) => ({\n label: `${employee.firstName} ${employee.lastName}`,\n value: employee.id,\n })),\n [employees]\n );\n\n const payrollFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n options: employeeOptions,\n colSpan: 2,\n },\n {\n name: 'period',\n label: 'Pay period',\n type: 'text',\n required: true,\n placeholder: 'e.g. February 2026',\n },\n {\n name: 'paymentMethod',\n label: 'Payment method',\n type: 'text',\n required: true,\n placeholder: 'e.g. Bank Transfer',\n },\n { name: 'grossPay', label: 'Gross pay', type: 'number', required: true },\n { name: 'deductions', label: 'Deductions', type: 'number', required: true },\n { name: 'netPay', label: 'Net pay (auto if blank)', type: 'number' },\n { name: 'payDate', label: 'Pay date', type: 'date' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n ],\n [employeeOptions]\n );\n\n const periodRecords = payrollRecords.filter((r) => r.period === selectedPeriod);\n const totalGross = periodRecords.reduce((s, r) => s + r.grossPay, 0);\n const totalNet = periodRecords.reduce((s, r) => s + r.netPay, 0);\n const totalDeductions = periodRecords.reduce((s, r) => s + r.deductions, 0);\n const paidCount = periodRecords.filter((r) => r.status === 'paid').length;\n const processingCount = periodRecords.filter((r) => r.status === 'processing').length;\n const recordPageItems = periodRecords.slice(offset, offset + pageSize);\n\n const periods = [...new Set(payrollRecords.map((r) => r.period))];\n\n const handlePeriodChange = (period: string) => {\n setSelectedPeriod(period);\n setPage(1);\n };\n\n const recordColumns: ResponsiveTableColumn<PayrollRecord>[] = [\n {\n key: 'employee',\n header: 'Employee',\n priority: 'primary',\n cell: (record) => <span className=\"font-medium\">{record.employeeName}</span>,\n },\n {\n key: 'department',\n header: 'Department',\n cell: (record) => <span className=\"text-text-secondary\">{record.department}</span>,\n },\n {\n key: 'gross',\n header: 'Gross Pay',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => <span>${record.grossPay.toLocaleString()}</span>,\n },\n {\n key: 'deductions',\n header: 'Deductions',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => (\n <span className=\"text-status-error-text\">-${record.deductions.toLocaleString()}</span>\n ),\n },\n {\n key: 'net',\n header: 'Net Pay',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => <span className=\"font-medium\">${record.netPay.toLocaleString()}</span>,\n },\n {\n key: 'method',\n header: 'Payment Method',\n cell: (record) => <span className=\"text-text-secondary\">{record.paymentMethod}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n cell: (record) => (\n <div className=\"flex items-center gap-2\">\n <Badge\n variant={\n record.status === 'paid'\n ? 'default'\n : record.status === 'processing'\n ? 'secondary'\n : 'outline'\n }\n className=\"text-[10px]\"\n >\n {record.status}\n </Badge>\n {record.status !== 'paid' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-6 px-2 text-[10px]\"\n onClick={() => handleMarkPaid(record)}\n >\n Mark paid\n </Button>\n )}\n </div>\n ),\n },\n ];\n\n const handleCreate = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const grossPay = getNumber(values, 'grossPay');\n const deductions = getNumber(values, 'deductions');\n const netPayInput = getNumber(values, 'netPay');\n const payload: PayrollRecord = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n department: employee?.department ?? '',\n period: getText(values, 'period'),\n grossPay,\n deductions,\n netPay: netPayInput > 0 ? netPayInput : Math.max(grossPay - deductions, 0),\n status: (getText(values, 'status') as PayrollRecord['status']) || 'draft',\n payDate: getText(values, 'payDate'),\n paymentMethod: getText(values, 'paymentMethod'),\n };\n await addPayrollRecord(payload);\n };\n\n const handleMarkPaid = async (record: PayrollRecord) => {\n try {\n await updatePayrollRecord(record.id, {\n status: 'paid',\n payDate: new Date().toISOString().slice(0, 10),\n });\n } catch (error) {\n showToast(\n error instanceof Error ? error.message : 'Unable to update payroll record',\n 'error'\n );\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Payroll Management</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Process and manage employee payroll</p>\n <PagePurpose className=\"mt-1\">\n Run and review payroll — track salaries, deductions, and pay runs so your team gets paid\n accurately and on time.\n </PagePurpose>\n </div>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled\n aria-label=\"Export payroll (coming soon)\"\n title=\"Payroll export is coming soon\"\n >\n <Download className=\"h-4 w-4 mr-1\" />\n Export\n <Badge variant=\"secondary\" className=\"ml-1 text-[9px]\">\n Soon\n </Badge>\n </Button>\n <Button size=\"sm\" onClick={() => createForm.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Record\n </Button>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Gross Payroll</CardTitle>\n <DollarSign className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">${totalGross.toLocaleString()}</div>\n <p className=\"text-xs text-text-secondary\">{selectedPeriod}</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Net Payroll</CardTitle>\n <TrendingUp className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">${totalNet.toLocaleString()}</div>\n <p className=\"text-xs text-text-secondary\">After deductions</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Paid</CardTitle>\n <CheckCircle className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{paidCount}</div>\n <p className=\"text-xs text-text-secondary\">employees paid</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Processing</CardTitle>\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{processingCount}</div>\n <p className=\"text-xs text-text-secondary\">in progress</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"flex flex-wrap gap-2\">\n {periods.map((period) => (\n <Button\n key={period}\n variant={selectedPeriod === period ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => handlePeriodChange(period)}\n >\n {period}\n </Button>\n ))}\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Payroll Records</CardTitle>\n <CardDescription>\n Individual employee payroll details for {selectedPeriod}\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <ResponsiveTable<PayrollRecord>\n data={recordPageItems}\n rowKey={(record) => record.id}\n columns={recordColumns}\n emptyState={\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No payroll records for {selectedPeriod}.\n </p>\n }\n />\n {periodRecords.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={periodRecords.length}\n onPageChange={setPage}\n />\n )}\n {periodRecords.length > 0 && (\n <>\n <Separator />\n <div className=\"flex flex-wrap items-center justify-between gap-4 text-sm font-semibold\">\n <span>Total for {selectedPeriod}</span>\n <div className=\"flex flex-wrap gap-x-6 gap-y-1\">\n <span>\n <span className=\"text-text-secondary\">Gross </span>$\n {totalGross.toLocaleString()}\n </span>\n <span className=\"text-status-error-text\">\n <span className=\"text-text-secondary\">Deductions </span>-$\n {totalDeductions.toLocaleString()}\n </span>\n <span>\n <span className=\"text-text-secondary\">Net </span>${totalNet.toLocaleString()}\n </span>\n </div>\n </div>\n </>\n )}\n </CardContent>\n </Card>\n\n <FormModal\n open={createForm.open}\n onOpenChange={createForm.onOpenChange}\n title=\"Add payroll record\"\n description=\"Create a payroll entry for a team member.\"\n fields={payrollFields}\n submitLabel=\"Add record\"\n onSubmit={handleCreate}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HrTimeTrackingPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrTimeTrackingPagePage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { CheckCircle, Clock, LogIn, LogOut, Timer } from 'lucide-react';\nimport { useCrewFoundryStore, type TimeEntry } from '../store/crewfoundryStore';\nimport { FormModal, useFormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\n\nconst STATUS_OPTIONS = [\n { label: 'Pending', value: 'pending' },\n { label: 'Approved', value: 'approved' },\n { label: 'Rejected', value: 'rejected' },\n];\n\nconst CLOCK_OUT_FIELDS: FieldConfig[] = [\n { name: 'clockOut', label: 'Clock-out time', type: 'text', required: true, placeholder: 'e.g. 17:30' },\n { name: 'totalHours', label: 'Total hours', type: 'number', required: true },\n { name: 'overtime', label: 'Overtime hours', type: 'number' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n];\n\nexport function HrTimeTrackingPagePage() {\n const { timeEntries, employees, addTimeEntry, updateTimeEntry } = useCrewFoundryStore();\n const createForm = useFormModal<void>();\n const clockOutForm = useFormModal<TimeEntry>();\n\n const employeeOptions = useMemo(\n () =>\n employees.map((employee) => ({\n label: `${employee.firstName} ${employee.lastName}`,\n value: employee.id,\n })),\n [employees]\n );\n\n const entryFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n options: employeeOptions,\n colSpan: 2,\n },\n { name: 'date', label: 'Date', type: 'date', required: true },\n { name: 'project', label: 'Project', type: 'text', required: true },\n { name: 'clockIn', label: 'Clock-in time', type: 'text', required: true, placeholder: 'e.g. 09:00' },\n { name: 'clockOut', label: 'Clock-out time', type: 'text', placeholder: 'e.g. 17:30' },\n { name: 'totalHours', label: 'Total hours', type: 'number' },\n { name: 'overtime', label: 'Overtime hours', type: 'number' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n ],\n [employeeOptions]\n );\n\n const totalHours = timeEntries.reduce((s, t) => s + t.totalHours, 0);\n const totalOvertime = timeEntries.reduce((s, t) => s + t.overtime, 0);\n const approvedEntries = timeEntries.filter((t) => t.status === 'approved').length;\n const pendingEntries = timeEntries.filter((t) => t.status === 'pending').length;\n\n const projectBreakdown = timeEntries.reduce<Record<string, number>>((acc, t) => {\n acc[t.project] = (acc[t.project] || 0) + t.totalHours;\n return acc;\n }, {});\n const maxProjectHours = Math.max(...Object.values(projectBreakdown), 1);\n\n const recentEntries = useMemo(\n () => [...timeEntries].sort((a, b) => b.date.localeCompare(a.date)),\n [timeEntries]\n );\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 8 });\n const activityPageItems = recentEntries.slice(offset, offset + pageSize);\n\n const handleCreate = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const payload: TimeEntry = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n date: getText(values, 'date'),\n clockIn: getText(values, 'clockIn'),\n clockOut: getText(values, 'clockOut'),\n totalHours: getNumber(values, 'totalHours'),\n overtime: getNumber(values, 'overtime'),\n project: getText(values, 'project'),\n status: (getText(values, 'status') as TimeEntry['status']) || 'pending',\n };\n await addTimeEntry(payload);\n };\n\n const handleClockOut = async (values: FormValues) => {\n const entry = clockOutForm.context;\n if (!entry) {\n return;\n }\n await updateTimeEntry(entry.id, {\n clockOut: getText(values, 'clockOut'),\n totalHours: getNumber(values, 'totalHours'),\n overtime: getNumber(values, 'overtime'),\n status: (getText(values, 'status') as TimeEntry['status']) || entry.status,\n });\n };\n\n const clockOutInitial: FormValues | undefined = clockOutForm.context\n ? {\n clockOut: clockOutForm.context.clockOut,\n totalHours: clockOutForm.context.totalHours,\n overtime: clockOutForm.context.overtime,\n status: clockOutForm.context.status,\n }\n : undefined;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Time Tracking</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Monitor employee work hours and overtime</p>\n <PagePurpose className=\"mt-1\">Track hours and attendance across your team — use it to monitor logged time, overtime, and timesheet approvals.</PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => createForm.openModal()}>\n <Timer className=\"h-4 w-4 mr-1\" />\n Add Entry\n </Button>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Total Hours</CardTitle>\n <Clock className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalHours.toFixed(1)}</div>\n <p className=\"text-xs text-text-secondary\">All logged hours</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Overtime</CardTitle>\n <Timer className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalOvertime.toFixed(1)}h</div>\n <p className=\"text-xs text-text-secondary\">Extra hours logged</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Approved</CardTitle>\n <CheckCircle className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{approvedEntries}</div>\n <p className=\"text-xs text-text-secondary\">time entries</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Pending</CardTitle>\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{pendingEntries}</div>\n <p className=\"text-xs text-text-secondary\">awaiting approval</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Hours by Project</CardTitle>\n <CardDescription>Time allocation across active projects</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(projectBreakdown)\n .sort(([, a], [, b]) => b - a)\n .map(([project, hours]) => (\n <div key={project} className=\"flex items-center gap-3\">\n <span className=\"w-48 text-sm truncate\">{project}</span>\n <div className=\"flex-1 h-4 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-4 bg-action-primary-bg rounded\"\n style={{ width: `${Math.round((hours / maxProjectHours) * 100)}%` }}\n />\n </div>\n <span className=\"text-sm font-medium w-14 text-right\">{hours.toFixed(1)}h</span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Recent Activity</CardTitle>\n <CardDescription>Latest employee clock-in/out entries</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {activityPageItems.map((entry) => (\n <div key={entry.id} className=\"flex items-center justify-between rounded border p-3\">\n <div>\n <p className=\"text-sm font-medium\">{entry.employeeName}</p>\n <p className=\"text-xs text-text-secondary\">{entry.project}</p>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right text-xs\">\n <div className=\"flex items-center gap-1\">\n <LogIn className=\"h-3 w-3 text-status-success-text\" />\n {entry.clockIn}\n </div>\n <div className=\"flex items-center gap-1\">\n <LogOut className=\"h-3 w-3 text-status-error-text\" />\n {entry.clockOut}\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium\">{entry.totalHours}h</p>\n {entry.overtime > 0 && (\n <p className=\"text-xs text-status-warning-text\">+{entry.overtime}h OT</p>\n )}\n </div>\n <Badge\n variant={entry.status === 'approved' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {entry.status}\n </Badge>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Clock out ${entry.employeeName}`}\n onClick={() => clockOutForm.openModal(entry)}\n >\n <LogOut className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n ))}\n {recentEntries.length === 0 && (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No time entries logged yet.\n </p>\n )}\n {recentEntries.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={recentEntries.length}\n onPageChange={setPage}\n />\n )}\n </CardContent>\n </Card>\n </div>\n\n <FormModal\n open={createForm.open}\n onOpenChange={createForm.onOpenChange}\n title=\"Add time entry\"\n description=\"Log a work session for a team member.\"\n fields={entryFields}\n submitLabel=\"Add entry\"\n onSubmit={handleCreate}\n />\n\n <FormModal\n open={clockOutForm.open}\n onOpenChange={clockOutForm.onOpenChange}\n title=\"Clock out\"\n description={\n clockOutForm.context\n ? `Record clock-out for ${clockOutForm.context.employeeName}.`\n : undefined\n }\n fields={CLOCK_OUT_FIELDS}\n initialValues={clockOutInitial}\n submitLabel=\"Save\"\n onSubmit={handleClockOut}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HrTimeTrackingPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrTimeTrackingPagePage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { CheckCircle, Clock, LogIn, LogOut, Timer } from 'lucide-react';\nimport { useCrewFoundryStore, type TimeEntry } from '../store/crewfoundryStore';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\n\nconst STATUS_OPTIONS = [\n { label: 'Pending', value: 'pending' },\n { label: 'Approved', value: 'approved' },\n { label: 'Rejected', value: 'rejected' },\n];\n\nconst CLOCK_OUT_FIELDS: FieldConfig[] = [\n {\n name: 'clockOut',\n label: 'Clock-out time',\n type: 'text',\n required: true,\n placeholder: 'e.g. 17:30',\n },\n { name: 'totalHours', label: 'Total hours', type: 'number', required: true },\n { name: 'overtime', label: 'Overtime hours', type: 'number' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n];\n\nexport function HrTimeTrackingPagePage() {\n const { timeEntries, employees, addTimeEntry, updateTimeEntry } = useCrewFoundryStore();\n const createForm = useFormModal<void>();\n const clockOutForm = useFormModal<TimeEntry>();\n\n const employeeOptions = useMemo(\n () =>\n employees.map((employee) => ({\n label: `${employee.firstName} ${employee.lastName}`,\n value: employee.id,\n })),\n [employees]\n );\n\n const entryFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n options: employeeOptions,\n colSpan: 2,\n },\n { name: 'date', label: 'Date', type: 'date', required: true },\n { name: 'project', label: 'Project', type: 'text', required: true },\n {\n name: 'clockIn',\n label: 'Clock-in time',\n type: 'text',\n required: true,\n placeholder: 'e.g. 09:00',\n },\n { name: 'clockOut', label: 'Clock-out time', type: 'text', placeholder: 'e.g. 17:30' },\n { name: 'totalHours', label: 'Total hours', type: 'number' },\n { name: 'overtime', label: 'Overtime hours', type: 'number' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n ],\n [employeeOptions]\n );\n\n const totalHours = timeEntries.reduce((s, t) => s + t.totalHours, 0);\n const totalOvertime = timeEntries.reduce((s, t) => s + t.overtime, 0);\n const approvedEntries = timeEntries.filter((t) => t.status === 'approved').length;\n const pendingEntries = timeEntries.filter((t) => t.status === 'pending').length;\n\n const projectBreakdown = timeEntries.reduce<Record<string, number>>((acc, t) => {\n acc[t.project] = (acc[t.project] || 0) + t.totalHours;\n return acc;\n }, {});\n const maxProjectHours = Math.max(...Object.values(projectBreakdown), 1);\n\n const recentEntries = useMemo(\n () => [...timeEntries].sort((a, b) => b.date.localeCompare(a.date)),\n [timeEntries]\n );\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 8 });\n const activityPageItems = recentEntries.slice(offset, offset + pageSize);\n\n const handleCreate = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const payload: TimeEntry = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n date: getText(values, 'date'),\n clockIn: getText(values, 'clockIn'),\n clockOut: getText(values, 'clockOut'),\n totalHours: getNumber(values, 'totalHours'),\n overtime: getNumber(values, 'overtime'),\n project: getText(values, 'project'),\n status: (getText(values, 'status') as TimeEntry['status']) || 'pending',\n };\n await addTimeEntry(payload);\n };\n\n const handleClockOut = async (values: FormValues) => {\n const entry = clockOutForm.context;\n if (!entry) {\n return;\n }\n await updateTimeEntry(entry.id, {\n clockOut: getText(values, 'clockOut'),\n totalHours: getNumber(values, 'totalHours'),\n overtime: getNumber(values, 'overtime'),\n status: (getText(values, 'status') as TimeEntry['status']) || entry.status,\n });\n };\n\n const clockOutInitial: FormValues | undefined = clockOutForm.context\n ? {\n clockOut: clockOutForm.context.clockOut,\n totalHours: clockOutForm.context.totalHours,\n overtime: clockOutForm.context.overtime,\n status: clockOutForm.context.status,\n }\n : undefined;\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Time Tracking</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Monitor employee work hours and overtime</p>\n <PagePurpose className=\"mt-1\">\n Track hours and attendance across your team — use it to monitor logged time, overtime,\n and timesheet approvals.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => createForm.openModal()}>\n <Timer className=\"h-4 w-4 mr-1\" />\n Add Entry\n </Button>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Total Hours</CardTitle>\n <Clock className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalHours.toFixed(1)}</div>\n <p className=\"text-xs text-text-secondary\">All logged hours</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Overtime</CardTitle>\n <Timer className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalOvertime.toFixed(1)}h</div>\n <p className=\"text-xs text-text-secondary\">Extra hours logged</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Approved</CardTitle>\n <CheckCircle className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{approvedEntries}</div>\n <p className=\"text-xs text-text-secondary\">time entries</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Pending</CardTitle>\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{pendingEntries}</div>\n <p className=\"text-xs text-text-secondary\">awaiting approval</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Hours by Project</CardTitle>\n <CardDescription>Time allocation across active projects</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(projectBreakdown)\n .sort(([, a], [, b]) => b - a)\n .map(([project, hours]) => (\n <div key={project} className=\"flex items-center gap-3\">\n <span className=\"w-48 text-sm truncate\">{project}</span>\n <div className=\"flex-1 h-4 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-4 bg-action-primary-bg rounded\"\n style={{ width: `${Math.round((hours / maxProjectHours) * 100)}%` }}\n />\n </div>\n <span className=\"text-sm font-medium w-14 text-right\">{hours.toFixed(1)}h</span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Recent Activity</CardTitle>\n <CardDescription>Latest employee clock-in/out entries</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {activityPageItems.map((entry) => (\n <div key={entry.id} className=\"flex items-center justify-between rounded border p-3\">\n <div>\n <p className=\"text-sm font-medium\">{entry.employeeName}</p>\n <p className=\"text-xs text-text-secondary\">{entry.project}</p>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right text-xs\">\n <div className=\"flex items-center gap-1\">\n <LogIn className=\"h-3 w-3 text-status-success-text\" />\n {entry.clockIn}\n </div>\n <div className=\"flex items-center gap-1\">\n <LogOut className=\"h-3 w-3 text-status-error-text\" />\n {entry.clockOut}\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium\">{entry.totalHours}h</p>\n {entry.overtime > 0 && (\n <p className=\"text-xs text-status-warning-text\">+{entry.overtime}h OT</p>\n )}\n </div>\n <Badge\n variant={entry.status === 'approved' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {entry.status}\n </Badge>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Clock out ${entry.employeeName}`}\n onClick={() => clockOutForm.openModal(entry)}\n >\n <LogOut className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n ))}\n {recentEntries.length === 0 && (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No time entries logged yet.\n </p>\n )}\n {recentEntries.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={recentEntries.length}\n onPageChange={setPage}\n />\n )}\n </CardContent>\n </Card>\n </div>\n\n <FormModal\n open={createForm.open}\n onOpenChange={createForm.onOpenChange}\n title=\"Add time entry\"\n description=\"Log a work session for a team member.\"\n fields={entryFields}\n submitLabel=\"Add entry\"\n onSubmit={handleCreate}\n />\n\n <FormModal\n open={clockOutForm.open}\n onOpenChange={clockOutForm.onOpenChange}\n title=\"Clock out\"\n description={\n clockOutForm.context\n ? `Record clock-out for ${clockOutForm.context.employeeName}.`\n : undefined\n }\n fields={CLOCK_OUT_FIELDS}\n initialValues={clockOutInitial}\n submitLabel=\"Save\"\n onSubmit={handleClockOut}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LearningAssessmentsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningAssessmentsPagePage.tsx"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"LearningAssessmentsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningAssessmentsPagePage.tsx"],"sourcesContent":["import { Button, GradientText, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useNavigate } from 'react-router-dom';\n\nexport function LearningAssessmentsPagePage() {\n const navigate = useNavigate();\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Assessments</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Quizzes and exams for your enrolled courses</p>\n <PagePurpose className=\"mt-1\">\n Create and review assessments that measure what your team has learned — use them to\n certify skills and identify knowledge gaps.\n </PagePurpose>\n </div>\n\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title=\"Assessments are coming soon\"\n description=\"Course assessments — question banks, timed attempts, scoring and pass thresholds — are being built. Once available, the quizzes tied to your enrolled courses will appear here.\"\n action={\n <Button variant=\"outline\" onClick={() => navigate('../../learning/courses-page')}>\n Browse courses\n </Button>\n }\n />\n </div>\n );\n}\n"],"mappings":";;;;AAGA,SAAgB,IAA8B;CAC5C,IAAM,IAAW,GAAa;AAE9B,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAA,UAAA;GACE,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,GAAD,EAAA,UAAc,eAA0B,CAAA;IACrC,CAAA;GACL,kBAAC,KAAD;IAAG,WAAU;cAA8B;IAA+C,CAAA;GAC1F,kBAAC,GAAD;IAAa,WAAU;cAAO;IAGhB,CAAA;GACV,EAAA,CAAA,EAEN,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,GAAD;IAAQ,SAAQ;IAAU,eAAe,EAAS,8BAA8B;cAAE;IAEzE,CAAA;GAEX,CAAA,CACE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LearningCertificatesPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningCertificatesPagePage.tsx"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"LearningCertificatesPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningCertificatesPagePage.tsx"],"sourcesContent":["import { Button, GradientText, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useNavigate } from 'react-router-dom';\n\nexport function LearningCertificatesPagePage() {\n const navigate = useNavigate();\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Certificates</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Proof of completed training and validated skills\n </p>\n <PagePurpose className=\"mt-1\">\n View and manage the certificates your team has earned — proof of completed training and\n validated skills.\n </PagePurpose>\n </div>\n\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title=\"No certificates yet\"\n description=\"Certificates are issued when a course is completed. Once certificate issuance is available, every credential you earn will appear here with its issue date and verification link.\"\n action={\n <Button variant=\"outline\" onClick={() => navigate('../../learning/courses-page')}>\n Browse courses\n </Button>\n }\n />\n </div>\n );\n}\n"],"mappings":";;;;AAGA,SAAgB,IAA+B;CAC7C,IAAM,IAAW,GAAa;AAE9B,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD,EAAA,UAAA;GACE,kBAAC,MAAD;IAAI,WAAU;cACZ,kBAAC,GAAD,EAAA,UAAc,gBAA2B,CAAA;IACtC,CAAA;GACL,kBAAC,KAAD;IAAG,WAAU;cAA8B;IAEvC,CAAA;GACJ,kBAAC,GAAD;IAAa,WAAU;cAAO;IAGhB,CAAA;GACV,EAAA,CAAA,EAEN,kBAAC,GAAD;GACE,cAAa;GACb,OAAM;GACN,aAAY;GACZ,QACE,kBAAC,GAAD;IAAQ,SAAQ;IAAU,eAAe,EAAS,8BAA8B;cAAE;IAEzE,CAAA;GAEX,CAAA,CACE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LearningCourseDetailsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningCourseDetailsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n EmphasisPanel,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { BookOpen, Clock, GraduationCap, Play, Star, Users } from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router-dom';\nimport { useState } from 'react';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function LearningCourseDetailsPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { courses, enrollCourse } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const [enrolling, setEnrolling] = useState(false);\n const courseId = searchParams.get('id');\n const course = courseId ? courses.find((c) => c.id === courseId) : courses[0];\n if (!course)\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Course not found\"\n description=\"This course is no longer available.\"\n />\n </div>\n );\n const completionRate =\n course.enrolled > 0 ? Math.round((course.completed / course.enrolled) * 100) : 0;\n\n const handleStart = async () => {\n setEnrolling(true);\n try {\n await enrollCourse(course.id);\n showToast('Enrolled — your course is ready', 'success');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to enroll in course', 'error');\n } finally {\n setEnrolling(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2 mb-1\">\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{course.title}</GradientText>\n </h1>\n <Badge\n variant={\n course.level === 'beginner'\n ? 'secondary'\n : course.level === 'intermediate'\n ? 'default'\n : 'destructive'\n }\n >\n {course.level}\n </Badge>\n </div>\n <p className=\"text-sm text-text-secondary\">\n by {course.instructor} · {course.category}\n </p>\n <PagePurpose className=\"mt-2\">\n Everything you need to decide on and start this course — what it covers, who it's\n for, and how others have done — so you can enroll with confidence and build the right\n skills.\n </PagePurpose>\n </div>\n <Button onClick={handleStart} disabled={enrolling}>\n <Play className=\"h-4 w-4 mr-1\" />\n {enrolling ? 'Enrolling…' : 'Start Course'}\n </Button>\n </div>\n\n <NextSteps\n storageKey=\"crewfoundry-course-details\"\n steps={[\n {\n id: 'start',\n label: 'Start the first module',\n description: 'Begin learning and track your progress as you go.',\n icon: <Play className=\"h-4 w-4\" />,\n onClick: () =>\n navigate(
|
|
1
|
+
{"version":3,"file":"LearningCourseDetailsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningCourseDetailsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n EmphasisPanel,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { BookOpen, Clock, GraduationCap, Play, Star, Users } from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router-dom';\nimport { useState } from 'react';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function LearningCourseDetailsPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { courses, enrollCourse } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const [enrolling, setEnrolling] = useState(false);\n const courseId = searchParams.get('id');\n const course = courseId ? courses.find((c) => c.id === courseId) : courses[0];\n if (!course)\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Course not found\"\n description=\"This course is no longer available.\"\n />\n </div>\n );\n const completionRate =\n course.enrolled > 0 ? Math.round((course.completed / course.enrolled) * 100) : 0;\n\n const handleStart = async () => {\n setEnrolling(true);\n try {\n await enrollCourse(course.id);\n showToast('Enrolled — your course is ready', 'success');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to enroll in course', 'error');\n } finally {\n setEnrolling(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2 mb-1\">\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{course.title}</GradientText>\n </h1>\n <Badge\n variant={\n course.level === 'beginner'\n ? 'secondary'\n : course.level === 'intermediate'\n ? 'default'\n : 'destructive'\n }\n >\n {course.level}\n </Badge>\n </div>\n <p className=\"text-sm text-text-secondary\">\n by {course.instructor} · {course.category}\n </p>\n <PagePurpose className=\"mt-2\">\n Everything you need to decide on and start this course — what it covers, who it's\n for, and how others have done — so you can enroll with confidence and build the right\n skills.\n </PagePurpose>\n </div>\n <Button onClick={handleStart} disabled={enrolling}>\n <Play className=\"h-4 w-4 mr-1\" />\n {enrolling ? 'Enrolling…' : 'Start Course'}\n </Button>\n </div>\n\n <NextSteps\n storageKey=\"crewfoundry-course-details\"\n steps={[\n {\n id: 'start',\n label: 'Start the first module',\n description: 'Begin learning and track your progress as you go.',\n icon: <Play className=\"h-4 w-4\" />,\n onClick: () =>\n navigate(`../../learning/course-learning-page?id=${encodeURIComponent(course.id)}`),\n },\n {\n id: 'my-learning',\n label: 'See all your learning',\n description: 'Review everything you are currently enrolled in.',\n icon: <GraduationCap className=\"h-4 w-4\" />,\n onClick: () => navigate('../../learning/my-learning-page'),\n },\n {\n id: 'browse',\n label: 'Browse more courses',\n description: 'Find related training to round out your skills.',\n icon: <BookOpen className=\"h-4 w-4\" />,\n onClick: () => navigate('../../learning/courses-page'),\n },\n ]}\n />\n\n <div className=\"grid gap-4 md:grid-cols-4\">\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Clock className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{course.duration}</p>\n <p className=\"text-xs text-text-secondary\">Duration</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <BookOpen className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{course.modules}</p>\n <p className=\"text-xs text-text-secondary\">Modules</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Users className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{course.enrolled}</p>\n <p className=\"text-xs text-text-secondary\">Enrolled</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Star className=\"h-5 w-5 mx-auto text-status-warning-text mb-1\" />\n <p className=\"text-lg font-bold\">{course.rating}/5</p>\n <p className=\"text-xs text-text-secondary\">Rating</p>\n </CardContent>\n </Card>\n </div>\n <EmphasisPanel>\n <div className=\"mb-2 text-lg font-semibold\">About this Course</div>\n <p className=\"text-sm text-text-secondary\">{course.description}</p>\n <div className=\"flex flex-wrap gap-1 mt-3\">\n {course.skills.map((s) => (\n <Badge key={s} variant=\"outline\">\n {s}\n </Badge>\n ))}\n </div>\n </EmphasisPanel>\n <Card>\n <CardHeader>\n <CardTitle>Completion Statistics</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2\">\n <div className=\"flex justify-between text-sm\">\n <span>\n {course.completed} of {course.enrolled} students completed\n </span>\n <span className=\"font-medium\">{completionRate}%</span>\n </div>\n <div className=\"h-3 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-3 bg-status-success-text rounded\"\n style={{ width: `${completionRate}%` }}\n />\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -97,7 +97,7 @@ var w = [
|
|
|
97
97
|
}
|
|
98
98
|
];
|
|
99
99
|
function T() {
|
|
100
|
-
let { courses: T, addCourse: E } = e(), D = c(), [O, k] = r(""), A = n(), { page: j, pageSize: M, offset: N, setPage: P } = C({ initialPageSize: 12 }), F = T.filter((e) => `${e.title} ${e.category} ${e.instructor}`.toLowerCase().includes(O.toLowerCase())), I = F.slice(N, N + M), L = (e) => D(
|
|
100
|
+
let { courses: T, addCourse: E } = e(), D = c(), [O, k] = r(""), A = n(), { page: j, pageSize: M, offset: N, setPage: P } = C({ initialPageSize: 12 }), F = T.filter((e) => `${e.title} ${e.category} ${e.instructor}`.toLowerCase().includes(O.toLowerCase())), I = F.slice(N, N + M), L = (e) => D(`../../learning/course-details-page?id=${encodeURIComponent(e)}`);
|
|
101
101
|
return /* @__PURE__ */ u("div", {
|
|
102
102
|
className: "space-y-6 p-6",
|
|
103
103
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LearningCoursesPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningCoursesPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n Button,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { Plus, Search, Star, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore, type LearningCourse } from '../store/crewfoundryStore';\nimport { FormModal, useFormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\n\nconst COURSE_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Course Title', type: 'text', required: true, colSpan: 2 },\n { name: 'category', label: 'Category', type: 'text', required: true },\n { name: 'instructor', label: 'Instructor', type: 'text', required: true },\n { name: 'duration', label: 'Duration', type: 'text', required: true, placeholder: 'e.g. 6 weeks' },\n { name: 'modules', label: 'Modules', type: 'number', required: true, placeholder: 'e.g. 8' },\n {\n name: 'level',\n label: 'Level',\n type: 'select',\n required: true,\n options: [\n { label: 'Beginner', value: 'beginner' },\n { label: 'Intermediate', value: 'intermediate' },\n { label: 'Advanced', value: 'advanced' },\n ],\n },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n required: true,\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Draft', value: 'draft' },\n { label: 'Archived', value: 'archived' },\n ],\n },\n { name: 'description', label: 'Description', type: 'textarea', required: true, colSpan: 2 },\n { name: 'skills', label: 'Skills', type: 'tags', placeholder: 'Add a skill and press Enter', colSpan: 2 },\n];\n\nexport function LearningCoursesPagePage() {\n const { courses, addCourse } = useCrewFoundryStore();\n const navigate = useNavigate();\n const [search, setSearch] = useState('');\n const createModal = useFormModal();\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 12 });\n const filtered = courses.filter((c) =>\n `${c.title} ${c.category} ${c.instructor}`.toLowerCase().includes(search.toLowerCase())\n );\n const paged = filtered.slice(offset, offset + pageSize);\n const openCourse = (id: string) =>\n navigate(`../course-details-page?id=${encodeURIComponent(id)}`);\n\n const handleCreate = async (values: FormValues) => {\n await addCourse({\n id: `course-${Date.now()}`,\n title: String(values.title ?? ''),\n category: String(values.category ?? ''),\n instructor: String(values.instructor ?? ''),\n duration: String(values.duration ?? ''),\n level: String(values.level ?? 'beginner') as LearningCourse['level'],\n enrolled: 0,\n completed: 0,\n rating: 0,\n status: String(values.status ?? 'draft') as LearningCourse['status'],\n description: String(values.description ?? ''),\n modules: typeof values.modules === 'number' ? values.modules : Number(values.modules) || 0,\n skills: Array.isArray(values.skills) ? values.skills : [],\n });\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Course Catalog</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{courses.length} courses available</p>\n <PagePurpose className=\"mt-1\">Browse the course catalog — find training to grow your team's skills and enroll in the programs that fit your goals.</PagePurpose>\n </div>\n <Button onClick={() => createModal.openModal()}>\n <Plus className=\"mr-1 h-4 w-4\" />\n Create Course\n </Button>\n </div>\n <div className=\"relative max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\" />\n <Input\n placeholder=\"Search courses...\"\n className=\"pl-9\"\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPage(1);\n }}\n />\n </div>\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {filtered.length === 0 ? (\n <div className=\"col-span-full\">\n <IllustratedEmptyState illustration=\"empty-search\" title=\"No courses found\" description=\"Try a different search term to find training.\" />\n </div>\n ) : (\n paged.map((course, _idx) => (\n <GlassCard\n key={course.id}\n treatment={_idx === 0 ? 'glass' : 'plain'}\n glow={_idx === 0}\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${course.title}`}\n onClick={() => openCourse(course.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openCourse(course.id);\n }\n }}\n className=\"hover:shadow-md transition-shadow cursor-pointer focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n >\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {course.category}\n </Badge>\n <Badge\n variant={\n course.level === 'beginner'\n ? 'secondary'\n : course.level === 'intermediate'\n ? 'default'\n : 'destructive'\n }\n className=\"text-[10px]\"\n >\n {course.level}\n </Badge>\n </div>\n <CardTitle className=\"text-base mt-2\">{course.title}</CardTitle>\n <CardDescription>\n {course.instructor} · {course.duration} · {course.modules} modules\n </CardDescription>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-text-secondary mb-3\">{course.description}</p>\n <div className=\"flex flex-wrap gap-1 mb-3\">\n {course.skills.map((s) => (\n <Badge key={s} variant=\"outline\" className=\"text-[9px]\">\n {s}\n </Badge>\n ))}\n </div>\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n {course.rating}\n </span>\n <span className=\"flex items-center gap-1\">\n <Users className=\"h-3 w-3\" />\n {course.enrolled} enrolled\n </span>\n <span>\n {course.enrolled > 0\n ? Math.round((course.completed / course.enrolled) * 100)\n : 0}\n % completion\n </span>\n </div>\n </CardContent>\n </GlassCard>\n )))}\n </div>\n\n {filtered.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n )}\n\n <FormModal\n open={createModal.open}\n onOpenChange={createModal.onOpenChange}\n title=\"Create Course\"\n description=\"Add a new course to the catalog.\"\n fields={COURSE_FIELDS}\n submitLabel=\"Create Course\"\n onSubmit={handleCreate}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LearningCoursesPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningCoursesPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n Button,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { Plus, Search, Star, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore, type LearningCourse } from '../store/crewfoundryStore';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\n\nconst COURSE_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Course Title', type: 'text', required: true, colSpan: 2 },\n { name: 'category', label: 'Category', type: 'text', required: true },\n { name: 'instructor', label: 'Instructor', type: 'text', required: true },\n {\n name: 'duration',\n label: 'Duration',\n type: 'text',\n required: true,\n placeholder: 'e.g. 6 weeks',\n },\n { name: 'modules', label: 'Modules', type: 'number', required: true, placeholder: 'e.g. 8' },\n {\n name: 'level',\n label: 'Level',\n type: 'select',\n required: true,\n options: [\n { label: 'Beginner', value: 'beginner' },\n { label: 'Intermediate', value: 'intermediate' },\n { label: 'Advanced', value: 'advanced' },\n ],\n },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n required: true,\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Draft', value: 'draft' },\n { label: 'Archived', value: 'archived' },\n ],\n },\n { name: 'description', label: 'Description', type: 'textarea', required: true, colSpan: 2 },\n {\n name: 'skills',\n label: 'Skills',\n type: 'tags',\n placeholder: 'Add a skill and press Enter',\n colSpan: 2,\n },\n];\n\nexport function LearningCoursesPagePage() {\n const { courses, addCourse } = useCrewFoundryStore();\n const navigate = useNavigate();\n const [search, setSearch] = useState('');\n const createModal = useFormModal();\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 12 });\n const filtered = courses.filter((c) =>\n `${c.title} ${c.category} ${c.instructor}`.toLowerCase().includes(search.toLowerCase())\n );\n const paged = filtered.slice(offset, offset + pageSize);\n const openCourse = (id: string) =>\n navigate(`../../learning/course-details-page?id=${encodeURIComponent(id)}`);\n\n const handleCreate = async (values: FormValues) => {\n await addCourse({\n id: `course-${Date.now()}`,\n title: String(values.title ?? ''),\n category: String(values.category ?? ''),\n instructor: String(values.instructor ?? ''),\n duration: String(values.duration ?? ''),\n level: String(values.level ?? 'beginner') as LearningCourse['level'],\n enrolled: 0,\n completed: 0,\n rating: 0,\n status: String(values.status ?? 'draft') as LearningCourse['status'],\n description: String(values.description ?? ''),\n modules: typeof values.modules === 'number' ? values.modules : Number(values.modules) || 0,\n skills: Array.isArray(values.skills) ? values.skills : [],\n });\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Course Catalog</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{courses.length} courses available</p>\n <PagePurpose className=\"mt-1\">\n Browse the course catalog — find training to grow your team's skills and enroll in\n the programs that fit your goals.\n </PagePurpose>\n </div>\n <Button onClick={() => createModal.openModal()}>\n <Plus className=\"mr-1 h-4 w-4\" />\n Create Course\n </Button>\n </div>\n <div className=\"relative max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\" />\n <Input\n placeholder=\"Search courses...\"\n className=\"pl-9\"\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPage(1);\n }}\n />\n </div>\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {filtered.length === 0 ? (\n <div className=\"col-span-full\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No courses found\"\n description=\"Try a different search term to find training.\"\n />\n </div>\n ) : (\n paged.map((course, _idx) => (\n <GlassCard\n key={course.id}\n treatment={_idx === 0 ? 'glass' : 'plain'}\n glow={_idx === 0}\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${course.title}`}\n onClick={() => openCourse(course.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openCourse(course.id);\n }\n }}\n className=\"hover:shadow-md transition-shadow cursor-pointer focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n >\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {course.category}\n </Badge>\n <Badge\n variant={\n course.level === 'beginner'\n ? 'secondary'\n : course.level === 'intermediate'\n ? 'default'\n : 'destructive'\n }\n className=\"text-[10px]\"\n >\n {course.level}\n </Badge>\n </div>\n <CardTitle className=\"text-base mt-2\">{course.title}</CardTitle>\n <CardDescription>\n {course.instructor} · {course.duration} · {course.modules} modules\n </CardDescription>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-text-secondary mb-3\">{course.description}</p>\n <div className=\"flex flex-wrap gap-1 mb-3\">\n {course.skills.map((s) => (\n <Badge key={s} variant=\"outline\" className=\"text-[9px]\">\n {s}\n </Badge>\n ))}\n </div>\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n {course.rating}\n </span>\n <span className=\"flex items-center gap-1\">\n <Users className=\"h-3 w-3\" />\n {course.enrolled} enrolled\n </span>\n <span>\n {course.enrolled > 0\n ? Math.round((course.completed / course.enrolled) * 100)\n : 0}\n % completion\n </span>\n </div>\n </CardContent>\n </GlassCard>\n ))\n )}\n </div>\n\n {filtered.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n )}\n\n <FormModal\n open={createModal.open}\n onOpenChange={createModal.onOpenChange}\n title=\"Create Course\"\n description=\"Add a new course to the catalog.\"\n fields={COURSE_FIELDS}\n submitLabel=\"Create Course\"\n onSubmit={handleCreate}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
|
5
5
|
import { Badge as l, Card as u, CardContent as d, CardDescription as f, CardHeader as p, CardTitle as m, GlassCard as h, GradientText as g, PagePurpose as _ } from "@burdenoff/fe-libs/ui";
|
|
6
6
|
//#region src/crewfoundry/pages/LearningLearningPagePage.tsx
|
|
7
7
|
function v() {
|
|
8
|
-
let v = o(), { courses: y, learningPaths: b } = e(), x = y.reduce((e, t) => e + t.enrolled, 0), S = y.reduce((e, t) => e + t.completed, 0), C = [...y].sort((e, t) => t.rating - e.rating).slice(0, 3), w = (e) => v(
|
|
8
|
+
let v = o(), { courses: y, learningPaths: b } = e(), x = y.reduce((e, t) => e + t.enrolled, 0), S = y.reduce((e, t) => e + t.completed, 0), C = [...y].sort((e, t) => t.rating - e.rating).slice(0, 3), w = (e) => v(`../../learning/course-details-page?id=${encodeURIComponent(e)}`), T = (e) => v(`../../learning/learning-path-detail-page?id=${encodeURIComponent(e)}`);
|
|
9
9
|
return /* @__PURE__ */ c("div", {
|
|
10
10
|
className: "space-y-6 p-6",
|
|
11
11
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LearningLearningPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningLearningPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { BookOpen, GraduationCap, Star, TrendingUp, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function LearningLearningPagePage() {\n const navigate = useNavigate();\n const { courses, learningPaths } = useCrewFoundryStore();\n const totalEnrolled = courses.reduce((s, c) => s + c.enrolled, 0);\n const totalCompleted = courses.reduce((s, c) => s + c.completed, 0);\n const featuredCourses = [...courses].sort((a, b) => b.rating - a.rating).slice(0, 3);\n const openCourse = (id: string) =>\n navigate(`../course-details-page?id=${encodeURIComponent(id)}`);\n const openPath = (id: string) =>\n navigate(`../learning-path-detail-page?id=${encodeURIComponent(id)}`);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Learning & Development</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Explore courses, learning paths, and grow your skills\n </p>\n <PagePurpose className=\"mt-1\">Your learning home — discover courses, continue where you left off, and keep developing the skills your role needs.</PagePurpose>\n </div>\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Courses</CardTitle>\n <BookOpen className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{courses.length}</div>\n <p className=\"text-xs text-text-secondary\">Active courses</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Enrolled</CardTitle>\n <Users className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalEnrolled}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Completed</CardTitle>\n <GraduationCap className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalCompleted}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Paths</CardTitle>\n <TrendingUp className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{learningPaths.length}</div>\n </CardContent>\n </GlassCard>\n </div>\n <div>\n <h2 className=\"text-lg font-medium mb-3\">Featured Courses</h2>\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {featuredCourses.map((course) => (\n <Card\n key={course.id}\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${course.title}`}\n onClick={() => openCourse(course.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openCourse(course.id);\n }\n }}\n className=\"cursor-pointer transition-shadow hover:shadow-md focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n >\n <CardHeader>\n <CardTitle className=\"text-base\">{course.title}</CardTitle>\n <CardDescription>\n {course.instructor} · {course.duration}\n </CardDescription>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-text-secondary mb-3\">{course.description}</p>\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n <span className=\"text-sm\">{course.rating}</span>\n </div>\n <div className=\"flex gap-1\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {course.level}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {course.category}\n </Badge>\n </div>\n </div>\n <div className=\"mt-2 text-xs text-text-secondary\">\n {course.enrolled} enrolled · {course.completed} completed\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n </div>\n <div>\n <h2 className=\"text-lg font-medium mb-3\">Learning Paths</h2>\n <div className=\"grid gap-4 md:grid-cols-3\">\n {learningPaths.map((path) => (\n <Card\n key={path.id}\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${path.title}`}\n onClick={() => openPath(path.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openPath(path.id);\n }\n }}\n className=\"cursor-pointer transition-shadow hover:shadow-md focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n >\n <CardHeader>\n <CardTitle className=\"text-base\">{path.title}</CardTitle>\n <CardDescription>{path.description}</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>\n {path.courses.length} courses · {path.duration}\n </span>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {path.level}\n </Badge>\n </div>\n <div className=\"mt-2 space-y-1\">\n <div className=\"flex justify-between text-xs\">\n <span>{path.enrolled} enrolled</span>\n <span>{path.completionRate}%</span>\n </div>\n <div className=\"h-2 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-2 bg-action-primary-bg rounded\"\n style={{ width: `${path.completionRate}%` }}\n />\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LearningLearningPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningLearningPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { BookOpen, GraduationCap, Star, TrendingUp, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function LearningLearningPagePage() {\n const navigate = useNavigate();\n const { courses, learningPaths } = useCrewFoundryStore();\n const totalEnrolled = courses.reduce((s, c) => s + c.enrolled, 0);\n const totalCompleted = courses.reduce((s, c) => s + c.completed, 0);\n const featuredCourses = [...courses].sort((a, b) => b.rating - a.rating).slice(0, 3);\n const openCourse = (id: string) =>\n navigate(`../../learning/course-details-page?id=${encodeURIComponent(id)}`);\n const openPath = (id: string) =>\n navigate(`../../learning/learning-path-detail-page?id=${encodeURIComponent(id)}`);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Learning & Development</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Explore courses, learning paths, and grow your skills\n </p>\n <PagePurpose className=\"mt-1\">\n Your learning home — discover courses, continue where you left off, and keep developing\n the skills your role needs.\n </PagePurpose>\n </div>\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Courses</CardTitle>\n <BookOpen className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{courses.length}</div>\n <p className=\"text-xs text-text-secondary\">Active courses</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Enrolled</CardTitle>\n <Users className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalEnrolled}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Completed</CardTitle>\n <GraduationCap className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalCompleted}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Paths</CardTitle>\n <TrendingUp className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{learningPaths.length}</div>\n </CardContent>\n </GlassCard>\n </div>\n <div>\n <h2 className=\"text-lg font-medium mb-3\">Featured Courses</h2>\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {featuredCourses.map((course) => (\n <Card\n key={course.id}\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${course.title}`}\n onClick={() => openCourse(course.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openCourse(course.id);\n }\n }}\n className=\"cursor-pointer transition-shadow hover:shadow-md focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n >\n <CardHeader>\n <CardTitle className=\"text-base\">{course.title}</CardTitle>\n <CardDescription>\n {course.instructor} · {course.duration}\n </CardDescription>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-text-secondary mb-3\">{course.description}</p>\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n <span className=\"text-sm\">{course.rating}</span>\n </div>\n <div className=\"flex gap-1\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {course.level}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {course.category}\n </Badge>\n </div>\n </div>\n <div className=\"mt-2 text-xs text-text-secondary\">\n {course.enrolled} enrolled · {course.completed} completed\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n </div>\n <div>\n <h2 className=\"text-lg font-medium mb-3\">Learning Paths</h2>\n <div className=\"grid gap-4 md:grid-cols-3\">\n {learningPaths.map((path) => (\n <Card\n key={path.id}\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${path.title}`}\n onClick={() => openPath(path.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openPath(path.id);\n }\n }}\n className=\"cursor-pointer transition-shadow hover:shadow-md focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n >\n <CardHeader>\n <CardTitle className=\"text-base\">{path.title}</CardTitle>\n <CardDescription>{path.description}</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>\n {path.courses.length} courses · {path.duration}\n </span>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {path.level}\n </Badge>\n </div>\n <div className=\"mt-2 space-y-1\">\n <div className=\"flex justify-between text-xs\">\n <span>{path.enrolled} enrolled</span>\n <span>{path.completionRate}%</span>\n </div>\n <div className=\"h-2 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-2 bg-action-primary-bg rounded\"\n style={{ width: `${path.completionRate}%` }}\n />\n </div>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LearningLearningPathDetailPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningLearningPathDetailPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n EmphasisPanel,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { BookOpen, CheckCircle, Clock, Star, Users } from 'lucide-react';\nimport { useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function LearningLearningPathDetailPagePage() {\n const [searchParams] = useSearchParams();\n const { learningPaths, courses, enrollCourse } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const [enrolling, setEnrolling] = useState(false);\n const pathId = searchParams.get('id');\n const path = pathId ? learningPaths.find((p) => p.id === pathId) : learningPaths[0];\n if (!path)\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No learning path found\"\n description=\"This learning path is no longer available.\"\n />\n </div>\n );\n const pathCourses = courses.filter((c) => path.courses.includes(c.id));\n\n const handleEnroll = async () => {\n if (pathCourses.length === 0) {\n showToast('No courses in this path yet', 'warning');\n return;\n }\n setEnrolling(true);\n try {\n for (const course of pathCourses) {\n await enrollCourse(course.id);\n }\n showToast(\n `Enrolled in ${pathCourses.length} course${pathCourses.length > 1 ? 's' : ''}`,\n 'success'\n );\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to enroll in path', 'error');\n } finally {\n setEnrolling(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{path.title}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{path.description}</p>\n <PagePurpose className=\"mt-1\"
|
|
1
|
+
{"version":3,"file":"LearningLearningPathDetailPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningLearningPathDetailPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n EmphasisPanel,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { BookOpen, CheckCircle, Clock, Star, Users } from 'lucide-react';\nimport { useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function LearningLearningPathDetailPagePage() {\n const [searchParams] = useSearchParams();\n const { learningPaths, courses, enrollCourse } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const [enrolling, setEnrolling] = useState(false);\n const pathId = searchParams.get('id');\n const path = pathId ? learningPaths.find((p) => p.id === pathId) : learningPaths[0];\n if (!path)\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No learning path found\"\n description=\"This learning path is no longer available.\"\n />\n </div>\n );\n const pathCourses = courses.filter((c) => path.courses.includes(c.id));\n\n const handleEnroll = async () => {\n if (pathCourses.length === 0) {\n showToast('No courses in this path yet', 'warning');\n return;\n }\n setEnrolling(true);\n try {\n for (const course of pathCourses) {\n await enrollCourse(course.id);\n }\n showToast(\n `Enrolled in ${pathCourses.length} course${pathCourses.length > 1 ? 's' : ''}`,\n 'success'\n );\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to enroll in path', 'error');\n } finally {\n setEnrolling(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{path.title}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{path.description}</p>\n <PagePurpose className=\"mt-1\">\n Follow a single learning path end-to-end — see its courses, your progress, and what to\n complete next to finish the journey.\n </PagePurpose>\n </div>\n <Button onClick={handleEnroll} disabled={enrolling}>\n {enrolling ? 'Enrolling…' : 'Enroll Now'}\n </Button>\n </div>\n <div className=\"grid gap-4 md:grid-cols-4\">\n <Card>\n <CardContent className=\"p-4 text-center\">\n <BookOpen className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{path.courses.length}</p>\n <p className=\"text-xs text-text-secondary\">Courses</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Clock className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{path.duration}</p>\n <p className=\"text-xs text-text-secondary\">Duration</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Users className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{path.enrolled}</p>\n <p className=\"text-xs text-text-secondary\">Enrolled</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <CheckCircle className=\"h-5 w-5 mx-auto text-status-success-text mb-1\" />\n <p className=\"text-lg font-bold\">{path.completionRate}%</p>\n <p className=\"text-xs text-text-secondary\">Completion</p>\n </CardContent>\n </Card>\n </div>\n <EmphasisPanel>\n <div className=\"text-lg font-semibold\">Courses in this Path</div>\n <p className=\"mb-4 text-sm text-text-secondary\">\n {pathCourses.length} courses · {path.level}\n </p>\n <div className=\"space-y-3\">\n {pathCourses.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title=\"No courses in this path yet\"\n description=\"Courses added to this path will appear here in order.\"\n />\n ) : (\n pathCourses.map((course, idx) => (\n <div key={course.id} className=\"flex items-center gap-3 rounded border p-4\">\n <div className=\"flex-shrink-0 w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center text-sm font-bold\">\n {idx + 1}\n </div>\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium\">{course.title}</p>\n <p className=\"text-xs text-text-secondary\">\n {course.instructor} · {course.duration} · {course.modules} modules\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n <span className=\"text-sm\">{course.rating}</span>\n </div>\n <Badge\n variant={\n course.level === 'beginner'\n ? 'secondary'\n : course.level === 'intermediate'\n ? 'default'\n : 'destructive'\n }\n className=\"text-[10px]\"\n >\n {course.level}\n </Badge>\n </div>\n </div>\n ))\n )}\n </div>\n </EmphasisPanel>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -50,7 +50,7 @@ var x = [
|
|
|
50
50
|
}
|
|
51
51
|
];
|
|
52
52
|
function S() {
|
|
53
|
-
let { learningPaths: S, courses: C, addLearningPath: w } = e(), T = o(), E = n(), { page: D, pageSize: O, offset: k, setPage: A } = b({ initialPageSize: 9 }), j = S.slice(k, k + O), M = (e) => T(
|
|
53
|
+
let { learningPaths: S, courses: C, addLearningPath: w } = e(), T = o(), E = n(), { page: D, pageSize: O, offset: k, setPage: A } = b({ initialPageSize: 9 }), j = S.slice(k, k + O), M = (e) => T(`../../learning/learning-path-detail-page?id=${encodeURIComponent(e)}`);
|
|
54
54
|
return /* @__PURE__ */ c("div", {
|
|
55
55
|
className: "space-y-6 p-6",
|
|
56
56
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LearningLearningPathsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningLearningPathsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { BookOpen, GraduationCap, Plus } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore, type LearningPath } from '../store/crewfoundryStore';\nimport {
|
|
1
|
+
{"version":3,"file":"LearningLearningPathsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningLearningPathsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { BookOpen, GraduationCap, Plus } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore, type LearningPath } from '../store/crewfoundryStore';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\n\nconst PATH_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Path Title', type: 'text', required: true, colSpan: 2 },\n { name: 'description', label: 'Description', type: 'textarea', required: true, colSpan: 2 },\n {\n name: 'duration',\n label: 'Duration',\n type: 'text',\n required: true,\n placeholder: 'e.g. 3 months',\n },\n {\n name: 'level',\n label: 'Level',\n type: 'select',\n required: true,\n options: [\n { label: 'Beginner', value: 'beginner' },\n { label: 'Intermediate', value: 'intermediate' },\n { label: 'Advanced', value: 'advanced' },\n ],\n },\n];\n\nexport function LearningLearningPathsPagePage() {\n const { learningPaths, courses, addLearningPath } = useCrewFoundryStore();\n const navigate = useNavigate();\n const createModal = useFormModal();\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 9 });\n const pagedPaths = learningPaths.slice(offset, offset + pageSize);\n const openPath = (id: string) =>\n navigate(`../../learning/learning-path-detail-page?id=${encodeURIComponent(id)}`);\n\n const handleCreate = async (values: FormValues) => {\n const path: LearningPath = {\n id: `path-${Date.now()}`,\n title: String(values.title ?? ''),\n description: String(values.description ?? ''),\n courses: [],\n duration: String(values.duration ?? ''),\n level: String(values.level ?? 'beginner'),\n enrolled: 0,\n completionRate: 0,\n };\n await addLearningPath(path);\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Learning Paths</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Structured learning journeys for career growth\n </p>\n <PagePurpose className=\"mt-1\">\n Structured learning journeys that string courses together — follow a path to build\n toward a role or skill set step by step.\n </PagePurpose>\n </div>\n <Button onClick={() => createModal.openModal()}>\n <Plus className=\"mr-1 h-4 w-4\" />\n Create Path\n </Button>\n </div>\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {learningPaths.length === 0 ? (\n <div className=\"col-span-full\">\n <IllustratedEmptyState\n illustration=\"empty-generic\"\n title=\"No learning paths yet\"\n description=\"Learning paths will appear here once they are created.\"\n />\n </div>\n ) : (\n pagedPaths.map((path, _idx) => {\n const pathCourses = courses.filter((c) => path.courses.includes(c.id));\n return (\n <GlassCard\n key={path.id}\n treatment={_idx === 0 ? 'glass' : 'plain'}\n glow={_idx === 0}\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${path.title}`}\n onClick={() => openPath(path.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openPath(path.id);\n }\n }}\n className=\"hover:shadow-md transition-shadow cursor-pointer focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n >\n <CardHeader>\n <CardTitle className=\"text-lg\">{path.title}</CardTitle>\n <CardDescription>{path.description}</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <BookOpen className=\"h-3 w-3\" />\n {path.courses.length} courses\n </span>\n <span>{path.duration}</span>\n </div>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {path.level}\n </Badge>\n <div className=\"space-y-1 mt-2\">\n {pathCourses.map((c) => (\n <div\n key={c.id}\n className=\"flex items-center gap-2 text-xs text-text-secondary\"\n >\n <GraduationCap className=\"h-3 w-3\" />\n {c.title}\n </div>\n ))}\n </div>\n <div className=\"pt-2 border-t\">\n <div className=\"flex justify-between text-xs mb-1\">\n <span>{path.enrolled} enrolled</span>\n <span>{path.completionRate}%</span>\n </div>\n <div className=\"h-2 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-2 bg-action-primary-bg rounded\"\n style={{ width: `${path.completionRate}%` }}\n />\n </div>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n );\n })\n )}\n </div>\n\n {learningPaths.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={learningPaths.length}\n onPageChange={setPage}\n />\n )}\n\n <FormModal\n open={createModal.open}\n onOpenChange={createModal.onOpenChange}\n title=\"Create Learning Path\"\n description=\"Group courses into a structured journey. You can add courses after creating the path.\"\n fields={PATH_FIELDS}\n submitLabel=\"Create Path\"\n onSubmit={handleCreate}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|