@burdenoff/microfe-crewfoundry 2026.704.1 → 2026.704.2
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.map +1 -1
- package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.js +1 -1
- 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.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":"HiringJobsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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 { Briefcase, MapPin, Plus, Search, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function HiringJobsPagePage() {\n const { t: translate } = useI18n();\n const navigate = useNavigate();\n const { jobPostings } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 12 });\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n\n const filtered = jobPostings.filter((j) =>\n `${j.title} ${j.department} ${j.location}`.toLowerCase().includes(search.toLowerCase())\n );\n const paged = filtered.slice(offset, offset + pageSize);\n const openJob = (id: string) => navigate(
|
|
1
|
+
{"version":3,"file":"HiringJobsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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 { Briefcase, MapPin, Plus, Search, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function HiringJobsPagePage() {\n const { t: translate } = useI18n();\n const navigate = useNavigate();\n const { jobPostings } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 12 });\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n\n const filtered = jobPostings.filter((j) =>\n `${j.title} ${j.department} ${j.location}`.toLowerCase().includes(search.toLowerCase())\n );\n const paged = filtered.slice(offset, offset + pageSize);\n const openJob = (id: string) => navigate(`../../hiring/job-details-page?id=${encodeURIComponent(id)}`);\n const activeJobs = jobPostings.filter((j) => j.status === 'active').length;\n const totalApplicants = jobPostings.reduce((s, j) => s + j.applicants, 0);\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>{t('crewfoundry.hiringJobs.title', 'Job Postings')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {activeJobs} {t('crewfoundry.hiringJobs.summary.activePositions', 'active positions')} ·{' '}\n {totalApplicants}{' '}\n {t('crewfoundry.hiringJobs.summary.totalApplicants', 'total applicants')}\n </p>\n <PagePurpose className=\"mt-1\">\n All your open and past job postings in one place — use it to monitor applicant volume\n and manage what roles you're actively hiring for.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => navigate('../create-job-page')}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {t('crewfoundry.hiringJobs.actions.postNewJob', 'Post New Job')}\n </Button>\n </div>\n\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={t('crewfoundry.hiringJobs.searchPlaceholder', 'Search positions...')}\n className=\"pl-9\"\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPage(1);\n }}\n />\n </div>\n\n {filtered.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job postings found\"\n description=\"Try adjusting your search, or create a new job posting.\"\n />\n )}\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n {paged.map((job, _idx) => (\n <GlassCard\n key={job.id}\n treatment={_idx === 0 ? 'glass' : 'plain'}\n glow={_idx === 0}\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${job.title}`}\n className=\"cursor-pointer transition-shadow hover:shadow-elevation-2 focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n onClick={() => openJob(job.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openJob(job.id);\n }\n }}\n >\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-lg\">{job.title}</CardTitle>\n <Badge\n variant={\n job.status === 'active'\n ? 'default'\n : job.status === 'paused'\n ? 'secondary'\n : 'outline'\n }\n >\n {job.status}\n </Badge>\n </div>\n <CardDescription>{job.description}</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-2 gap-3 mb-3\">\n <div className=\"flex items-center gap-1 text-sm text-text-secondary\">\n <Briefcase className=\"h-3 w-3\" />\n {job.department}\n </div>\n <div className=\"flex items-center gap-1 text-sm text-text-secondary\">\n <MapPin className=\"h-3 w-3\" />\n {job.location}\n </div>\n <div className=\"flex items-center gap-1 text-sm text-text-secondary\">\n <Users className=\"h-3 w-3\" />\n {job.applicants} {t('crewfoundry.hiringJobs.card.applicants', 'applicants')}\n </div>\n <div className=\"text-sm text-text-secondary\">\n ${(job.salaryMin / 1000).toFixed(0)}K — ${(job.salaryMax / 1000).toFixed(0)}K\n </div>\n </div>\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <div className=\"flex gap-1\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {job.type.replace('_', ' ')}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {job.experienceLevel}\n </Badge>\n </div>\n <span>\n {t('crewfoundry.hiringJobs.card.deadline', 'Deadline')}: {job.deadline}\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 </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HrDepartmentsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrDepartmentsPagePage.tsx"],"sourcesContent":["import { 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 Separator,\n} from '@burdenoff/fe-libs/ui';\nimport { Building2, DollarSign, MapPin, Pencil, Plus, Trash2, Users } from 'lucide-react';\nimport { useCrewFoundryStore, type Department } from '../store/crewfoundryStore';\nimport { FormModal, useFormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\n\nconst DEPARTMENT_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Department name', type: 'text', required: true, colSpan: 2 },\n { name: 'headName', label: 'Department head', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n { name: 'budget', label: 'Annual budget', type: 'number', required: true },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n required: true,\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Restructuring', value: 'restructuring' },\n ],\n },\n];\n\nconst toFormValues = (department: Department): FormValues => ({\n name: department.name,\n headName: department.headName,\n location: department.location,\n budget: department.budget,\n status: department.status,\n});\n\nexport function HrDepartmentsPagePage() {\n const { departments, employees, addDepartment, updateDepartment, removeDepartment } =\n useCrewFoundryStore();\n const form = useFormModal<Department>();\n const [deleting, setDeleting] = useState<Department | null>(null);\n\n const totalBudget = departments.reduce((s, d) => s + d.budget, 0);\n const totalHeadcount = departments.reduce((s, d) => s + d.employeeCount, 0);\n\n const editing = form.context;\n\n const handleSubmit = async (values: FormValues) => {\n const payload: Department = {\n id: editing?.id ?? newClientId(),\n name: getText(values, 'name'),\n headId: editing?.headId ?? '',\n headName: getText(values, 'headName'),\n employeeCount: editing?.employeeCount ?? 0,\n budget: getNumber(values, 'budget'),\n location: getText(values, 'location'),\n status: getText(values, 'status') as Department['status'],\n };\n if (editing) {\n await updateDepartment(editing.id, payload);\n } else {\n await addDepartment(payload);\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>Departments</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Organizational structure and department management\n </p>\n <PagePurpose className=\"mt-1\">Organize your company into departments — set up structure, assign heads, and see how headcount is distributed across the org.</PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Department\n </Button>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\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 Departments</CardTitle>\n <Building2 className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{departments.length}</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\">Total Headcount</CardTitle>\n <Users className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalHeadcount}</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\">Total Budget</CardTitle>\n <DollarSign className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(totalBudget / 1000000).toFixed(1)}M\n </div>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n {departments.map((dept) => {\n const deptEmps = employees.filter((e) => e.department === dept.name);\n const avgSalary =\n deptEmps.length > 0\n ? Math.round(deptEmps.reduce((s, e) => s + e.salary, 0) / deptEmps.length)\n : 0;\n const avgPerf =\n deptEmps.length > 0\n ? (deptEmps.reduce((s, e) => s + e.performanceScore, 0) / deptEmps.length).toFixed(1)\n : '0';\n\n return (\n <Card key={dept.id}>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-lg\">{dept.name}</CardTitle>\n <div className=\"flex items-center gap-1\">\n <Badge variant={dept.status === 'active' ? 'default' : 'secondary'}>\n {dept.status}\n </Badge>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Edit ${dept.name}`}\n onClick={() => form.openModal(dept)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Delete ${dept.name}`}\n onClick={() => setDeleting(dept)}\n >\n <Trash2 className=\"h-4 w-4 text-status-error-text\" />\n </Button>\n </div>\n </div>\n <CardDescription className=\"flex items-center gap-1\">\n <MapPin className=\"h-3 w-3\" />\n {dept.location}\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-2 gap-4 mb-4\">\n <div>\n <p className=\"text-xs text-text-secondary\">Department Head</p>\n <p className=\"text-sm font-medium\">{dept.headName}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Headcount</p>\n <p className=\"text-sm font-medium\">{dept.employeeCount} employees</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Annual Budget</p>\n <p className=\"text-sm font-medium\">${(dept.budget / 1000000).toFixed(1)}M</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Avg Salary</p>\n <p className=\"text-sm font-medium\">${avgSalary.toLocaleString()}</p>\n </div>\n </div>\n <Separator className=\"mb-3\" />\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"text-text-secondary\">Avg Performance: {avgPerf}/5</span>\n <span className=\"text-text-secondary\">\n Cost/Head:{' '}\n {dept.employeeCount > 0\n ? `$${Math.round(dept.budget / dept.employeeCount / 1000)}K`\n : '—'}\n </span>\n </div>\n </CardContent>\n </Card>\n );\n })}\n </div>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit department' : 'Add department'}\n description={\n editing\n ? 'Update this department’s details.'\n : 'Create a new department in your organization.'\n }\n fields={DEPARTMENT_FIELDS}\n initialValues={editing ? toFormValues(editing) : undefined}\n submitLabel={editing ? 'Save changes' : 'Add department'}\n onSubmit={handleSubmit}\n />\n\n <ConfirmDialog\n open={deleting !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleting(null);\n }\n }}\n title=\"Delete department?\"\n description={\n deleting\n ? `${deleting.name} will be removed. This cannot be undone.`\n : undefined\n }\n confirmLabel=\"Delete\"\n destructive\n onConfirm={async () => {\n if (deleting) {\n await removeDepartment(deleting.id);\n setDeleting(null);\n }\n }}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HrDepartmentsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrDepartmentsPagePage.tsx"],"sourcesContent":["import { 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 Separator,\n} from '@burdenoff/fe-libs/ui';\nimport { Building2, DollarSign, MapPin, Pencil, Plus, Trash2, Users } from 'lucide-react';\nimport { useCrewFoundryStore, type Department } from '../store/crewfoundryStore';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\n\nconst DEPARTMENT_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Department name', type: 'text', required: true, colSpan: 2 },\n { name: 'headName', label: 'Department head', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n { name: 'budget', label: 'Annual budget', type: 'number', required: true },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n required: true,\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Restructuring', value: 'restructuring' },\n ],\n },\n];\n\nconst toFormValues = (department: Department): FormValues => ({\n name: department.name,\n headName: department.headName,\n location: department.location,\n budget: department.budget,\n status: department.status,\n});\n\nexport function HrDepartmentsPagePage() {\n const { departments, employees, addDepartment, updateDepartment, removeDepartment } =\n useCrewFoundryStore();\n const form = useFormModal<Department>();\n const [deleting, setDeleting] = useState<Department | null>(null);\n\n const totalBudget = departments.reduce((s, d) => s + d.budget, 0);\n const totalHeadcount = departments.reduce((s, d) => s + d.employeeCount, 0);\n\n const editing = form.context;\n\n const handleSubmit = async (values: FormValues) => {\n const payload: Department = {\n id: editing?.id ?? newClientId(),\n name: getText(values, 'name'),\n headId: editing?.headId ?? '',\n headName: getText(values, 'headName'),\n employeeCount: editing?.employeeCount ?? 0,\n budget: getNumber(values, 'budget'),\n location: getText(values, 'location'),\n status: getText(values, 'status') as Department['status'],\n };\n if (editing) {\n await updateDepartment(editing.id, payload);\n } else {\n await addDepartment(payload);\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>Departments</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Organizational structure and department management\n </p>\n <PagePurpose className=\"mt-1\">\n Organize your company into departments — set up structure, assign heads, and see how\n headcount is distributed across the org.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Department\n </Button>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\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 Departments</CardTitle>\n <Building2 className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{departments.length}</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\">Total Headcount</CardTitle>\n <Users className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalHeadcount}</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\">Total Budget</CardTitle>\n <DollarSign className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(totalBudget / 1000000).toFixed(1)}M\n </div>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n {departments.map((dept) => {\n const deptEmps = employees.filter((e) => e.department === dept.name);\n const avgSalary =\n deptEmps.length > 0\n ? Math.round(deptEmps.reduce((s, e) => s + e.salary, 0) / deptEmps.length)\n : 0;\n const avgPerf =\n deptEmps.length > 0\n ? (deptEmps.reduce((s, e) => s + e.performanceScore, 0) / deptEmps.length).toFixed(1)\n : '0';\n\n return (\n <Card key={dept.id}>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-lg\">{dept.name}</CardTitle>\n <div className=\"flex items-center gap-1\">\n <Badge variant={dept.status === 'active' ? 'default' : 'secondary'}>\n {dept.status}\n </Badge>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Edit ${dept.name}`}\n onClick={() => form.openModal(dept)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Delete ${dept.name}`}\n onClick={() => setDeleting(dept)}\n >\n <Trash2 className=\"h-4 w-4 text-status-error-text\" />\n </Button>\n </div>\n </div>\n <CardDescription className=\"flex items-center gap-1\">\n <MapPin className=\"h-3 w-3\" />\n {dept.location}\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-2 gap-4 mb-4\">\n <div>\n <p className=\"text-xs text-text-secondary\">Department Head</p>\n <p className=\"text-sm font-medium\">{dept.headName}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Headcount</p>\n <p className=\"text-sm font-medium\">{dept.employeeCount} employees</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Annual Budget</p>\n <p className=\"text-sm font-medium\">${(dept.budget / 1000000).toFixed(1)}M</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Avg Salary</p>\n <p className=\"text-sm font-medium\">${avgSalary.toLocaleString()}</p>\n </div>\n </div>\n <Separator className=\"mb-3\" />\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"text-text-secondary\">Avg Performance: {avgPerf}/5</span>\n <span className=\"text-text-secondary\">\n Cost/Head:{' '}\n {dept.employeeCount > 0\n ? `$${Math.round(dept.budget / dept.employeeCount / 1000)}K`\n : '—'}\n </span>\n </div>\n </CardContent>\n </Card>\n );\n })}\n </div>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit department' : 'Add department'}\n description={\n editing\n ? 'Update this department’s details.'\n : 'Create a new department in your organization.'\n }\n fields={DEPARTMENT_FIELDS}\n initialValues={editing ? toFormValues(editing) : undefined}\n submitLabel={editing ? 'Save changes' : 'Add department'}\n onSubmit={handleSubmit}\n />\n\n <ConfirmDialog\n open={deleting !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleting(null);\n }\n }}\n title=\"Delete department?\"\n description={\n deleting ? `${deleting.name} will be removed. This cannot be undone.` : undefined\n }\n confirmLabel=\"Delete\"\n destructive\n onConfirm={async () => {\n if (deleting) {\n await removeDepartment(deleting.id);\n setDeleting(null);\n }\n }}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HrEmployeesPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrEmployeesPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport {\n MapPin,\n Pencil,\n Plus,\n Search,\n Star,\n Trash2,\n UserCheck,\n Users,\n} from 'lucide-react';\nimport { useCrewFoundryStore, type Employee } from '../store/crewfoundryStore';\nimport { FormModal, useFormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\n\nconst EMPLOYEE_FIELDS: FieldConfig[] = [\n { name: 'firstName', label: 'First name', type: 'text', required: true },\n { name: 'lastName', label: 'Last name', type: 'text', required: true },\n { name: 'email', label: 'Email', type: 'email', required: true, colSpan: 2 },\n { name: 'position', label: 'Position', type: 'text', required: true },\n { name: 'department', label: 'Department', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n { name: 'phone', label: 'Phone', type: 'text', required: true },\n {\n name: 'type',\n label: 'Employment type',\n type: 'select',\n required: true,\n options: [\n { label: 'Full time', value: 'full_time' },\n { label: 'Part time', value: 'part_time' },\n { label: 'Contract', value: 'contract' },\n { label: 'Intern', value: 'intern' },\n ],\n },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n required: true,\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Inactive', value: 'inactive' },\n { label: 'On leave', value: 'on_leave' },\n { label: 'Terminated', value: 'terminated' },\n ],\n },\n { name: 'salary', label: 'Annual salary', type: 'number', required: true },\n { name: 'startDate', label: 'Start date', type: 'date', required: true },\n { name: 'performanceScore', label: 'Performance score (0-5)', type: 'number' },\n];\n\nconst toFormValues = (employee: Employee): FormValues => ({\n firstName: employee.firstName,\n lastName: employee.lastName,\n email: employee.email,\n position: employee.position,\n department: employee.department,\n location: employee.location,\n phone: employee.phone,\n type: employee.type,\n status: employee.status,\n salary: employee.salary,\n startDate: employee.startDate.slice(0, 10),\n performanceScore: employee.performanceScore,\n});\n\nexport function HrEmployeesPagePage() {\n const { employees, addEmployee, updateEmployee, removeEmployee } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const [filterDept, setFilterDept] = useState<string>('all');\n const form = useFormModal<Employee>();\n const [deleting, setDeleting] = useState<Employee | null>(null);\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n\n const departments = [...new Set(employees.map((e) => e.department))];\n const filtered = employees.filter((e) => {\n const matchSearch = `${e.firstName} ${e.lastName} ${e.email} ${e.position}`\n .toLowerCase()\n .includes(search.toLowerCase());\n const matchDept = filterDept === 'all' || e.department === filterDept;\n return matchSearch && matchDept;\n });\n const pageItems = filtered.slice(offset, offset + pageSize);\n\n const handleSearchChange = (value: string) => {\n setSearch(value);\n setPage(1);\n };\n const handleDeptChange = (value: string) => {\n setFilterDept(value);\n setPage(1);\n };\n\n const activeCount = employees.filter((e) => e.status === 'active').length;\n const onLeaveCount = employees.filter((e) => e.status === 'on_leave').length;\n const avgRating =\n employees.length > 0\n ? (employees.reduce((s, e) => s + e.performanceScore, 0) / employees.length).toFixed(1)\n : '0.0';\n\n const editing = form.context;\n\n const handleSubmit = async (values: FormValues) => {\n const payload: Employee = {\n id: editing?.id ?? newClientId(),\n firstName: getText(values, 'firstName'),\n lastName: getText(values, 'lastName'),\n email: getText(values, 'email'),\n department: getText(values, 'department'),\n position: getText(values, 'position'),\n status: getText(values, 'status') as Employee['status'],\n type: getText(values, 'type') as Employee['type'],\n salary: getNumber(values, 'salary'),\n startDate: getText(values, 'startDate'),\n location: getText(values, 'location'),\n phone: getText(values, 'phone'),\n performanceScore: getNumber(values, 'performanceScore'),\n managerId: editing?.managerId,\n avatar: editing?.avatar,\n };\n if (editing) {\n await updateEmployee(editing.id, payload);\n } else {\n await addEmployee(payload);\n }\n };\n\n const columns: ResponsiveTableColumn<Employee>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (emp) => (\n <div>\n <p className=\"font-medium\">\n {emp.firstName} {emp.lastName}\n </p>\n <p className=\"text-xs text-text-secondary\">{emp.email}</p>\n </div>\n ),\n },\n {\n key: 'position',\n header: 'Position',\n cell: (emp) => <span className=\"text-text-secondary\">{emp.position}</span>,\n },\n {\n key: 'department',\n header: 'Department',\n cell: (emp) => <span>{emp.department}</span>,\n },\n {\n key: 'location',\n header: 'Location',\n cell: (emp) => (\n <div className=\"flex items-center gap-1 text-text-secondary\">\n <MapPin className=\"h-3 w-3\" />\n <span className=\"truncate text-xs\">{emp.location}</span>\n </div>\n ),\n },\n {\n key: 'salary',\n header: 'Salary',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (emp) => <span>${emp.salary.toLocaleString()}</span>,\n },\n {\n key: 'rating',\n header: 'Rating',\n cell: (emp) => (\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n <span>{emp.performanceScore}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cell: (emp) => (\n <Badge\n variant={\n emp.status === 'active'\n ? 'default'\n : emp.status === 'on_leave'\n ? 'secondary'\n : 'destructive'\n }\n className=\"text-[10px]\"\n >\n {emp.status.replace('_', ' ')}\n </Badge>\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>Employee Directory</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {employees.length} employees across {departments.length} departments\n </p>\n <PagePurpose className=\"mt-1\">Your complete employee directory — search, filter, and review everyone on the team along with their role, department, and status.</PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Employee\n </Button>\n </div>\n\n <div className=\"grid gap-4 md: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</CardTitle>\n <Users className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{employees.length}</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\">Active</CardTitle>\n <UserCheck className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{activeCount}</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\">On Leave</CardTitle>\n <Users className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{onLeaveCount}</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\">Avg Rating</CardTitle>\n <Star className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{avgRating}</div>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n <div className=\"relative flex-1 sm: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 employees...\"\n className=\"pl-9\"\n value={search}\n onChange={(e) => handleSearchChange(e.target.value)}\n />\n </div>\n <Select value={filterDept} onValueChange={handleDeptChange}>\n <SelectTrigger className=\"w-full sm:w-56\" aria-label=\"Filter by department\">\n <SelectValue placeholder=\"All departments\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">All departments</SelectItem>\n {departments.map((dept) => (\n <SelectItem key={dept} value={dept}>\n {dept}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <Card>\n <CardContent className=\"space-y-4 p-4\">\n <ResponsiveTable<Employee>\n data={pageItems}\n rowKey={(emp) => emp.id}\n columns={columns}\n rowActions={(emp) => (\n <>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Edit ${emp.firstName} ${emp.lastName}`}\n onClick={() => form.openModal(emp)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Delete ${emp.firstName} ${emp.lastName}`}\n onClick={() => setDeleting(emp)}\n >\n <Trash2 className=\"h-4 w-4 text-status-error-text\" />\n </Button>\n </>\n )}\n emptyState={\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No employees match\"\n description=\"Try a different search term or department filter.\"\n />\n </div>\n }\n />\n {filtered.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n )}\n </CardContent>\n </Card>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit employee' : 'Add employee'}\n description={\n editing\n ? 'Update this team member’s profile details.'\n : 'Add a new team member to your directory.'\n }\n fields={EMPLOYEE_FIELDS}\n initialValues={editing ? toFormValues(editing) : undefined}\n submitLabel={editing ? 'Save changes' : 'Add employee'}\n onSubmit={handleSubmit}\n />\n\n <ConfirmDialog\n open={deleting !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleting(null);\n }\n }}\n title=\"Delete employee?\"\n description={\n deleting\n ? `${deleting.firstName} ${deleting.lastName} will be removed from the directory. This cannot be undone.`\n : undefined\n }\n confirmLabel=\"Delete\"\n destructive\n onConfirm={async () => {\n if (deleting) {\n await removeEmployee(deleting.id);\n setDeleting(null);\n }\n }}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HrEmployeesPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrEmployeesPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { MapPin, Pencil, Plus, Search, Star, Trash2, UserCheck, Users } from 'lucide-react';\nimport { useCrewFoundryStore, type Employee } from '../store/crewfoundryStore';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\n\nconst EMPLOYEE_FIELDS: FieldConfig[] = [\n { name: 'firstName', label: 'First name', type: 'text', required: true },\n { name: 'lastName', label: 'Last name', type: 'text', required: true },\n { name: 'email', label: 'Email', type: 'email', required: true, colSpan: 2 },\n { name: 'position', label: 'Position', type: 'text', required: true },\n { name: 'department', label: 'Department', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n { name: 'phone', label: 'Phone', type: 'text', required: true },\n {\n name: 'type',\n label: 'Employment type',\n type: 'select',\n required: true,\n options: [\n { label: 'Full time', value: 'full_time' },\n { label: 'Part time', value: 'part_time' },\n { label: 'Contract', value: 'contract' },\n { label: 'Intern', value: 'intern' },\n ],\n },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n required: true,\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Inactive', value: 'inactive' },\n { label: 'On leave', value: 'on_leave' },\n { label: 'Terminated', value: 'terminated' },\n ],\n },\n { name: 'salary', label: 'Annual salary', type: 'number', required: true },\n { name: 'startDate', label: 'Start date', type: 'date', required: true },\n { name: 'performanceScore', label: 'Performance score (0-5)', type: 'number' },\n];\n\nconst toFormValues = (employee: Employee): FormValues => ({\n firstName: employee.firstName,\n lastName: employee.lastName,\n email: employee.email,\n position: employee.position,\n department: employee.department,\n location: employee.location,\n phone: employee.phone,\n type: employee.type,\n status: employee.status,\n salary: employee.salary,\n startDate: employee.startDate.slice(0, 10),\n performanceScore: employee.performanceScore,\n});\n\nexport function HrEmployeesPagePage() {\n const { employees, addEmployee, updateEmployee, removeEmployee } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const [filterDept, setFilterDept] = useState<string>('all');\n const form = useFormModal<Employee>();\n const [deleting, setDeleting] = useState<Employee | null>(null);\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n\n const departments = [...new Set(employees.map((e) => e.department))];\n const filtered = employees.filter((e) => {\n const matchSearch = `${e.firstName} ${e.lastName} ${e.email} ${e.position}`\n .toLowerCase()\n .includes(search.toLowerCase());\n const matchDept = filterDept === 'all' || e.department === filterDept;\n return matchSearch && matchDept;\n });\n const pageItems = filtered.slice(offset, offset + pageSize);\n\n const handleSearchChange = (value: string) => {\n setSearch(value);\n setPage(1);\n };\n const handleDeptChange = (value: string) => {\n setFilterDept(value);\n setPage(1);\n };\n\n const activeCount = employees.filter((e) => e.status === 'active').length;\n const onLeaveCount = employees.filter((e) => e.status === 'on_leave').length;\n const avgRating =\n employees.length > 0\n ? (employees.reduce((s, e) => s + e.performanceScore, 0) / employees.length).toFixed(1)\n : '0.0';\n\n const editing = form.context;\n\n const handleSubmit = async (values: FormValues) => {\n const payload: Employee = {\n id: editing?.id ?? newClientId(),\n firstName: getText(values, 'firstName'),\n lastName: getText(values, 'lastName'),\n email: getText(values, 'email'),\n department: getText(values, 'department'),\n position: getText(values, 'position'),\n status: getText(values, 'status') as Employee['status'],\n type: getText(values, 'type') as Employee['type'],\n salary: getNumber(values, 'salary'),\n startDate: getText(values, 'startDate'),\n location: getText(values, 'location'),\n phone: getText(values, 'phone'),\n performanceScore: getNumber(values, 'performanceScore'),\n managerId: editing?.managerId,\n avatar: editing?.avatar,\n };\n if (editing) {\n await updateEmployee(editing.id, payload);\n } else {\n await addEmployee(payload);\n }\n };\n\n const columns: ResponsiveTableColumn<Employee>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (emp) => (\n <div>\n <p className=\"font-medium\">\n {emp.firstName} {emp.lastName}\n </p>\n <p className=\"text-xs text-text-secondary\">{emp.email}</p>\n </div>\n ),\n },\n {\n key: 'position',\n header: 'Position',\n cell: (emp) => <span className=\"text-text-secondary\">{emp.position}</span>,\n },\n {\n key: 'department',\n header: 'Department',\n cell: (emp) => <span>{emp.department}</span>,\n },\n {\n key: 'location',\n header: 'Location',\n cell: (emp) => (\n <div className=\"flex items-center gap-1 text-text-secondary\">\n <MapPin className=\"h-3 w-3\" />\n <span className=\"truncate text-xs\">{emp.location}</span>\n </div>\n ),\n },\n {\n key: 'salary',\n header: 'Salary',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (emp) => <span>${emp.salary.toLocaleString()}</span>,\n },\n {\n key: 'rating',\n header: 'Rating',\n cell: (emp) => (\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n <span>{emp.performanceScore}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cell: (emp) => (\n <Badge\n variant={\n emp.status === 'active'\n ? 'default'\n : emp.status === 'on_leave'\n ? 'secondary'\n : 'destructive'\n }\n className=\"text-[10px]\"\n >\n {emp.status.replace('_', ' ')}\n </Badge>\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>Employee Directory</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {employees.length} employees across {departments.length} departments\n </p>\n <PagePurpose className=\"mt-1\">\n Your complete employee directory — search, filter, and review everyone on the team along\n with their role, department, and status.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Employee\n </Button>\n </div>\n\n <div className=\"grid gap-4 md: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</CardTitle>\n <Users className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{employees.length}</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\">Active</CardTitle>\n <UserCheck className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{activeCount}</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\">On Leave</CardTitle>\n <Users className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{onLeaveCount}</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\">Avg Rating</CardTitle>\n <Star className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{avgRating}</div>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n <div className=\"relative flex-1 sm: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 employees...\"\n className=\"pl-9\"\n value={search}\n onChange={(e) => handleSearchChange(e.target.value)}\n />\n </div>\n <Select value={filterDept} onValueChange={handleDeptChange}>\n <SelectTrigger className=\"w-full sm:w-56\" aria-label=\"Filter by department\">\n <SelectValue placeholder=\"All departments\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">All departments</SelectItem>\n {departments.map((dept) => (\n <SelectItem key={dept} value={dept}>\n {dept}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <Card>\n <CardContent className=\"space-y-4 p-4\">\n <ResponsiveTable<Employee>\n data={pageItems}\n rowKey={(emp) => emp.id}\n columns={columns}\n rowActions={(emp) => (\n <>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Edit ${emp.firstName} ${emp.lastName}`}\n onClick={() => form.openModal(emp)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Delete ${emp.firstName} ${emp.lastName}`}\n onClick={() => setDeleting(emp)}\n >\n <Trash2 className=\"h-4 w-4 text-status-error-text\" />\n </Button>\n </>\n )}\n emptyState={\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No employees match\"\n description=\"Try a different search term or department filter.\"\n />\n </div>\n }\n />\n {filtered.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n )}\n </CardContent>\n </Card>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit employee' : 'Add employee'}\n description={\n editing\n ? 'Update this team member’s profile details.'\n : 'Add a new team member to your directory.'\n }\n fields={EMPLOYEE_FIELDS}\n initialValues={editing ? toFormValues(editing) : undefined}\n submitLabel={editing ? 'Save changes' : 'Add employee'}\n onSubmit={handleSubmit}\n />\n\n <ConfirmDialog\n open={deleting !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleting(null);\n }\n }}\n title=\"Delete employee?\"\n description={\n deleting\n ? `${deleting.firstName} ${deleting.lastName} will be removed from the directory. This cannot be undone.`\n : undefined\n }\n confirmLabel=\"Delete\"\n destructive\n onConfirm={async () => {\n if (deleting) {\n await removeEmployee(deleting.id);\n setDeleting(null);\n }\n }}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HrLeaveManagementPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrLeaveManagementPagePage.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 ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { useAuth, useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Calendar, CheckCircle, Clock, XCircle } from 'lucide-react';\nimport { useCrewFoundryStore, type LeaveRequest } from '../store/crewfoundryStore';\nimport { FormModal, useFormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\n\ntype LeaveBalance = {\n type: string;\n total: number;\n used: number;\n remaining: number;\n};\n\nconst LEAVE_TYPE_OPTIONS = [\n { label: 'Annual', value: 'annual' },\n { label: 'Sick', value: 'sick' },\n { label: 'Personal', value: 'personal' },\n { label: 'Maternity', value: 'maternity' },\n { label: 'Paternity', value: 'paternity' },\n { label: 'Unpaid', value: 'unpaid' },\n];\n\nconst toLabel = (value: string): string =>\n value\n .split('_')\n .map((part) => part[0]?.toUpperCase() + part.slice(1))\n .join(' ');\n\nexport function HrLeaveManagementPagePage() {\n const { leaveRequests, employees, addLeaveRequest, updateLeaveRequest } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const { currentUser } = useAuth();\n const form = useFormModal<void>();\n\n const approverName =\n currentUser?.name ||\n [currentUser?.firstName, currentUser?.lastName].filter(Boolean).join(' ') ||\n currentUser?.email ||\n 'Manager';\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 leaveFields = 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: 'type', label: 'Leave type', type: 'select', required: true, options: LEAVE_TYPE_OPTIONS },\n { name: 'days', label: 'Number of days', type: 'number', required: true },\n { name: 'startDate', label: 'Start date', type: 'date', required: true },\n { name: 'endDate', label: 'End date', type: 'date', required: true },\n { name: 'reason', label: 'Reason', type: 'textarea', required: true, colSpan: 2 },\n ],\n [employeeOptions]\n );\n\n const pendingCount = leaveRequests.filter((leave) => leave.status === 'pending').length;\n const approvedCount = leaveRequests.filter((leave) => leave.status === 'approved').length;\n const totalDays = leaveRequests\n .filter((leave) => leave.status === 'approved')\n .reduce((sum, leave) => sum + leave.days, 0);\n\n const leaveBalances = useMemo<LeaveBalance[]>(() => {\n // Default annual entitlement per leave type (company policy defaults).\n // \"Used\" below is derived from real approved leave requests in the store.\n const entitlementByType: Record<string, number> = {\n annual: 20,\n sick: 12,\n personal: 5,\n maternity: 90,\n paternity: 30,\n unpaid: 15,\n };\n\n const usedByType = leaveRequests\n .filter((leave) => leave.status === 'approved')\n .reduce<Record<string, number>>((acc, leave) => {\n acc[leave.type] = (acc[leave.type] || 0) + leave.days;\n return acc;\n }, {});\n\n return Object.entries(entitlementByType).map(([type, total]) => {\n const used = usedByType[type] || 0;\n return {\n type: toLabel(type),\n total,\n used,\n remaining: Math.max(total - used, 0),\n };\n });\n }, [leaveRequests]);\n\n const onLeaveToday = useMemo(() => {\n const today = new Date().toISOString().split('T')[0];\n return leaveRequests.filter(\n (leave) => leave.status === 'approved' && leave.startDate <= today && leave.endDate >= today\n ).length;\n }, [leaveRequests]);\n\n const pendingList = useMemo(\n () => leaveRequests.filter((leave) => leave.status === 'pending'),\n [leaveRequests]\n );\n const sortedRequests = useMemo(\n () => [...leaveRequests].sort((a, b) => b.startDate.localeCompare(a.startDate)),\n [leaveRequests]\n );\n\n const pendingPg = usePagination({ initialPageSize: 5 });\n const allPg = usePagination({ initialPageSize: 10 });\n const pendingPageItems = pendingList.slice(\n pendingPg.offset,\n pendingPg.offset + pendingPg.pageSize\n );\n const requestPageItems = sortedRequests.slice(allPg.offset, allPg.offset + allPg.pageSize);\n\n const requestColumns: ResponsiveTableColumn<LeaveRequest>[] = [\n {\n key: 'employee',\n header: 'Employee',\n priority: 'primary',\n cell: (leave) => <span className=\"font-medium\">{leave.employeeName}</span>,\n },\n {\n key: 'department',\n header: 'Department',\n cell: (leave) => <span className=\"text-text-secondary\">{leave.department}</span>,\n },\n {\n key: 'type',\n header: 'Type',\n cell: (leave) => (\n <Badge variant=\"outline\" className=\"w-fit text-[10px]\">\n {leave.type}\n </Badge>\n ),\n },\n {\n key: 'period',\n header: 'Period',\n cell: (leave) => (\n <span className=\"text-xs\">\n {leave.startDate} — {leave.endDate}\n </span>\n ),\n },\n {\n key: 'days',\n header: 'Days',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (leave) => <span>{leave.days}</span>,\n },\n {\n key: 'reason',\n header: 'Reason',\n cell: (leave) => (\n <span className=\"block max-w-[16rem] truncate text-xs text-text-secondary\">\n {leave.reason}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cell: (leave) => (\n <Badge\n variant={\n leave.status === 'approved'\n ? 'default'\n : leave.status === 'pending'\n ? 'secondary'\n : 'destructive'\n }\n className=\"text-[10px]\"\n >\n {leave.status}\n </Badge>\n ),\n },\n ];\n\n const handleSubmit = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const payload: LeaveRequest = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n department: employee?.department ?? '',\n type: getText(values, 'type') as LeaveRequest['type'],\n startDate: getText(values, 'startDate'),\n endDate: getText(values, 'endDate'),\n days: getNumber(values, 'days'),\n status: 'pending',\n reason: getText(values, 'reason'),\n };\n await addLeaveRequest(payload);\n };\n\n const handleDecision = async (leave: LeaveRequest, status: 'approved' | 'rejected') => {\n try {\n await updateLeaveRequest(leave.id, { status, approverName });\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to update leave request', '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>Leave Management</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Track and manage employee leave requests</p>\n <PagePurpose className=\"mt-1\">Handle time-off in one place — review and approve leave requests and keep an eye on team availability and balances.</PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Calendar className=\"mr-1 h-4 w-4\" />\n Request Leave\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\">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\">{pendingCount}</div>\n <p className=\"text-xs text-text-secondary\">Awaiting approval</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\">{approvedCount}</div>\n <p className=\"text-xs text-text-secondary\">Current period</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\">Days Used</CardTitle>\n <Calendar className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalDays}</div>\n <p className=\"text-xs text-text-secondary\">Total approved days</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\">On Leave Today</CardTitle>\n <XCircle className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{onLeaveToday}</div>\n <p className=\"text-xs text-text-secondary\">Out of office</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Leave Balances</CardTitle>\n <CardDescription>\n Company-wide leave allocation (default policy) vs. approved days used\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {leaveBalances.map((balance) => {\n const usedPct = Math.round((balance.used / Math.max(balance.total, 1)) * 100);\n return (\n <div key={balance.type} className=\"space-y-1\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"font-medium\">{balance.type}</span>\n <span>\n {balance.used}/{balance.total} days used\n </span>\n </div>\n <div className=\"h-2 overflow-hidden rounded bg-bg-sunken\">\n <div\n className={`h-2 rounded ${\n usedPct > 80\n ? 'bg-status-error-text'\n : usedPct > 50\n ? 'bg-status-warning-text'\n : 'bg-status-success-text'\n }`}\n style={{ width: `${usedPct}%` }}\n />\n </div>\n </div>\n );\n })}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Pending Approvals</CardTitle>\n <CardDescription>Leave requests waiting for your review</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {pendingPageItems.map((leave) => (\n <div\n key={leave.id}\n className=\"flex items-center justify-between rounded border p-3\"\n >\n <div>\n <p className=\"text-sm font-medium\">{leave.employeeName}</p>\n <p className=\"text-xs text-text-secondary\">\n {leave.type} · {leave.days} days · {leave.startDate} to {leave.endDate}\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">{leave.reason}</p>\n </div>\n <div className=\"flex gap-1\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs\"\n onClick={() => handleDecision(leave, 'rejected')}\n >\n Reject\n </Button>\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n onClick={() => handleDecision(leave, 'approved')}\n >\n Approve\n </Button>\n </div>\n </div>\n ))}\n {pendingCount === 0 && (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No leave requests are awaiting approval.\n </p>\n )}\n {pendingList.length > pendingPg.pageSize && (\n <Pagination\n page={pendingPg.page}\n pageSize={pendingPg.pageSize}\n totalItems={pendingList.length}\n onPageChange={pendingPg.setPage}\n />\n )}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>All Leave Requests</CardTitle>\n <CardDescription>Complete leave request history</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <ResponsiveTable<LeaveRequest>\n data={requestPageItems}\n rowKey={(leave) => leave.id}\n columns={requestColumns}\n emptyState={\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No leave requests yet.\n </p>\n }\n />\n {sortedRequests.length > allPg.pageSize && (\n <Pagination\n page={allPg.page}\n pageSize={allPg.pageSize}\n totalItems={sortedRequests.length}\n onPageChange={allPg.setPage}\n />\n )}\n </CardContent>\n </Card>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title=\"Request leave\"\n description=\"Submit a new time-off request for review.\"\n fields={leaveFields}\n submitLabel=\"Submit request\"\n onSubmit={handleSubmit}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AA8BA,IAAM,IAAqB;CACzB;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAQ,OAAO;EAAQ;CAChC;EAAE,OAAO;EAAY,OAAO;EAAY;CACxC;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAU,OAAO;EAAU;CACrC,EAEK,KAAW,MACf,EACG,MAAM,IAAI,CACV,KAAK,MAAS,EAAK,IAAI,aAAa,GAAG,EAAK,MAAM,EAAE,CAAC,CACrD,KAAK,IAAI;AAEd,SAAgB,IAA4B;CAC1C,IAAM,EAAE,kBAAe,cAAW,oBAAiB,0BAAuB,GAAqB,EACzF,EAAE,iBAAc,GAAa,EAC7B,EAAE,mBAAgB,GAAS,EAC3B,IAAO,GAAoB,EAE3B,IACJ,GAAa,QACb,CAAC,GAAa,WAAW,GAAa,SAAS,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI,IACzE,GAAa,SACb,WAEI,IAAkB,QAEpB,EAAU,KAAK,OAAc;EAC3B,OAAO,GAAG,EAAS,UAAU,GAAG,EAAS;EACzC,OAAO,EAAS;EACjB,EAAE,EACL,CAAC,EAAU,CACZ,EAEK,IAAc,QACZ;EACJ;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACN,UAAU;GACV,SAAS;GACT,SAAS;GACV;EACD;GAAE,MAAM;GAAQ,OAAO;GAAc,MAAM;GAAU,UAAU;GAAM,SAAS;GAAoB;EAClG;GAAE,MAAM;GAAQ,OAAO;GAAkB,MAAM;GAAU,UAAU;GAAM;EACzE;GAAE,MAAM;GAAa,OAAO;GAAc,MAAM;GAAQ,UAAU;GAAM;EACxE;GAAE,MAAM;GAAW,OAAO;GAAY,MAAM;GAAQ,UAAU;GAAM;EACpE;GAAE,MAAM;GAAU,OAAO;GAAU,MAAM;GAAY,UAAU;GAAM,SAAS;GAAG;EAClF,EACD,CAAC,EAAgB,CAClB,EAEK,IAAe,EAAc,QAAQ,MAAU,EAAM,WAAW,UAAU,CAAC,QAC3E,IAAgB,EAAc,QAAQ,MAAU,EAAM,WAAW,WAAW,CAAC,QAC7E,IAAY,EACf,QAAQ,MAAU,EAAM,WAAW,WAAW,CAC9C,QAAQ,GAAK,MAAU,IAAM,EAAM,MAAM,EAAE,EAExC,IAAgB,QAA8B;EAGlD,IAAM,IAA4C;GAChD,QAAQ;GACR,MAAM;GACN,UAAU;GACV,WAAW;GACX,WAAW;GACX,QAAQ;GACT,EAEK,IAAa,EAChB,QAAQ,MAAU,EAAM,WAAW,WAAW,CAC9C,QAAgC,GAAK,OACpC,EAAI,EAAM,SAAS,EAAI,EAAM,SAAS,KAAK,EAAM,MAC1C,IACN,EAAE,CAAC;AAER,SAAO,OAAO,QAAQ,EAAkB,CAAC,KAAK,CAAC,GAAM,OAAW;GAC9D,IAAM,IAAO,EAAW,MAAS;AACjC,UAAO;IACL,MAAM,EAAQ,EAAK;IACnB;IACA;IACA,WAAW,KAAK,IAAI,IAAQ,GAAM,EAAE;IACrC;IACD;IACD,CAAC,EAAc,CAAC,EAEb,IAAe,QAAc;EACjC,IAAM,qBAAQ,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,IAAI,CAAC;AAClD,SAAO,EAAc,QAClB,MAAU,EAAM,WAAW,cAAc,EAAM,aAAa,KAAS,EAAM,WAAW,EACxF,CAAC;IACD,CAAC,EAAc,CAAC,EAEb,IAAc,QACZ,EAAc,QAAQ,MAAU,EAAM,WAAW,UAAU,EACjE,CAAC,EAAc,CAChB,EACK,IAAiB,QACf,CAAC,GAAG,EAAc,CAAC,MAAM,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,UAAU,CAAC,EAC/E,CAAC,EAAc,CAChB,EAEK,IAAY,EAAc,EAAE,iBAAiB,GAAG,CAAC,EACjD,IAAQ,EAAc,EAAE,iBAAiB,IAAI,CAAC,EAC9C,IAAmB,EAAY,MACnC,EAAU,QACV,EAAU,SAAS,EAAU,SAC9B,EACK,IAAmB,EAAe,MAAM,EAAM,QAAQ,EAAM,SAAS,EAAM,SAAS,EAEpF,IAAwD;EAC5D;GACE,KAAK;GACL,QAAQ;GACR,UAAU;GACV,OAAO,MAAU,kBAAC,QAAD;IAAM,WAAU;cAAe,EAAM;IAAoB,CAAA;GAC3E;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO,MAAU,kBAAC,QAAD;IAAM,WAAU;cAAuB,EAAM;IAAkB,CAAA;GACjF;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO,MACL,kBAAC,GAAD;IAAO,SAAQ;IAAU,WAAU;cAChC,EAAM;IACD,CAAA;GAEX;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO,MACL,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACG,EAAM;KAAU;KAAI,EAAM;KACtB;;GAEV;EACD;GACE,KAAK;GACL,QAAQ;GACR,iBAAiB;GACjB,eAAe;GACf,OAAO,MAAU,kBAAC,QAAD,EAAA,UAAO,EAAM,MAAY,CAAA;GAC3C;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO,MACL,kBAAC,QAAD;IAAM,WAAU;cACb,EAAM;IACF,CAAA;GAEV;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO,MACL,kBAAC,GAAD;IACE,SACE,EAAM,WAAW,aACb,YACA,EAAM,WAAW,YACf,cACA;IAER,WAAU;cAET,EAAM;IACD,CAAA;GAEX;EACF,EAEK,IAAe,OAAO,MAAuB;EACjD,IAAM,IAAa,EAAQ,GAAQ,aAAa,EAC1C,IAAW,EAAU,MAAM,MAAS,EAAK,OAAO,EAAW;AAajE,QAAM,EAZwB;GAC5B,IAAI,GAAa;GACjB;GACA,cAAc,IAAW,GAAG,EAAS,UAAU,GAAG,EAAS,aAAa;GACxE,YAAY,GAAU,cAAc;GACpC,MAAM,EAAQ,GAAQ,OAAO;GAC7B,WAAW,EAAQ,GAAQ,YAAY;GACvC,SAAS,EAAQ,GAAQ,UAAU;GACnC,MAAM,EAAU,GAAQ,OAAO;GAC/B,QAAQ;GACR,QAAQ,EAAQ,GAAQ,SAAS;GAClC,CAC6B;IAG1B,IAAiB,OAAO,GAAqB,MAAoC;AACrF,MAAI;AACF,SAAM,EAAmB,EAAM,IAAI;IAAE;IAAQ;IAAc,CAAC;WACrD,GAAO;AACd,KAAU,aAAiB,QAAQ,EAAM,UAAU,kCAAkC,QAAQ;;;AAIjG,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA;KACE,kBAAC,MAAD;MAAI,WAAU;gBACZ,kBAAC,GAAD,EAAA,UAAc,oBAA+B,CAAA;MAC1C,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAA4C,CAAA;KACvF,kBAAC,GAAD;MAAa,WAAU;gBAAO;MAAiI,CAAA;KAC3J,EAAA,CAAA,EACN,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAK,WAAW;eAAjD,CACE,kBAAC,GAAD,EAAU,WAAU,gBAAiB,CAAA,EAAA,gBAE9B;OACL;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAW,WAAU;MAAQ,MAAA;MAAK,WAAU;gBAA5C,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAAmB,CAAA,EAC9D,kBAAC,GAAD,EAAO,WAAU,oCAAqC,CAAA,CAC3C;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAmC;OAAmB,CAAA,EACrE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAqB,CAAA,CACpD,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAAoB,CAAA,EAC/D,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA,CACjD;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAmC;OAAoB,CAAA,EACtE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAkB,CAAA,CACjD,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAAqB,CAAA,EAChE,kBAAC,GAAD,EAAU,WAAU,+BAAgC,CAAA,CACzC;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAmC;OAAgB,CAAA,EAClE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAuB,CAAA,CACtD,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAA0B,CAAA,EACrE,kBAAC,GAAD,EAAS,WAAU,+BAAgC,CAAA,CACxC;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAmC;OAAmB,CAAA,EACrE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAiB,CAAA,CAChD,EAAA,CAAA,CACJ;;KACR;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,kBAA0B,CAAA,EACrC,kBAAC,GAAD,EAAA,UAAiB,yEAEC,CAAA,CACP,EAAA,CAAA,EACb,kBAAC,GAAD;KAAa,WAAU;eACpB,EAAc,KAAK,MAAY;MAC9B,IAAM,IAAU,KAAK,MAAO,EAAQ,OAAO,KAAK,IAAI,EAAQ,OAAO,EAAE,GAAI,IAAI;AAC7E,aACE,kBAAC,OAAD;OAAwB,WAAU;iBAAlC,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAe,EAAQ;SAAY,CAAA,EACnD,kBAAC,QAAD,EAAA,UAAA;SACG,EAAQ;SAAK;SAAE,EAAQ;SAAM;SACzB,EAAA,CAAA,CACH;WACN,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,OAAD;SACE,WAAW,eACT,IAAU,KACN,yBACA,IAAU,KACR,2BACA;SAER,OAAO,EAAE,OAAO,GAAG,EAAQ,IAAI;SAC/B,CAAA;QACE,CAAA,CACF;SAnBI,EAAQ,KAmBZ;OAER;KACU,CAAA,CACT,EAAA,CAAA,EAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,qBAA6B,CAAA,EACxC,kBAAC,GAAD,EAAA,UAAiB,0CAAwD,CAAA,CAC9D,EAAA,CAAA,EACb,kBAAC,GAAD;KAAa,WAAU;eAAvB;MACG,EAAiB,KAAK,MACrB,kBAAC,OAAD;OAEE,WAAU;iBAFZ,CAIE,kBAAC,OAAD,EAAA,UAAA;QACE,kBAAC,KAAD;SAAG,WAAU;mBAAuB,EAAM;SAAiB,CAAA;QAC3D,kBAAC,KAAD;SAAG,WAAU;mBAAb;UACG,EAAM;UAAK;UAAI,EAAM;UAAK;UAAS,EAAM;UAAU;UAAK,EAAM;UAC7D;;QACJ,kBAAC,KAAD;SAAG,WAAU;mBAAoC,EAAM;SAAW,CAAA;QAC9D,EAAA,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SACE,MAAK;SACL,SAAQ;SACR,WAAU;SACV,eAAe,EAAe,GAAO,WAAW;mBACjD;SAEQ,CAAA,EACT,kBAAC,GAAD;SACE,MAAK;SACL,WAAU;SACV,eAAe,EAAe,GAAO,WAAW;mBACjD;SAEQ,CAAA,CACL;UACF;SA3BC,EAAM,GA2BP,CACN;MACD,MAAiB,KAChB,kBAAC,KAAD;OAAG,WAAU;iBAA+C;OAExD,CAAA;MAEL,EAAY,SAAS,EAAU,YAC9B,kBAAC,GAAD;OACE,MAAM,EAAU;OAChB,UAAU,EAAU;OACpB,YAAY,EAAY;OACxB,cAAc,EAAU;OACxB,CAAA;MAEQ;OACT,EAAA,CAAA,CACH;;GAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,sBAA8B,CAAA,EACzC,kBAAC,GAAD,EAAA,UAAiB,kCAAgD,CAAA,CACtD,EAAA,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cAAvB,CACE,kBAAC,GAAD;KACE,MAAM;KACN,SAAS,MAAU,EAAM;KACzB,SAAS;KACT,YACE,kBAAC,KAAD;MAAG,WAAU;gBAA+C;MAExD,CAAA;KAEN,CAAA,EACD,EAAe,SAAS,EAAM,YAC7B,kBAAC,GAAD;KACE,MAAM,EAAM;KACZ,UAAU,EAAM;KAChB,YAAY,EAAe;KAC3B,cAAc,EAAM;KACpB,CAAA,CAEQ;MACT,EAAA,CAAA;GAEP,kBAAC,GAAD;IACE,MAAM,EAAK;IACX,cAAc,EAAK;IACnB,OAAM;IACN,aAAY;IACZ,QAAQ;IACR,aAAY;IACZ,UAAU;IACV,CAAA;GACE"}
|
|
1
|
+
{"version":3,"file":"HrLeaveManagementPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrLeaveManagementPagePage.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 ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { useAuth, useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Calendar, CheckCircle, Clock, XCircle } from 'lucide-react';\nimport { useCrewFoundryStore, type LeaveRequest } 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\ntype LeaveBalance = {\n type: string;\n total: number;\n used: number;\n remaining: number;\n};\n\nconst LEAVE_TYPE_OPTIONS = [\n { label: 'Annual', value: 'annual' },\n { label: 'Sick', value: 'sick' },\n { label: 'Personal', value: 'personal' },\n { label: 'Maternity', value: 'maternity' },\n { label: 'Paternity', value: 'paternity' },\n { label: 'Unpaid', value: 'unpaid' },\n];\n\nconst toLabel = (value: string): string =>\n value\n .split('_')\n .map((part) => part[0]?.toUpperCase() + part.slice(1))\n .join(' ');\n\nexport function HrLeaveManagementPagePage() {\n const { leaveRequests, employees, addLeaveRequest, updateLeaveRequest } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const { currentUser } = useAuth();\n const form = useFormModal<void>();\n\n const approverName =\n currentUser?.name ||\n [currentUser?.firstName, currentUser?.lastName].filter(Boolean).join(' ') ||\n currentUser?.email ||\n 'Manager';\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 leaveFields = 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: 'type',\n label: 'Leave type',\n type: 'select',\n required: true,\n options: LEAVE_TYPE_OPTIONS,\n },\n { name: 'days', label: 'Number of days', type: 'number', required: true },\n { name: 'startDate', label: 'Start date', type: 'date', required: true },\n { name: 'endDate', label: 'End date', type: 'date', required: true },\n { name: 'reason', label: 'Reason', type: 'textarea', required: true, colSpan: 2 },\n ],\n [employeeOptions]\n );\n\n const pendingCount = leaveRequests.filter((leave) => leave.status === 'pending').length;\n const approvedCount = leaveRequests.filter((leave) => leave.status === 'approved').length;\n const totalDays = leaveRequests\n .filter((leave) => leave.status === 'approved')\n .reduce((sum, leave) => sum + leave.days, 0);\n\n const leaveBalances = useMemo<LeaveBalance[]>(() => {\n // Default annual entitlement per leave type (company policy defaults).\n // \"Used\" below is derived from real approved leave requests in the store.\n const entitlementByType: Record<string, number> = {\n annual: 20,\n sick: 12,\n personal: 5,\n maternity: 90,\n paternity: 30,\n unpaid: 15,\n };\n\n const usedByType = leaveRequests\n .filter((leave) => leave.status === 'approved')\n .reduce<Record<string, number>>((acc, leave) => {\n acc[leave.type] = (acc[leave.type] || 0) + leave.days;\n return acc;\n }, {});\n\n return Object.entries(entitlementByType).map(([type, total]) => {\n const used = usedByType[type] || 0;\n return {\n type: toLabel(type),\n total,\n used,\n remaining: Math.max(total - used, 0),\n };\n });\n }, [leaveRequests]);\n\n const onLeaveToday = useMemo(() => {\n const today = new Date().toISOString().split('T')[0];\n return leaveRequests.filter(\n (leave) => leave.status === 'approved' && leave.startDate <= today && leave.endDate >= today\n ).length;\n }, [leaveRequests]);\n\n const pendingList = useMemo(\n () => leaveRequests.filter((leave) => leave.status === 'pending'),\n [leaveRequests]\n );\n const sortedRequests = useMemo(\n () => [...leaveRequests].sort((a, b) => b.startDate.localeCompare(a.startDate)),\n [leaveRequests]\n );\n\n const pendingPg = usePagination({ initialPageSize: 5 });\n const allPg = usePagination({ initialPageSize: 10 });\n const pendingPageItems = pendingList.slice(\n pendingPg.offset,\n pendingPg.offset + pendingPg.pageSize\n );\n const requestPageItems = sortedRequests.slice(allPg.offset, allPg.offset + allPg.pageSize);\n\n const requestColumns: ResponsiveTableColumn<LeaveRequest>[] = [\n {\n key: 'employee',\n header: 'Employee',\n priority: 'primary',\n cell: (leave) => <span className=\"font-medium\">{leave.employeeName}</span>,\n },\n {\n key: 'department',\n header: 'Department',\n cell: (leave) => <span className=\"text-text-secondary\">{leave.department}</span>,\n },\n {\n key: 'type',\n header: 'Type',\n cell: (leave) => (\n <Badge variant=\"outline\" className=\"w-fit text-[10px]\">\n {leave.type}\n </Badge>\n ),\n },\n {\n key: 'period',\n header: 'Period',\n cell: (leave) => (\n <span className=\"text-xs\">\n {leave.startDate} — {leave.endDate}\n </span>\n ),\n },\n {\n key: 'days',\n header: 'Days',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (leave) => <span>{leave.days}</span>,\n },\n {\n key: 'reason',\n header: 'Reason',\n cell: (leave) => (\n <span className=\"block max-w-[16rem] truncate text-xs text-text-secondary\">\n {leave.reason}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cell: (leave) => (\n <Badge\n variant={\n leave.status === 'approved'\n ? 'default'\n : leave.status === 'pending'\n ? 'secondary'\n : 'destructive'\n }\n className=\"text-[10px]\"\n >\n {leave.status}\n </Badge>\n ),\n },\n ];\n\n const handleSubmit = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const payload: LeaveRequest = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n department: employee?.department ?? '',\n type: getText(values, 'type') as LeaveRequest['type'],\n startDate: getText(values, 'startDate'),\n endDate: getText(values, 'endDate'),\n days: getNumber(values, 'days'),\n status: 'pending',\n reason: getText(values, 'reason'),\n };\n await addLeaveRequest(payload);\n };\n\n const handleDecision = async (leave: LeaveRequest, status: 'approved' | 'rejected') => {\n try {\n await updateLeaveRequest(leave.id, { status, approverName });\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to update leave request', '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>Leave Management</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Track and manage employee leave requests</p>\n <PagePurpose className=\"mt-1\">\n Handle time-off in one place — review and approve leave requests and keep an eye on team\n availability and balances.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Calendar className=\"mr-1 h-4 w-4\" />\n Request Leave\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\">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\">{pendingCount}</div>\n <p className=\"text-xs text-text-secondary\">Awaiting approval</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\">{approvedCount}</div>\n <p className=\"text-xs text-text-secondary\">Current period</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\">Days Used</CardTitle>\n <Calendar className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalDays}</div>\n <p className=\"text-xs text-text-secondary\">Total approved days</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\">On Leave Today</CardTitle>\n <XCircle className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{onLeaveToday}</div>\n <p className=\"text-xs text-text-secondary\">Out of office</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Leave Balances</CardTitle>\n <CardDescription>\n Company-wide leave allocation (default policy) vs. approved days used\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {leaveBalances.map((balance) => {\n const usedPct = Math.round((balance.used / Math.max(balance.total, 1)) * 100);\n return (\n <div key={balance.type} className=\"space-y-1\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"font-medium\">{balance.type}</span>\n <span>\n {balance.used}/{balance.total} days used\n </span>\n </div>\n <div className=\"h-2 overflow-hidden rounded bg-bg-sunken\">\n <div\n className={`h-2 rounded ${\n usedPct > 80\n ? 'bg-status-error-text'\n : usedPct > 50\n ? 'bg-status-warning-text'\n : 'bg-status-success-text'\n }`}\n style={{ width: `${usedPct}%` }}\n />\n </div>\n </div>\n );\n })}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Pending Approvals</CardTitle>\n <CardDescription>Leave requests waiting for your review</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {pendingPageItems.map((leave) => (\n <div key={leave.id} className=\"flex items-center justify-between rounded border p-3\">\n <div>\n <p className=\"text-sm font-medium\">{leave.employeeName}</p>\n <p className=\"text-xs text-text-secondary\">\n {leave.type} · {leave.days} days · {leave.startDate} to {leave.endDate}\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">{leave.reason}</p>\n </div>\n <div className=\"flex gap-1\">\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs\"\n onClick={() => handleDecision(leave, 'rejected')}\n >\n Reject\n </Button>\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n onClick={() => handleDecision(leave, 'approved')}\n >\n Approve\n </Button>\n </div>\n </div>\n ))}\n {pendingCount === 0 && (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No leave requests are awaiting approval.\n </p>\n )}\n {pendingList.length > pendingPg.pageSize && (\n <Pagination\n page={pendingPg.page}\n pageSize={pendingPg.pageSize}\n totalItems={pendingList.length}\n onPageChange={pendingPg.setPage}\n />\n )}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>All Leave Requests</CardTitle>\n <CardDescription>Complete leave request history</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <ResponsiveTable<LeaveRequest>\n data={requestPageItems}\n rowKey={(leave) => leave.id}\n columns={requestColumns}\n emptyState={\n <p className=\"py-6 text-center text-sm text-text-secondary\">No leave requests yet.</p>\n }\n />\n {sortedRequests.length > allPg.pageSize && (\n <Pagination\n page={allPg.page}\n pageSize={allPg.pageSize}\n totalItems={sortedRequests.length}\n onPageChange={allPg.setPage}\n />\n )}\n </CardContent>\n </Card>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title=\"Request leave\"\n description=\"Submit a new time-off request for review.\"\n fields={leaveFields}\n submitLabel=\"Submit request\"\n onSubmit={handleSubmit}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -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"}
|