@burdenoff/microfe-crewfoundry 2026.1013.2 → 2026.1029.1
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/CrewFoundryRoot.d.ts.map +1 -1
- package/dist/crewfoundry/CrewFoundryRoot.js +160 -256
- package/dist/crewfoundry/CrewFoundryRoot.js.map +1 -1
- package/dist/crewfoundry/CrewFoundryRoutes.d.ts.map +1 -1
- package/dist/crewfoundry/CrewFoundryRoutes.js +152 -192
- package/dist/crewfoundry/CrewFoundryRoutes.js.map +1 -1
- package/dist/crewfoundry/components/InlineError.d.ts +5 -0
- package/dist/crewfoundry/components/InlineError.d.ts.map +1 -0
- package/dist/crewfoundry/components/InlineError.js +17 -0
- package/dist/crewfoundry/components/InlineError.js.map +1 -0
- package/dist/crewfoundry/components/marketplace/BookServiceModal.d.ts +14 -0
- package/dist/crewfoundry/components/marketplace/BookServiceModal.d.ts.map +1 -0
- package/dist/crewfoundry/components/marketplace/BookServiceModal.js +75 -0
- package/dist/crewfoundry/components/marketplace/BookServiceModal.js.map +1 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.d.ts +24 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.d.ts.map +1 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.js +47 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.js.map +1 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.d.ts +12 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.d.ts.map +1 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.js +123 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.js.map +1 -0
- package/dist/crewfoundry/components/viz/CertificateCard.js +4 -118
- package/dist/crewfoundry/components/viz/PathSteps.js +3 -68
- package/dist/crewfoundry/components/viz/lib/geometry.js +5 -13
- package/dist/crewfoundry/components/viz/lib/geometry.js.map +1 -1
- package/dist/crewfoundry/lib/analytics.d.ts +1 -6
- package/dist/crewfoundry/lib/analytics.d.ts.map +1 -1
- package/dist/crewfoundry/lib/analytics.js +3 -18
- package/dist/crewfoundry/lib/analytics.js.map +1 -1
- package/dist/crewfoundry/lib/companyFinance.d.ts +3 -7
- package/dist/crewfoundry/lib/companyFinance.d.ts.map +1 -1
- package/dist/crewfoundry/lib/companyFinance.js +3 -8
- package/dist/crewfoundry/lib/companyFinance.js.map +1 -1
- package/dist/crewfoundry/lib/learning.d.ts +12 -0
- package/dist/crewfoundry/lib/learning.d.ts.map +1 -0
- package/dist/crewfoundry/lib/learning.js +26 -0
- package/dist/crewfoundry/lib/learning.js.map +1 -0
- package/dist/crewfoundry/lib/marketplace.d.ts +30 -0
- package/dist/crewfoundry/lib/marketplace.d.ts.map +1 -0
- package/dist/crewfoundry/lib/marketplace.js +91 -0
- package/dist/crewfoundry/lib/marketplace.js.map +1 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.d.ts +7 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.d.ts.map +1 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.js +25 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.js.map +1 -0
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js +12 -25
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +145 -157
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +14 -14
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +14 -14
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js +71 -33
- package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js +247 -173
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.d.ts +12 -0
- package/dist/crewfoundry/pages/LearningCoursesPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js +307 -293
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.d.ts +2 -0
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.d.ts.map +1 -0
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.js +223 -0
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.js.map +1 -0
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.d.ts +4 -0
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.js +115 -160
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js +165 -75
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.d.ts +7 -0
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.d.ts.map +1 -0
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.js +175 -0
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.js.map +1 -0
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +182 -223
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js +168 -227
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js +127 -290
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.d.ts +138 -78
- package/dist/crewfoundry/store/crewfoundryStore.d.ts.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.js +323 -244
- package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
- package/dist/crewfoundry/utils/dateUtils.d.ts +5 -0
- package/dist/crewfoundry/utils/dateUtils.d.ts.map +1 -1
- package/dist/crewfoundry/utils/dateUtils.js +10 -1
- package/dist/crewfoundry/utils/dateUtils.js.map +1 -1
- package/dist/generated/global-types.d.ts +5145 -1627
- package/dist/generated/global-types.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +100373 -34602
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/utils/pageContext.d.ts.map +1 -1
- package/dist/utils/pageContext.js +0 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +1 -1
- package/dist/crewfoundry/components/viz/CertificateCard.js.map +0 -1
- package/dist/crewfoundry/components/viz/PathSteps.js.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js +0 -53
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +0 -107
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js +0 -62
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js +0 -37
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js +0 -111
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js +0 -309
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningLearningPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPagePage.js +0 -200
- package/dist/crewfoundry/pages/LearningLearningPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js +0 -190
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +0 -230
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js +0 -293
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.js +0 -163
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js +0 -365
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js +0 -224
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js +0 -180
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.js +0 -38
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js +0 -204
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.js +0 -55
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js +0 -335
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js +0 -160
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.js +0 -80
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.js +0 -191
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.js.map +0 -1
- package/dist/crewfoundry/utils/courseProgress.d.ts +0 -33
- package/dist/crewfoundry/utils/courseProgress.d.ts.map +0 -1
- package/dist/crewfoundry/utils/courseProgress.js +0 -46
- package/dist/crewfoundry/utils/courseProgress.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CommunicationsAnnouncementsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CommunicationsAnnouncementsPagePage.tsx"],"sourcesContent":["import {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { formatRelativeTime } from '@burdenoff/fe-libs/shared/utils';\nimport { Eye, MessageSquare, Pencil, Pin, PinOff, Plus, Search, Send, Trash2 } from 'lucide-react';\nimport { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router';\nimport { useCrewFoundryStore, type Announcement } from '../store/crewfoundryStore';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack } from '../components/viz';\n\nconst CATEGORY_OPTIONS = [\n { label: 'General', value: 'general' },\n { label: 'HR', value: 'hr' },\n { label: 'IT', value: 'it' },\n { label: 'Emergency', value: 'emergency' },\n { label: 'Event', value: 'event' },\n];\n\nconst PRIORITY_OPTIONS = [\n { label: 'Low', value: 'low' },\n { label: 'Medium', value: 'medium' },\n { label: 'High', value: 'high' },\n { label: 'Urgent', value: 'urgent' },\n];\n\n// Category → tokenized pill styling (semantic tokens only — themes cleanly).\nconst CATEGORY_STYLE: Record<Announcement['category'], string> = {\n general: 'bg-bg-sunken text-text-secondary',\n hr: 'bg-status-success-bg text-status-success-text',\n it: 'bg-bg-sunken text-text-secondary',\n emergency: 'bg-status-error-bg text-status-error-text',\n event: 'bg-status-info-bg text-status-info-text',\n};\n\nconst PRIORITY_STYLE: Record<Announcement['priority'], string> = {\n low: 'bg-bg-sunken text-text-secondary',\n medium: 'bg-bg-sunken text-text-secondary',\n high: 'bg-status-warning-bg text-status-warning-text',\n urgent: 'bg-status-error-bg text-status-error-text',\n};\n\nconst CREATE_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Title', type: 'text', required: true, colSpan: 2 },\n { name: 'content', label: 'Content', type: 'textarea', required: true, colSpan: 2 },\n { name: 'author', label: 'Author', type: 'text', required: true },\n { name: 'authorRole', label: 'Author Role', type: 'text', required: true },\n {\n name: 'category',\n label: 'Category',\n type: 'select',\n required: true,\n options: CATEGORY_OPTIONS,\n placeholder: 'Select a category',\n },\n {\n name: 'priority',\n label: 'Priority',\n type: 'select',\n required: true,\n options: PRIORITY_OPTIONS,\n placeholder: 'Select a priority',\n },\n { name: 'tags', label: 'Tags', type: 'tags', placeholder: 'Add a tag and press Enter' },\n { name: 'isPinned', label: 'Pin to top', type: 'switch' },\n];\n\n// Author/role are immutable after creation (they are not part of the update\n// input), so the edit form omits them.\nconst EDIT_FIELDS: FieldConfig[] = CREATE_FIELDS.filter(\n (field) => field.name !== 'author' && field.name !== 'authorRole'\n);\n\nconst relativeTime = (value: string): string => {\n if (!value) {\n return '';\n }\n try {\n return formatRelativeTime(value);\n } catch {\n return value;\n }\n};\n\nexport function CommunicationsAnnouncementsPagePage() {\n const { announcements, notifications, addAnnouncement, updateAnnouncement, removeAnnouncement } =\n useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const navigate = useNavigate();\n const form = useFormModal<Announcement>();\n const [deleteTarget, setDeleteTarget] = useState<Announcement | null>(null);\n const [deleting, setDeleting] = useState(false);\n const [search, setSearch] = useState('');\n const [draftMessage, setDraftMessage] = useState('');\n\n const editing = form.context;\n\n // Real conversations surfaced by the workspace overlap query land in the store\n // as `communications`-category notifications (id `conversation-*`). Render the\n // rail from that real data — no fabricated threads.\n const conversations = useMemo(\n () => notifications.filter((n) => n.category === 'communications'),\n [notifications]\n );\n\n const query = search.trim().toLowerCase();\n const filtered = useMemo(\n () =>\n query.length === 0\n ? announcements\n : announcements.filter((a) =>\n `${a.title} ${a.content} ${a.tags.join(' ')}`.toLowerCase().includes(query)\n ),\n [announcements, query]\n );\n\n const pinned = filtered.filter((a) => a.isPinned);\n const regular = filtered.filter((a) => !a.isPinned);\n\n const handleSubmit = async (values: FormValues) => {\n const tags = Array.isArray(values.tags) ? values.tags : [];\n if (editing) {\n await updateAnnouncement(editing.id, {\n title: String(values.title),\n content: String(values.content),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n tags,\n });\n showToast('Announcement updated', 'success');\n return;\n }\n await addAnnouncement({\n id: crypto.randomUUID(),\n title: String(values.title),\n content: String(values.content),\n author: String(values.author),\n authorRole: String(values.authorRole),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n createdAt: new Date().toISOString().slice(0, 10),\n viewCount: 0,\n tags,\n });\n showToast('Announcement published', 'success');\n };\n\n const togglePin = async (announcement: Announcement) => {\n try {\n await updateAnnouncement(announcement.id, { isPinned: !announcement.isPinned });\n showToast(announcement.isPinned ? 'Unpinned' : 'Pinned to top', 'success');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to update announcement', 'error');\n }\n };\n\n const confirmDelete = async () => {\n if (!deleteTarget) {\n return;\n }\n setDeleting(true);\n try {\n await removeAnnouncement(deleteTarget.id);\n showToast('Announcement deleted', 'success');\n setDeleteTarget(null);\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to delete announcement', 'error');\n } finally {\n setDeleting(false);\n }\n };\n\n const openMessages = () => navigate('../communications/messages-page');\n\n const initialValues: FormValues | undefined = editing\n ? {\n title: editing.title,\n content: editing.content,\n category: editing.category,\n priority: editing.priority,\n tags: editing.tags,\n isPinned: editing.isPinned,\n }\n : undefined;\n\n const renderCard = (ann: Announcement, isPinnedCard: boolean) => (\n <GlassCard\n key={ann.id}\n treatment={isPinnedCard ? 'glass' : 'plain'}\n glow={isPinnedCard}\n className=\"transition-shadow hover:shadow-elevation-2\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge className={`border-transparent text-[10px] ${PRIORITY_STYLE[ann.priority]}`}>\n {formatEnumLabel(ann.priority)}\n </Badge>\n <Badge className={`border-transparent text-[10px] ${CATEGORY_STYLE[ann.category]}`}>\n {formatEnumLabel(ann.category)}\n </Badge>\n <span className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n <AvatarStack people={[{ name: ann.author }]} size={20} />\n {ann.author}\n <span className=\"text-text-muted\">· {relativeTime(ann.createdAt)}</span>\n </span>\n </div>\n <div className=\"flex items-center gap-0.5\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className={`h-7 w-7 ${ann.isPinned ? 'text-status-info-text' : ''}`}\n aria-label={ann.isPinned ? `Unpin ${ann.title}` : `Pin ${ann.title}`}\n aria-pressed={ann.isPinned}\n onClick={() => void togglePin(ann)}\n >\n {ann.isPinned ? <PinOff className=\"h-3.5 w-3.5\" /> : <Pin className=\"h-3.5 w-3.5\" />}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n aria-label={`Edit announcement ${ann.title}`}\n onClick={() => form.openModal(ann)}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7 text-status-error-text\"\n aria-label={`Delete announcement ${ann.title}`}\n onClick={() => setDeleteTarget(ann)}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n </div>\n <CardTitle className=\"text-base\">{ann.title}</CardTitle>\n </CardHeader>\n <CardContent className=\"pt-0\">\n <p className=\"text-sm text-text-secondary\">{ann.content}</p>\n {ann.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {ann.tags.map((t) => (\n <Badge key={t} variant=\"outline\" className=\"text-[9px]\">\n {t}\n </Badge>\n ))}\n </div>\n )}\n <div className=\"mt-3 flex items-center gap-1 text-xs text-text-muted\">\n <Eye className=\"h-3 w-3\" />\n {ann.viewCount} read\n </div>\n </CardContent>\n </GlassCard>\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Announcements</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {announcements.length} company announcements\n </p>\n <PagePurpose className=\"mt-1\">\n Broadcast company-wide news and updates so everyone stays informed — post announcements\n here when you have something the whole team needs to see.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Announcement\n </Button>\n </div>\n\n <div className=\"relative max-w-md\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search announcements...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n />\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Feed */}\n <div className=\"space-y-4 lg:col-span-2\">\n {filtered.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={query ? 'No announcements match' : 'No announcements yet'}\n description={\n query\n ? 'Try a different search term.'\n : 'Post an announcement to share news with the whole team.'\n }\n />\n )}\n {pinned.length > 0 && (\n <div className=\"space-y-3\">\n <h2 className=\"flex items-center gap-1 text-sm font-medium\">\n <Pin className=\"h-4 w-4\" />\n Pinned\n </h2>\n {pinned.map((ann) => renderCard(ann, true))}\n </div>\n )}\n {regular.length > 0 && (\n <div className=\"space-y-3\">{regular.map((ann) => renderCard(ann, false))}</div>\n )}\n </div>\n\n {/* Conversations rail */}\n <div className=\"lg:col-span-1\">\n <Card className=\"lg:sticky lg:top-6\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-3\">\n <CardTitle className=\"text-base\">Conversations</CardTitle>\n <Button variant=\"ghost\" size=\"sm\" onClick={openMessages}>\n <MessageSquare className=\"mr-1 h-3.5 w-3.5\" />\n Open\n </Button>\n </CardHeader>\n <CardContent className=\"space-y-1\">\n {conversations.length === 0 ? (\n <p className=\"py-6 text-center text-xs text-text-muted\">\n No active conversations yet. Team channels will appear here as they get activity.\n </p>\n ) : (\n conversations.map((conv) => (\n <button\n key={conv.id}\n type=\"button\"\n onClick={openMessages}\n className=\"flex w-full items-start gap-3 rounded-[var(--radius-card)] p-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n <AvatarStack people={[{ name: conv.title }]} size={32} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"truncate text-sm font-medium\">{conv.title}</p>\n <span className=\"shrink-0 text-[10px] text-text-muted\">\n {relativeTime(conv.createdAt)}\n </span>\n </div>\n <p className=\"truncate text-xs text-text-secondary\">{conv.message}</p>\n </div>\n {!conv.read && (\n <span\n className=\"mt-1.5 h-2 w-2 shrink-0 rounded-full bg-status-success-text\"\n aria-label=\"Unread\"\n />\n )}\n </button>\n ))\n )}\n <div className=\"flex items-center gap-2 pt-3\">\n <Input\n placeholder=\"Type a message...\"\n className=\"h-9 flex-1\"\n value={draftMessage}\n onChange={(event) => setDraftMessage(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n openMessages();\n }\n }}\n aria-label=\"Message composer\"\n />\n <Button\n size=\"icon\"\n className=\"h-9 w-9 shrink-0\"\n aria-label=\"Open messages to send\"\n onClick={openMessages}\n >\n <Send className=\"h-4 w-4\" />\n </Button>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit Announcement' : 'New Announcement'}\n description={\n editing\n ? 'Update this announcement for the whole team.'\n : 'Share news and updates across the company.'\n }\n fields={editing ? EDIT_FIELDS : CREATE_FIELDS}\n initialValues={initialValues}\n submitLabel={editing ? 'Save changes' : 'Publish'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete announcement?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `\"${deleteTarget.title}\" will be permanently removed. This cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void confirmDelete();\n }}\n disabled={deleting}\n >\n {deleting ? 'Deleting…' : 'Delete'}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CommunicationsAnnouncementsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CommunicationsAnnouncementsPagePage.tsx"],"sourcesContent":["import {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { formatRelativeTime } from '@burdenoff/fe-libs/shared/utils';\nimport { Eye, MessageSquare, Pencil, Pin, PinOff, Plus, Search, Send, Trash2 } from 'lucide-react';\nimport { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router';\nimport {\n CONVERSATIONS_ROUTE,\n useCrewFoundryStore,\n type Announcement,\n} from '../store/crewfoundryStore';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack } from '../components/viz';\n\nconst CATEGORY_OPTIONS = [\n { label: 'General', value: 'general' },\n { label: 'HR', value: 'hr' },\n { label: 'IT', value: 'it' },\n { label: 'Emergency', value: 'emergency' },\n { label: 'Event', value: 'event' },\n];\n\nconst PRIORITY_OPTIONS = [\n { label: 'Low', value: 'low' },\n { label: 'Medium', value: 'medium' },\n { label: 'High', value: 'high' },\n { label: 'Urgent', value: 'urgent' },\n];\n\n// Category → tokenized pill styling (semantic tokens only — themes cleanly).\nconst CATEGORY_STYLE: Record<Announcement['category'], string> = {\n general: 'bg-bg-sunken text-text-secondary',\n hr: 'bg-status-success-bg text-status-success-text',\n it: 'bg-bg-sunken text-text-secondary',\n emergency: 'bg-status-error-bg text-status-error-text',\n event: 'bg-status-info-bg text-status-info-text',\n};\n\nconst PRIORITY_STYLE: Record<Announcement['priority'], string> = {\n low: 'bg-bg-sunken text-text-secondary',\n medium: 'bg-bg-sunken text-text-secondary',\n high: 'bg-status-warning-bg text-status-warning-text',\n urgent: 'bg-status-error-bg text-status-error-text',\n};\n\nconst CREATE_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Title', type: 'text', required: true, colSpan: 2 },\n { name: 'content', label: 'Content', type: 'textarea', required: true, colSpan: 2 },\n { name: 'author', label: 'Author', type: 'text', required: true },\n { name: 'authorRole', label: 'Author Role', type: 'text', required: true },\n {\n name: 'category',\n label: 'Category',\n type: 'select',\n required: true,\n options: CATEGORY_OPTIONS,\n placeholder: 'Select a category',\n },\n {\n name: 'priority',\n label: 'Priority',\n type: 'select',\n required: true,\n options: PRIORITY_OPTIONS,\n placeholder: 'Select a priority',\n },\n { name: 'tags', label: 'Tags', type: 'tags', placeholder: 'Add a tag and press Enter' },\n { name: 'isPinned', label: 'Pin to top', type: 'switch' },\n];\n\n// Author/role are immutable after creation (they are not part of the update\n// input), so the edit form omits them.\nconst EDIT_FIELDS: FieldConfig[] = CREATE_FIELDS.filter(\n (field) => field.name !== 'author' && field.name !== 'authorRole'\n);\n\nconst relativeTime = (value: string): string => {\n if (!value) {\n return '';\n }\n try {\n return formatRelativeTime(value);\n } catch {\n return value;\n }\n};\n\nexport function CommunicationsAnnouncementsPagePage() {\n const { announcements, notifications, addAnnouncement, updateAnnouncement, removeAnnouncement } =\n useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const navigate = useNavigate();\n const form = useFormModal<Announcement>();\n const [deleteTarget, setDeleteTarget] = useState<Announcement | null>(null);\n const [deleting, setDeleting] = useState(false);\n const [search, setSearch] = useState('');\n\n const editing = form.context;\n\n // Real conversations surfaced by the workspace overlap query land in the store\n // as `communications`-category notifications (id `conversation-*`). Render the\n // rail from that real data — no fabricated threads.\n const conversations = useMemo(\n () => notifications.filter((n) => n.category === 'communications'),\n [notifications]\n );\n\n const query = search.trim().toLowerCase();\n const filtered = useMemo(\n () =>\n query.length === 0\n ? announcements\n : announcements.filter((a) =>\n `${a.title} ${a.content} ${a.tags.join(' ')}`.toLowerCase().includes(query)\n ),\n [announcements, query]\n );\n\n const pinned = filtered.filter((a) => a.isPinned);\n const regular = filtered.filter((a) => !a.isPinned);\n\n const handleSubmit = async (values: FormValues) => {\n const tags = Array.isArray(values.tags) ? values.tags : [];\n if (editing) {\n await updateAnnouncement(editing.id, {\n title: String(values.title),\n content: String(values.content),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n tags,\n });\n showToast('Announcement updated', 'success');\n return;\n }\n await addAnnouncement({\n id: crypto.randomUUID(),\n title: String(values.title),\n content: String(values.content),\n author: String(values.author),\n authorRole: String(values.authorRole),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n createdAt: new Date().toISOString().slice(0, 10),\n viewCount: 0,\n tags,\n });\n showToast('Announcement published', 'success');\n };\n\n const togglePin = async (announcement: Announcement) => {\n try {\n await updateAnnouncement(announcement.id, { isPinned: !announcement.isPinned });\n showToast(announcement.isPinned ? 'Unpinned' : 'Pinned to top', 'success');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to update announcement', 'error');\n }\n };\n\n const confirmDelete = async () => {\n if (!deleteTarget) {\n return;\n }\n setDeleting(true);\n try {\n await removeAnnouncement(deleteTarget.id);\n showToast('Announcement deleted', 'success');\n setDeleteTarget(null);\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to delete announcement', 'error');\n } finally {\n setDeleting(false);\n }\n };\n\n // Messaging is owned by the shell's shared Conversations app\n // (wspace-conversations-svc); send there instead of a local composer that\n // could not deliver anything.\n const openMessages = () => navigate(CONVERSATIONS_ROUTE);\n\n const initialValues: FormValues | undefined = editing\n ? {\n title: editing.title,\n content: editing.content,\n category: editing.category,\n priority: editing.priority,\n tags: editing.tags,\n isPinned: editing.isPinned,\n }\n : undefined;\n\n const renderCard = (ann: Announcement, isPinnedCard: boolean) => (\n <GlassCard\n key={ann.id}\n treatment={isPinnedCard ? 'glass' : 'plain'}\n glow={isPinnedCard}\n className=\"transition-shadow hover:shadow-elevation-2\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge className={`border-transparent text-[10px] ${PRIORITY_STYLE[ann.priority]}`}>\n {formatEnumLabel(ann.priority)}\n </Badge>\n <Badge className={`border-transparent text-[10px] ${CATEGORY_STYLE[ann.category]}`}>\n {formatEnumLabel(ann.category)}\n </Badge>\n <span className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n <AvatarStack people={[{ name: ann.author }]} size={20} />\n {ann.author}\n <span className=\"text-text-muted\">· {relativeTime(ann.createdAt)}</span>\n </span>\n </div>\n <div className=\"flex items-center gap-0.5\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className={`h-7 w-7 ${ann.isPinned ? 'text-status-info-text' : ''}`}\n aria-label={ann.isPinned ? `Unpin ${ann.title}` : `Pin ${ann.title}`}\n aria-pressed={ann.isPinned}\n onClick={() => void togglePin(ann)}\n >\n {ann.isPinned ? <PinOff className=\"h-3.5 w-3.5\" /> : <Pin className=\"h-3.5 w-3.5\" />}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n aria-label={`Edit announcement ${ann.title}`}\n onClick={() => form.openModal(ann)}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7 text-status-error-text\"\n aria-label={`Delete announcement ${ann.title}`}\n onClick={() => setDeleteTarget(ann)}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\n </div>\n <CardTitle className=\"text-base\">{ann.title}</CardTitle>\n </CardHeader>\n <CardContent className=\"pt-0\">\n <p className=\"text-sm text-text-secondary\">{ann.content}</p>\n {ann.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {ann.tags.map((t) => (\n <Badge key={t} variant=\"outline\" className=\"text-[9px]\">\n {t}\n </Badge>\n ))}\n </div>\n )}\n <div className=\"mt-3 flex items-center gap-1 text-xs text-text-muted\">\n <Eye className=\"h-3 w-3\" />\n {ann.viewCount} read\n </div>\n </CardContent>\n </GlassCard>\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Announcements</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {announcements.length} company announcements\n </p>\n <PagePurpose className=\"mt-1\">\n Broadcast company-wide news and updates so everyone stays informed — post announcements\n here when you have something the whole team needs to see.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Announcement\n </Button>\n </div>\n\n <div className=\"relative max-w-md\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search announcements...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n />\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Feed */}\n <div className=\"space-y-4 lg:col-span-2\">\n {filtered.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={query ? 'No announcements match' : 'No announcements yet'}\n description={\n query\n ? 'Try a different search term.'\n : 'Post an announcement to share news with the whole team.'\n }\n />\n )}\n {pinned.length > 0 && (\n <div className=\"space-y-3\">\n <h2 className=\"flex items-center gap-1 text-sm font-medium\">\n <Pin className=\"h-4 w-4\" />\n Pinned\n </h2>\n {pinned.map((ann) => renderCard(ann, true))}\n </div>\n )}\n {regular.length > 0 && (\n <div className=\"space-y-3\">{regular.map((ann) => renderCard(ann, false))}</div>\n )}\n </div>\n\n {/* Conversations rail */}\n <div className=\"lg:col-span-1\">\n <Card className=\"lg:sticky lg:top-6\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-3\">\n <CardTitle className=\"text-base\">Conversations</CardTitle>\n <Button variant=\"ghost\" size=\"sm\" onClick={openMessages}>\n <MessageSquare className=\"mr-1 h-3.5 w-3.5\" />\n Open\n </Button>\n </CardHeader>\n <CardContent className=\"space-y-1\">\n {conversations.length === 0 ? (\n <p className=\"py-6 text-center text-xs text-text-muted\">\n No active conversations yet. Team channels will appear here as they get activity.\n </p>\n ) : (\n conversations.map((conv) => (\n <button\n key={conv.id}\n type=\"button\"\n onClick={openMessages}\n className=\"flex w-full items-start gap-3 rounded-[var(--radius-card)] p-2 text-left transition-colors hover:bg-bg-sunken\"\n >\n <AvatarStack people={[{ name: conv.title }]} size={32} />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"truncate text-sm font-medium\">{conv.title}</p>\n <span className=\"shrink-0 text-[10px] text-text-muted\">\n {relativeTime(conv.createdAt)}\n </span>\n </div>\n <p className=\"truncate text-xs text-text-secondary\">{conv.message}</p>\n </div>\n {!conv.read && (\n <span\n className=\"mt-1.5 h-2 w-2 shrink-0 rounded-full bg-status-success-text\"\n aria-label=\"Unread\"\n />\n )}\n </button>\n ))\n )}\n <Button variant=\"outline\" size=\"sm\" className=\"mt-3 w-full\" onClick={openMessages}>\n <Send className=\"mr-1 h-3.5 w-3.5\" />\n Write a message in Conversations\n </Button>\n </CardContent>\n </Card>\n </div>\n </div>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit Announcement' : 'New Announcement'}\n description={\n editing\n ? 'Update this announcement for the whole team.'\n : 'Share news and updates across the company.'\n }\n fields={editing ? EDIT_FIELDS : CREATE_FIELDS}\n initialValues={initialValues}\n submitLabel={editing ? 'Save changes' : 'Publish'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete announcement?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `\"${deleteTarget.title}\" will be permanently removed. This cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void confirmDelete();\n }}\n disabled={deleting}\n >\n {deleting ? 'Deleting…' : 'Delete'}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompanyClientManagementPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/CompanyClientManagementPagePage.tsx"],"names":[],"mappings":"AA2GA,wBAAgB,+BAA+B,
|
|
1
|
+
{"version":3,"file":"CompanyClientManagementPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/CompanyClientManagementPagePage.tsx"],"names":[],"mappings":"AA2GA,wBAAgB,+BAA+B,gCAsQ9C"}
|
|
@@ -5,7 +5,7 @@ import { AvatarStack as r } from "../components/viz/AvatarStack.js";
|
|
|
5
5
|
import { SegmentedTabs as ee } from "../components/viz/SegmentedTabs.js";
|
|
6
6
|
import "../components/viz/index.js";
|
|
7
7
|
import { FormModal as te, useFormModal as i } from "../components/forms/FormModal.js";
|
|
8
|
-
import {
|
|
8
|
+
import { clientRevenue as a, totalContractValue as o } from "../lib/companyFinance.js";
|
|
9
9
|
import { useState as s } from "react";
|
|
10
10
|
import { MoreHorizontal as c, Pencil as l, Plus as u, Search as ne, Trash2 as re } from "lucide-react";
|
|
11
11
|
import { useNavigate as ie } from "react-router";
|
|
@@ -97,10 +97,10 @@ var R = [
|
|
|
97
97
|
since: String(e.since ?? "")
|
|
98
98
|
}), V = (e) => e === "active" ? "default" : e === "prospect" ? "secondary" : "destructive", H = (e) => e >= 1e6 ? `$${(e / 1e6).toFixed(1)}M` : e >= 1e3 ? `$${Math.round(e / 1e3)}k` : `$${Math.round(e)}`;
|
|
99
99
|
function U() {
|
|
100
|
-
let { clients: U, vendors: W,
|
|
100
|
+
let { clients: U, vendors: W, addClient: de, updateClient: fe, removeClient: pe } = e(), me = ie(), G = i(), [K, he] = s(""), [q, ge] = s("all"), [J, Y] = s(null), { page: _e, pageSize: X, offset: Z, setPage: Q } = L({ initialPageSize: 25 }), ve = U.filter((e) => e.status === "active"), ye = o(U, W), be = a(U), $ = U.filter((e) => {
|
|
101
101
|
let t = `${e.name} ${e.industry} ${e.contactPerson} ${e.email}`.toLowerCase().includes(K.toLowerCase()), n = q === "all" || e.status === q;
|
|
102
102
|
return t && n;
|
|
103
|
-
}),
|
|
103
|
+
}), xe = $.slice(Z, Z + X);
|
|
104
104
|
return /* @__PURE__ */ f("div", {
|
|
105
105
|
className: "space-y-6 p-6",
|
|
106
106
|
children: [
|
|
@@ -135,7 +135,7 @@ function U() {
|
|
|
135
135
|
variant: "underline",
|
|
136
136
|
value: "clients",
|
|
137
137
|
onChange: (e) => {
|
|
138
|
-
e === "vendors" &&
|
|
138
|
+
e === "vendors" && me("../company/vendor-management-page");
|
|
139
139
|
},
|
|
140
140
|
tabs: [{
|
|
141
141
|
id: "clients",
|
|
@@ -152,7 +152,7 @@ function U() {
|
|
|
152
152
|
children: [
|
|
153
153
|
/* @__PURE__ */ d(n, {
|
|
154
154
|
label: "Active Clients",
|
|
155
|
-
value:
|
|
155
|
+
value: ve.length
|
|
156
156
|
}),
|
|
157
157
|
/* @__PURE__ */ d(n, {
|
|
158
158
|
label: "Vendors",
|
|
@@ -160,11 +160,11 @@ function U() {
|
|
|
160
160
|
}),
|
|
161
161
|
/* @__PURE__ */ d(n, {
|
|
162
162
|
label: "Total Contract Value",
|
|
163
|
-
value: H(
|
|
163
|
+
value: H(ye)
|
|
164
164
|
}),
|
|
165
165
|
/* @__PURE__ */ d(n, {
|
|
166
|
-
label: "
|
|
167
|
-
value: H(
|
|
166
|
+
label: "Client Revenue",
|
|
167
|
+
value: H(be)
|
|
168
168
|
})
|
|
169
169
|
]
|
|
170
170
|
}),
|
|
@@ -180,13 +180,13 @@ function U() {
|
|
|
180
180
|
className: "pl-9",
|
|
181
181
|
value: K,
|
|
182
182
|
onChange: (e) => {
|
|
183
|
-
|
|
183
|
+
he(e.target.value), Q(1);
|
|
184
184
|
}
|
|
185
185
|
})]
|
|
186
186
|
}), /* @__PURE__ */ f(N, {
|
|
187
187
|
value: q,
|
|
188
188
|
onValueChange: (e) => {
|
|
189
|
-
|
|
189
|
+
ge(e), Q(1);
|
|
190
190
|
},
|
|
191
191
|
children: [/* @__PURE__ */ d(I, {
|
|
192
192
|
className: "w-full sm:w-40",
|
|
@@ -213,7 +213,7 @@ function U() {
|
|
|
213
213
|
})]
|
|
214
214
|
}),
|
|
215
215
|
/* @__PURE__ */ d(M, {
|
|
216
|
-
data:
|
|
216
|
+
data: xe,
|
|
217
217
|
rowKey: (e) => e.id,
|
|
218
218
|
columns: [
|
|
219
219
|
{
|
|
@@ -316,7 +316,7 @@ function U() {
|
|
|
316
316
|
})
|
|
317
317
|
}),
|
|
318
318
|
$.length > X && /* @__PURE__ */ d(j, {
|
|
319
|
-
page:
|
|
319
|
+
page: _e,
|
|
320
320
|
pageSize: X,
|
|
321
321
|
totalItems: $.length,
|
|
322
322
|
onPageChange: Q
|
|
@@ -332,7 +332,7 @@ function U() {
|
|
|
332
332
|
initialValues: G.context ? z(G.context) : void 0,
|
|
333
333
|
submitLabel: G.context ? "Save changes" : "Add client",
|
|
334
334
|
onSubmit: async (e) => {
|
|
335
|
-
G.context ? await
|
|
335
|
+
G.context ? await fe(G.context.id, B(e, G.context)) : await de(B(e));
|
|
336
336
|
}
|
|
337
337
|
}),
|
|
338
338
|
/* @__PURE__ */ d(ae, {
|
|
@@ -342,7 +342,7 @@ function U() {
|
|
|
342
342
|
},
|
|
343
343
|
children: /* @__PURE__ */ f(ce, { children: [/* @__PURE__ */ f(m, { children: [/* @__PURE__ */ d(h, { children: "Delete client?" }), /* @__PURE__ */ d(le, { children: J ? `This removes “${J.name}” from your client portfolio. This action cannot be undone.` : "" })] }), /* @__PURE__ */ f(p, { children: [/* @__PURE__ */ d(se, { children: "Cancel" }), /* @__PURE__ */ d(oe, {
|
|
344
344
|
onClick: (e) => {
|
|
345
|
-
e.preventDefault(), J &&
|
|
345
|
+
e.preventDefault(), J && pe(J.id), Y(null);
|
|
346
346
|
},
|
|
347
347
|
children: "Delete"
|
|
348
348
|
})] })] })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompanyClientManagementPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CompanyClientManagementPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\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 { MoreHorizontal, Pencil, Plus, Search, Trash2 } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { useCrewFoundryStore, type Client } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, SegmentedTabs, StatCard } from '../components/viz';\nimport { thisQuarterRevenue, totalContractValue } from '../lib/companyFinance';\n\nconst CLIENT_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Client name', type: 'text', required: true },\n { name: 'industry', label: 'Industry', type: 'text' },\n { name: 'contactPerson', label: 'Contact person', type: 'text' },\n { name: 'email', label: 'Contact email', type: 'email' },\n { name: 'phone', label: 'Phone', type: 'text' },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Prospect', value: 'prospect' },\n { label: 'Churned', value: 'churned' },\n ],\n },\n { name: 'revenue', label: 'Revenue (USD)', type: 'number' },\n { name: 'projects', label: 'Projects', type: 'number' },\n { name: 'since', label: 'Client since', type: 'date' },\n];\n\nconst toInitialValues = (client: Client): FormValues => ({\n name: client.name,\n industry: client.industry,\n contactPerson: client.contactPerson,\n email: client.email,\n phone: client.phone,\n status: client.status,\n revenue: client.revenue,\n projects: client.projects,\n since: client.since ? client.since.slice(0, 10) : '',\n});\n\nconst valuesToClient = (values: FormValues, base?: Client): Client => ({\n id: base?.id ?? crypto.randomUUID(),\n name: String(values.name ?? ''),\n industry: String(values.industry ?? ''),\n contactPerson: String(values.contactPerson ?? ''),\n email: String(values.email ?? ''),\n phone: String(values.phone ?? ''),\n status: (values.status as Client['status']) || 'prospect',\n revenue: typeof values.revenue === 'number' ? values.revenue : Number(values.revenue) || 0,\n projects: typeof values.projects === 'number' ? values.projects : Number(values.projects) || 0,\n since: String(values.since ?? ''),\n});\n\nconst statusVariant = (status: Client['status']) =>\n status === 'active' ? 'default' : status === 'prospect' ? 'secondary' : 'destructive';\n\nconst compactMoney = (value: number): string => {\n if (value >= 1_000_000) {\n return `$${(value / 1_000_000).toFixed(1)}M`;\n }\n if (value >= 1_000) {\n return `$${Math.round(value / 1_000)}k`;\n }\n return `$${Math.round(value)}`;\n};\n\nexport function CompanyClientManagementPagePage() {\n const { clients, vendors, invoices, addClient, updateClient, removeClient } =\n useCrewFoundryStore();\n const navigate = useNavigate();\n const modal = useFormModal<Client>();\n const [search, setSearch] = useState('');\n const [filterStatus, setFilterStatus] = useState<string>('all');\n const [deleteTarget, setDeleteTarget] = useState<Client | null>(null);\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n\n const activeClients = clients.filter((c) => c.status === 'active');\n const contractValue = totalContractValue(clients, vendors);\n const quarterRevenue = thisQuarterRevenue(invoices);\n\n const filtered = clients.filter((c) => {\n const matchSearch = `${c.name} ${c.industry} ${c.contactPerson} ${c.email}`\n .toLowerCase()\n .includes(search.toLowerCase());\n const matchStatus = filterStatus === 'all' || c.status === filterStatus;\n return matchSearch && matchStatus;\n });\n const paged = filtered.slice(offset, offset + pageSize);\n\n const handleSubmit = async (values: FormValues) => {\n if (modal.context) {\n await updateClient(modal.context.id, valuesToClient(values, modal.context));\n } else {\n await addClient(valuesToClient(values));\n }\n };\n\n const columns: ResponsiveTableColumn<Client>[] = [\n {\n key: 'name',\n header: 'Client',\n priority: 'primary',\n cell: (c) => (\n <div className=\"flex items-center gap-3\">\n <AvatarStack people={[{ name: c.name }]} size={36} />\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium\">{c.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">{c.industry || '—'}</p>\n </div>\n </div>\n ),\n },\n {\n key: 'contact',\n header: 'Primary Contact',\n priority: 'secondary',\n cell: (c) => (\n <div>\n <p className=\"text-xs\">{c.contactPerson || '—'}</p>\n <p className=\"text-xs text-text-secondary\">{c.email}</p>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (c) => (\n <Badge variant={statusVariant(c.status)} className=\"text-[10px]\">\n {formatEnumLabel(c.status)}\n </Badge>\n ),\n },\n {\n key: 'projects',\n header: 'Projects',\n priority: 'hidden-on-mobile',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (c) => <span className=\"tabular-nums\">{c.projects}</span>,\n },\n {\n key: 'revenue',\n header: 'Value',\n priority: 'secondary',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (c) => <span className=\"tabular-nums\">${c.revenue.toLocaleString()}</span>,\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>Clients & Vendors</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {clients.length} clients · {vendors.length} vendors\n </p>\n <PagePurpose className=\"mt-1\">\n Keep track of the clients you work with — their contacts, status, and engagements — so\n account relationships never slip through the cracks.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => modal.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Client\n </Button>\n </div>\n\n <SegmentedTabs\n aria-label=\"Clients and vendors\"\n variant=\"underline\"\n value=\"clients\"\n onChange={(id) => {\n if (id === 'vendors') {\n navigate('../company/vendor-management-page');\n }\n }}\n tabs={[\n { id: 'clients', label: 'Clients', count: clients.length },\n { id: 'vendors', label: 'Vendors', count: vendors.length },\n ]}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard label=\"Active Clients\" value={activeClients.length} />\n <StatCard label=\"Vendors\" value={vendors.length} />\n <StatCard label=\"Total Contract Value\" value={compactMoney(contractValue)} />\n <StatCard label=\"This Quarter\" value={compactMoney(quarterRevenue)} />\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Client Portfolio</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search clients...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => {\n setSearch(event.target.value);\n setPage(1);\n }}\n />\n </div>\n <Select\n value={filterStatus}\n onValueChange={(value) => {\n setFilterStatus(value);\n setPage(1);\n }}\n >\n <SelectTrigger className=\"w-full sm:w-40\" aria-label=\"Filter by status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">All statuses</SelectItem>\n <SelectItem value=\"active\">Active</SelectItem>\n <SelectItem value=\"prospect\">Prospect</SelectItem>\n <SelectItem value=\"churned\">Churned</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <ResponsiveTable\n data={paged}\n rowKey={(c) => c.id}\n columns={columns}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No clients match\"\n description=\"Try a different search term or status filter.\"\n />\n }\n rowActions={(c) => (\n <div className=\"flex items-center justify-end\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n aria-label={`More actions for ${c.name}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onSelect={() => modal.openModal(c)}>\n <Pencil className=\"mr-2 h-4 w-4\" />\n Edit\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"text-status-error-text\"\n onSelect={() => setDeleteTarget(c)}\n >\n <Trash2 className=\"mr-2 h-4 w-4\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\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={modal.open}\n onOpenChange={modal.onOpenChange}\n title={modal.context ? 'Edit client' : 'Add client'}\n description=\"Track the client relationship, contacts, and engagement value.\"\n fields={CLIENT_FIELDS}\n initialValues={modal.context ? toInitialValues(modal.context) : undefined}\n submitLabel={modal.context ? 'Save changes' : 'Add client'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete client?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `This removes “${deleteTarget.name}” from your client portfolio. This action cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n if (deleteTarget) {\n void removeClient(deleteTarget.id);\n }\n setDeleteTarget(null);\n }}\n >\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CompanyClientManagementPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CompanyClientManagementPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\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 { MoreHorizontal, Pencil, Plus, Search, Trash2 } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { useCrewFoundryStore, type Client } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, SegmentedTabs, StatCard } from '../components/viz';\nimport { clientRevenue, totalContractValue } from '../lib/companyFinance';\n\nconst CLIENT_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Client name', type: 'text', required: true },\n { name: 'industry', label: 'Industry', type: 'text' },\n { name: 'contactPerson', label: 'Contact person', type: 'text' },\n { name: 'email', label: 'Contact email', type: 'email' },\n { name: 'phone', label: 'Phone', type: 'text' },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Prospect', value: 'prospect' },\n { label: 'Churned', value: 'churned' },\n ],\n },\n { name: 'revenue', label: 'Revenue (USD)', type: 'number' },\n { name: 'projects', label: 'Projects', type: 'number' },\n { name: 'since', label: 'Client since', type: 'date' },\n];\n\nconst toInitialValues = (client: Client): FormValues => ({\n name: client.name,\n industry: client.industry,\n contactPerson: client.contactPerson,\n email: client.email,\n phone: client.phone,\n status: client.status,\n revenue: client.revenue,\n projects: client.projects,\n since: client.since ? client.since.slice(0, 10) : '',\n});\n\nconst valuesToClient = (values: FormValues, base?: Client): Client => ({\n id: base?.id ?? crypto.randomUUID(),\n name: String(values.name ?? ''),\n industry: String(values.industry ?? ''),\n contactPerson: String(values.contactPerson ?? ''),\n email: String(values.email ?? ''),\n phone: String(values.phone ?? ''),\n status: (values.status as Client['status']) || 'prospect',\n revenue: typeof values.revenue === 'number' ? values.revenue : Number(values.revenue) || 0,\n projects: typeof values.projects === 'number' ? values.projects : Number(values.projects) || 0,\n since: String(values.since ?? ''),\n});\n\nconst statusVariant = (status: Client['status']) =>\n status === 'active' ? 'default' : status === 'prospect' ? 'secondary' : 'destructive';\n\nconst compactMoney = (value: number): string => {\n if (value >= 1_000_000) {\n return `$${(value / 1_000_000).toFixed(1)}M`;\n }\n if (value >= 1_000) {\n return `$${Math.round(value / 1_000)}k`;\n }\n return `$${Math.round(value)}`;\n};\n\nexport function CompanyClientManagementPagePage() {\n const { clients, vendors, addClient, updateClient, removeClient } = useCrewFoundryStore();\n const navigate = useNavigate();\n const modal = useFormModal<Client>();\n const [search, setSearch] = useState('');\n const [filterStatus, setFilterStatus] = useState<string>('all');\n const [deleteTarget, setDeleteTarget] = useState<Client | null>(null);\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n\n const activeClients = clients.filter((c) => c.status === 'active');\n const contractValue = totalContractValue(clients, vendors);\n const revenue = clientRevenue(clients);\n\n const filtered = clients.filter((c) => {\n const matchSearch = `${c.name} ${c.industry} ${c.contactPerson} ${c.email}`\n .toLowerCase()\n .includes(search.toLowerCase());\n const matchStatus = filterStatus === 'all' || c.status === filterStatus;\n return matchSearch && matchStatus;\n });\n const paged = filtered.slice(offset, offset + pageSize);\n\n const handleSubmit = async (values: FormValues) => {\n if (modal.context) {\n await updateClient(modal.context.id, valuesToClient(values, modal.context));\n } else {\n await addClient(valuesToClient(values));\n }\n };\n\n const columns: ResponsiveTableColumn<Client>[] = [\n {\n key: 'name',\n header: 'Client',\n priority: 'primary',\n cell: (c) => (\n <div className=\"flex items-center gap-3\">\n <AvatarStack people={[{ name: c.name }]} size={36} />\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium\">{c.name}</p>\n <p className=\"truncate text-xs text-text-secondary\">{c.industry || '—'}</p>\n </div>\n </div>\n ),\n },\n {\n key: 'contact',\n header: 'Primary Contact',\n priority: 'secondary',\n cell: (c) => (\n <div>\n <p className=\"text-xs\">{c.contactPerson || '—'}</p>\n <p className=\"text-xs text-text-secondary\">{c.email}</p>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (c) => (\n <Badge variant={statusVariant(c.status)} className=\"text-[10px]\">\n {formatEnumLabel(c.status)}\n </Badge>\n ),\n },\n {\n key: 'projects',\n header: 'Projects',\n priority: 'hidden-on-mobile',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (c) => <span className=\"tabular-nums\">{c.projects}</span>,\n },\n {\n key: 'revenue',\n header: 'Value',\n priority: 'secondary',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (c) => <span className=\"tabular-nums\">${c.revenue.toLocaleString()}</span>,\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>Clients & Vendors</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {clients.length} clients · {vendors.length} vendors\n </p>\n <PagePurpose className=\"mt-1\">\n Keep track of the clients you work with — their contacts, status, and engagements — so\n account relationships never slip through the cracks.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => modal.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Client\n </Button>\n </div>\n\n <SegmentedTabs\n aria-label=\"Clients and vendors\"\n variant=\"underline\"\n value=\"clients\"\n onChange={(id) => {\n if (id === 'vendors') {\n navigate('../company/vendor-management-page');\n }\n }}\n tabs={[\n { id: 'clients', label: 'Clients', count: clients.length },\n { id: 'vendors', label: 'Vendors', count: vendors.length },\n ]}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard label=\"Active Clients\" value={activeClients.length} />\n <StatCard label=\"Vendors\" value={vendors.length} />\n <StatCard label=\"Total Contract Value\" value={compactMoney(contractValue)} />\n <StatCard label=\"Client Revenue\" value={compactMoney(revenue)} />\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Client Portfolio</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search clients...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => {\n setSearch(event.target.value);\n setPage(1);\n }}\n />\n </div>\n <Select\n value={filterStatus}\n onValueChange={(value) => {\n setFilterStatus(value);\n setPage(1);\n }}\n >\n <SelectTrigger className=\"w-full sm:w-40\" aria-label=\"Filter by status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">All statuses</SelectItem>\n <SelectItem value=\"active\">Active</SelectItem>\n <SelectItem value=\"prospect\">Prospect</SelectItem>\n <SelectItem value=\"churned\">Churned</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <ResponsiveTable\n data={paged}\n rowKey={(c) => c.id}\n columns={columns}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No clients match\"\n description=\"Try a different search term or status filter.\"\n />\n }\n rowActions={(c) => (\n <div className=\"flex items-center justify-end\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n aria-label={`More actions for ${c.name}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onSelect={() => modal.openModal(c)}>\n <Pencil className=\"mr-2 h-4 w-4\" />\n Edit\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"text-status-error-text\"\n onSelect={() => setDeleteTarget(c)}\n >\n <Trash2 className=\"mr-2 h-4 w-4\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\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={modal.open}\n onOpenChange={modal.onOpenChange}\n title={modal.context ? 'Edit client' : 'Add client'}\n description=\"Track the client relationship, contacts, and engagement value.\"\n fields={CLIENT_FIELDS}\n initialValues={modal.context ? toInitialValues(modal.context) : undefined}\n submitLabel={modal.context ? 'Save changes' : 'Add client'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete client?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `This removes “${deleteTarget.name}” from your client portfolio. This action cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n if (deleteTarget) {\n void removeClient(deleteTarget.id);\n }\n setDeleteTarget(null);\n }}\n >\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompanyVendorManagementPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/CompanyVendorManagementPagePage.tsx"],"names":[],"mappings":"AAsGA,wBAAgB,+BAA+B,
|
|
1
|
+
{"version":3,"file":"CompanyVendorManagementPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/CompanyVendorManagementPagePage.tsx"],"names":[],"mappings":"AAsGA,wBAAgB,+BAA+B,gCA+P9C"}
|
|
@@ -5,7 +5,7 @@ import { AvatarStack as r } from "../components/viz/AvatarStack.js";
|
|
|
5
5
|
import { SegmentedTabs as ee } from "../components/viz/SegmentedTabs.js";
|
|
6
6
|
import "../components/viz/index.js";
|
|
7
7
|
import { FormModal as te, useFormModal as i } from "../components/forms/FormModal.js";
|
|
8
|
-
import {
|
|
8
|
+
import { clientRevenue as a, totalContractValue as o } from "../lib/companyFinance.js";
|
|
9
9
|
import { useState as s } from "react";
|
|
10
10
|
import { Mail as c, MoreHorizontal as l, Pencil as u, Plus as d, Search as f, Star as ne, Trash2 as re } from "lucide-react";
|
|
11
11
|
import { useNavigate as ie } from "react-router";
|
|
@@ -91,10 +91,10 @@ var z = [
|
|
|
91
91
|
rating: typeof e.rating == "number" ? e.rating : Number(e.rating) || 0
|
|
92
92
|
}), H = (e) => e >= 1e6 ? `$${(e / 1e6).toFixed(1)}M` : e >= 1e3 ? `$${Math.round(e / 1e3)}k` : `$${Math.round(e)}`;
|
|
93
93
|
function U() {
|
|
94
|
-
let { vendors: U, clients: W,
|
|
94
|
+
let { vendors: U, clients: W, addVendor: de, updateVendor: fe, removeVendor: pe } = e(), me = ie(), G = i(), [K, he] = s(""), [q, ge] = s("all"), [J, Y] = s(null), { page: _e, pageSize: X, offset: Z, setPage: Q } = ue({ initialPageSize: 12 }), ve = W.filter((e) => e.status === "active"), ye = o(W, U), be = a(W), $ = U.filter((e) => {
|
|
95
95
|
let t = `${e.name} ${e.category} ${e.contactPerson} ${e.email}`.toLowerCase().includes(K.toLowerCase()), n = q === "all" || e.status === q;
|
|
96
96
|
return t && n;
|
|
97
|
-
}),
|
|
97
|
+
}), xe = $.slice(Z, Z + X);
|
|
98
98
|
return /* @__PURE__ */ m("div", {
|
|
99
99
|
className: "space-y-6 p-6",
|
|
100
100
|
children: [
|
|
@@ -129,7 +129,7 @@ function U() {
|
|
|
129
129
|
variant: "underline",
|
|
130
130
|
value: "vendors",
|
|
131
131
|
onChange: (e) => {
|
|
132
|
-
e === "clients" &&
|
|
132
|
+
e === "clients" && me("../company/client-management-page");
|
|
133
133
|
},
|
|
134
134
|
tabs: [{
|
|
135
135
|
id: "clients",
|
|
@@ -146,7 +146,7 @@ function U() {
|
|
|
146
146
|
children: [
|
|
147
147
|
/* @__PURE__ */ p(n, {
|
|
148
148
|
label: "Active Clients",
|
|
149
|
-
value:
|
|
149
|
+
value: ve.length
|
|
150
150
|
}),
|
|
151
151
|
/* @__PURE__ */ p(n, {
|
|
152
152
|
label: "Vendors",
|
|
@@ -154,11 +154,11 @@ function U() {
|
|
|
154
154
|
}),
|
|
155
155
|
/* @__PURE__ */ p(n, {
|
|
156
156
|
label: "Total Contract Value",
|
|
157
|
-
value: H(
|
|
157
|
+
value: H(ye)
|
|
158
158
|
}),
|
|
159
159
|
/* @__PURE__ */ p(n, {
|
|
160
|
-
label: "
|
|
161
|
-
value: H(
|
|
160
|
+
label: "Client Revenue",
|
|
161
|
+
value: H(be)
|
|
162
162
|
})
|
|
163
163
|
]
|
|
164
164
|
}),
|
|
@@ -171,13 +171,13 @@ function U() {
|
|
|
171
171
|
className: "pl-9",
|
|
172
172
|
value: K,
|
|
173
173
|
onChange: (e) => {
|
|
174
|
-
|
|
174
|
+
he(e.target.value), Q(1);
|
|
175
175
|
}
|
|
176
176
|
})]
|
|
177
177
|
}), /* @__PURE__ */ m(P, {
|
|
178
178
|
value: q,
|
|
179
179
|
onValueChange: (e) => {
|
|
180
|
-
|
|
180
|
+
ge(e), Q(1);
|
|
181
181
|
},
|
|
182
182
|
children: [/* @__PURE__ */ p(L, {
|
|
183
183
|
className: "w-full sm:w-40",
|
|
@@ -219,7 +219,7 @@ function U() {
|
|
|
219
219
|
title: "No vendors match",
|
|
220
220
|
description: "Try a different search term or status filter."
|
|
221
221
|
})
|
|
222
|
-
}) :
|
|
222
|
+
}) : xe.map((e) => /* @__PURE__ */ m(O, {
|
|
223
223
|
treatment: "glass",
|
|
224
224
|
children: [/* @__PURE__ */ p(x, { children: /* @__PURE__ */ m("div", {
|
|
225
225
|
className: "flex items-center justify-between gap-2",
|
|
@@ -305,7 +305,7 @@ function U() {
|
|
|
305
305
|
}, e.id))
|
|
306
306
|
}),
|
|
307
307
|
$.length > X && /* @__PURE__ */ p(N, {
|
|
308
|
-
page:
|
|
308
|
+
page: _e,
|
|
309
309
|
pageSize: X,
|
|
310
310
|
totalItems: $.length,
|
|
311
311
|
onPageChange: Q
|
|
@@ -319,7 +319,7 @@ function U() {
|
|
|
319
319
|
initialValues: G.context ? B(G.context) : void 0,
|
|
320
320
|
submitLabel: G.context ? "Save changes" : "Add vendor",
|
|
321
321
|
onSubmit: async (e) => {
|
|
322
|
-
G.context ? await
|
|
322
|
+
G.context ? await fe(G.context.id, V(e, G.context)) : await de(V(e));
|
|
323
323
|
}
|
|
324
324
|
}),
|
|
325
325
|
/* @__PURE__ */ p(ae, {
|
|
@@ -329,7 +329,7 @@ function U() {
|
|
|
329
329
|
},
|
|
330
330
|
children: /* @__PURE__ */ m(ce, { children: [/* @__PURE__ */ m(g, { children: [/* @__PURE__ */ p(_, { children: "Delete vendor?" }), /* @__PURE__ */ p(le, { children: J ? `This removes “${J.name}” from your vendor directory. This action cannot be undone.` : "" })] }), /* @__PURE__ */ m(h, { children: [/* @__PURE__ */ p(se, { children: "Cancel" }), /* @__PURE__ */ p(oe, {
|
|
331
331
|
onClick: (e) => {
|
|
332
|
-
e.preventDefault(), J &&
|
|
332
|
+
e.preventDefault(), J && pe(J.id), Y(null);
|
|
333
333
|
},
|
|
334
334
|
children: "Delete"
|
|
335
335
|
})] })] })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompanyVendorManagementPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CompanyVendorManagementPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n CardContent,\n CardHeader,\n CardTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { Mail, MoreHorizontal, Pencil, Plus, Search, Star, Trash2 } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { useCrewFoundryStore, type Vendor } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, SegmentedTabs, StatCard } from '../components/viz';\nimport { totalContractValue, thisQuarterRevenue } from '../lib/companyFinance';\n\nconst VENDOR_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Vendor name', type: 'text', required: true },\n { name: 'category', label: 'Category', type: 'text', required: true },\n { name: 'contactPerson', label: 'Contact person', type: 'text' },\n { name: 'email', label: 'Contact email', type: 'email' },\n { name: 'phone', label: 'Phone', type: 'text' },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Inactive', value: 'inactive' },\n { label: 'Pending', value: 'pending' },\n ],\n },\n { name: 'contractValue', label: 'Contract value (USD)', type: 'number' },\n { name: 'rating', label: 'Rating (0-5)', type: 'number' },\n];\n\nconst toInitialValues = (vendor: Vendor): FormValues => ({\n name: vendor.name,\n category: vendor.category,\n contactPerson: vendor.contactPerson,\n email: vendor.email,\n phone: vendor.phone,\n status: vendor.status,\n contractValue: vendor.contractValue,\n rating: vendor.rating,\n});\n\nconst valuesToVendor = (values: FormValues, base?: Vendor): Vendor => ({\n id: base?.id ?? crypto.randomUUID(),\n name: String(values.name ?? ''),\n category: String(values.category ?? ''),\n contactPerson: String(values.contactPerson ?? ''),\n email: String(values.email ?? ''),\n phone: String(values.phone ?? ''),\n status: (values.status as Vendor['status']) || 'pending',\n contractValue:\n typeof values.contractValue === 'number'\n ? values.contractValue\n : Number(values.contractValue) || 0,\n rating: typeof values.rating === 'number' ? values.rating : Number(values.rating) || 0,\n});\n\nconst compactMoney = (value: number): string => {\n if (value >= 1_000_000) {\n return `$${(value / 1_000_000).toFixed(1)}M`;\n }\n if (value >= 1_000) {\n return `$${Math.round(value / 1_000)}k`;\n }\n return `$${Math.round(value)}`;\n};\n\nexport function CompanyVendorManagementPagePage() {\n const { vendors, clients, invoices, addVendor, updateVendor, removeVendor } =\n useCrewFoundryStore();\n const navigate = useNavigate();\n const modal = useFormModal<Vendor>();\n const [search, setSearch] = useState('');\n const [filterStatus, setFilterStatus] = useState<string>('all');\n const [deleteTarget, setDeleteTarget] = useState<Vendor | null>(null);\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 12 });\n\n const activeClients = clients.filter((c) => c.status === 'active');\n const contractValue = totalContractValue(clients, vendors);\n const quarterRevenue = thisQuarterRevenue(invoices);\n\n const filtered = vendors.filter((v) => {\n const matchSearch = `${v.name} ${v.category} ${v.contactPerson} ${v.email}`\n .toLowerCase()\n .includes(search.toLowerCase());\n const matchStatus = filterStatus === 'all' || v.status === filterStatus;\n return matchSearch && matchStatus;\n });\n const paged = filtered.slice(offset, offset + pageSize);\n\n const handleSubmit = async (values: FormValues) => {\n if (modal.context) {\n await updateVendor(modal.context.id, valuesToVendor(values, modal.context));\n } else {\n await addVendor(valuesToVendor(values));\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>Clients & Vendors</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {clients.length} clients · {vendors.length} vendors\n </p>\n <PagePurpose className=\"mt-1\">\n Track the vendors and suppliers you depend on — contracts, contacts, and status — to\n manage external relationships in one place.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => modal.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Vendor\n </Button>\n </div>\n\n <SegmentedTabs\n aria-label=\"Clients and vendors\"\n variant=\"underline\"\n value=\"vendors\"\n onChange={(id) => {\n if (id === 'clients') {\n navigate('../company/client-management-page');\n }\n }}\n tabs={[\n { id: 'clients', label: 'Clients', count: clients.length },\n { id: 'vendors', label: 'Vendors', count: vendors.length },\n ]}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard label=\"Active Clients\" value={activeClients.length} />\n <StatCard label=\"Vendors\" value={vendors.length} />\n <StatCard label=\"Total Contract Value\" value={compactMoney(contractValue)} />\n <StatCard label=\"This Quarter\" value={compactMoney(quarterRevenue)} />\n </div>\n\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search vendors...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => {\n setSearch(event.target.value);\n setPage(1);\n }}\n />\n </div>\n <Select\n value={filterStatus}\n onValueChange={(value) => {\n setFilterStatus(value);\n setPage(1);\n }}\n >\n <SelectTrigger className=\"w-full sm:w-40\" aria-label=\"Filter by status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">All statuses</SelectItem>\n <SelectItem value=\"active\">Active</SelectItem>\n <SelectItem value=\"inactive\">Inactive</SelectItem>\n <SelectItem value=\"pending\">Pending</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {vendors.length === 0 ? (\n <div className=\"col-span-full\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No vendors yet\"\n description=\"Add a vendor to start tracking external relationships.\"\n />\n </div>\n ) : filtered.length === 0 ? (\n <div className=\"col-span-full\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No vendors match\"\n description=\"Try a different search term or status filter.\"\n />\n </div>\n ) : (\n paged.map((vendor) => (\n <GlassCard key={vendor.id} treatment=\"glass\">\n <CardHeader>\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <AvatarStack people={[{ name: vendor.name }]} size={36} />\n <CardTitle className=\"truncate text-base\">{vendor.name}</CardTitle>\n </div>\n <div className=\"flex items-center gap-1\">\n <Badge\n variant={vendor.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(vendor.status)}\n </Badge>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n aria-label={`More actions for ${vendor.name}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onSelect={() => modal.openModal(vendor)}>\n <Pencil className=\"mr-2 h-4 w-4\" />\n Edit\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"text-status-error-text\"\n onSelect={() => setDeleteTarget(vendor)}\n >\n <Trash2 className=\"mr-2 h-4 w-4\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {vendor.category}\n </Badge>\n <div className=\"grid grid-cols-2 gap-2 mt-2\">\n <div>\n <p className=\"text-xs text-text-secondary\">Contact</p>\n <p className=\"text-sm\">{vendor.contactPerson || '—'}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Contract Value</p>\n <p className=\"text-sm font-medium tabular-nums\">\n ${vendor.contractValue.toLocaleString()}\n </p>\n </div>\n </div>\n <div className=\"flex items-center justify-between pt-2 border-t mt-2\">\n <div className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <Mail className=\"h-3 w-3\" />\n {vendor.email || '—'}\n </div>\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n <span className=\"text-sm tabular-nums\">{vendor.rating}</span>\n </div>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n ))\n )}\n </div>\n {filtered.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n )}\n\n <FormModal\n open={modal.open}\n onOpenChange={modal.onOpenChange}\n title={modal.context ? 'Edit vendor' : 'Add vendor'}\n description=\"Track the vendor, its contract value, and primary contact.\"\n fields={VENDOR_FIELDS}\n initialValues={modal.context ? toInitialValues(modal.context) : undefined}\n submitLabel={modal.context ? 'Save changes' : 'Add vendor'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete vendor?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `This removes “${deleteTarget.name}” from your vendor directory. This action cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n if (deleteTarget) {\n void removeVendor(deleteTarget.id);\n }\n setDeleteTarget(null);\n }}\n >\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CompanyVendorManagementPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CompanyVendorManagementPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n CardContent,\n CardHeader,\n CardTitle,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { Mail, MoreHorizontal, Pencil, Plus, Search, Star, Trash2 } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { useCrewFoundryStore, type Vendor } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, SegmentedTabs, StatCard } from '../components/viz';\nimport { clientRevenue, totalContractValue } from '../lib/companyFinance';\n\nconst VENDOR_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Vendor name', type: 'text', required: true },\n { name: 'category', label: 'Category', type: 'text', required: true },\n { name: 'contactPerson', label: 'Contact person', type: 'text' },\n { name: 'email', label: 'Contact email', type: 'email' },\n { name: 'phone', label: 'Phone', type: 'text' },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Inactive', value: 'inactive' },\n { label: 'Pending', value: 'pending' },\n ],\n },\n { name: 'contractValue', label: 'Contract value (USD)', type: 'number' },\n { name: 'rating', label: 'Rating (0-5)', type: 'number' },\n];\n\nconst toInitialValues = (vendor: Vendor): FormValues => ({\n name: vendor.name,\n category: vendor.category,\n contactPerson: vendor.contactPerson,\n email: vendor.email,\n phone: vendor.phone,\n status: vendor.status,\n contractValue: vendor.contractValue,\n rating: vendor.rating,\n});\n\nconst valuesToVendor = (values: FormValues, base?: Vendor): Vendor => ({\n id: base?.id ?? crypto.randomUUID(),\n name: String(values.name ?? ''),\n category: String(values.category ?? ''),\n contactPerson: String(values.contactPerson ?? ''),\n email: String(values.email ?? ''),\n phone: String(values.phone ?? ''),\n status: (values.status as Vendor['status']) || 'pending',\n contractValue:\n typeof values.contractValue === 'number'\n ? values.contractValue\n : Number(values.contractValue) || 0,\n rating: typeof values.rating === 'number' ? values.rating : Number(values.rating) || 0,\n});\n\nconst compactMoney = (value: number): string => {\n if (value >= 1_000_000) {\n return `$${(value / 1_000_000).toFixed(1)}M`;\n }\n if (value >= 1_000) {\n return `$${Math.round(value / 1_000)}k`;\n }\n return `$${Math.round(value)}`;\n};\n\nexport function CompanyVendorManagementPagePage() {\n const { vendors, clients, addVendor, updateVendor, removeVendor } = useCrewFoundryStore();\n const navigate = useNavigate();\n const modal = useFormModal<Vendor>();\n const [search, setSearch] = useState('');\n const [filterStatus, setFilterStatus] = useState<string>('all');\n const [deleteTarget, setDeleteTarget] = useState<Vendor | null>(null);\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 12 });\n\n const activeClients = clients.filter((c) => c.status === 'active');\n const contractValue = totalContractValue(clients, vendors);\n const revenue = clientRevenue(clients);\n\n const filtered = vendors.filter((v) => {\n const matchSearch = `${v.name} ${v.category} ${v.contactPerson} ${v.email}`\n .toLowerCase()\n .includes(search.toLowerCase());\n const matchStatus = filterStatus === 'all' || v.status === filterStatus;\n return matchSearch && matchStatus;\n });\n const paged = filtered.slice(offset, offset + pageSize);\n\n const handleSubmit = async (values: FormValues) => {\n if (modal.context) {\n await updateVendor(modal.context.id, valuesToVendor(values, modal.context));\n } else {\n await addVendor(valuesToVendor(values));\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>Clients & Vendors</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {clients.length} clients · {vendors.length} vendors\n </p>\n <PagePurpose className=\"mt-1\">\n Track the vendors and suppliers you depend on — contracts, contacts, and status — to\n manage external relationships in one place.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => modal.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Vendor\n </Button>\n </div>\n\n <SegmentedTabs\n aria-label=\"Clients and vendors\"\n variant=\"underline\"\n value=\"vendors\"\n onChange={(id) => {\n if (id === 'clients') {\n navigate('../company/client-management-page');\n }\n }}\n tabs={[\n { id: 'clients', label: 'Clients', count: clients.length },\n { id: 'vendors', label: 'Vendors', count: vendors.length },\n ]}\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard label=\"Active Clients\" value={activeClients.length} />\n <StatCard label=\"Vendors\" value={vendors.length} />\n <StatCard label=\"Total Contract Value\" value={compactMoney(contractValue)} />\n <StatCard label=\"Client Revenue\" value={compactMoney(revenue)} />\n </div>\n\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search vendors...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => {\n setSearch(event.target.value);\n setPage(1);\n }}\n />\n </div>\n <Select\n value={filterStatus}\n onValueChange={(value) => {\n setFilterStatus(value);\n setPage(1);\n }}\n >\n <SelectTrigger className=\"w-full sm:w-40\" aria-label=\"Filter by status\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">All statuses</SelectItem>\n <SelectItem value=\"active\">Active</SelectItem>\n <SelectItem value=\"inactive\">Inactive</SelectItem>\n <SelectItem value=\"pending\">Pending</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {vendors.length === 0 ? (\n <div className=\"col-span-full\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No vendors yet\"\n description=\"Add a vendor to start tracking external relationships.\"\n />\n </div>\n ) : filtered.length === 0 ? (\n <div className=\"col-span-full\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No vendors match\"\n description=\"Try a different search term or status filter.\"\n />\n </div>\n ) : (\n paged.map((vendor) => (\n <GlassCard key={vendor.id} treatment=\"glass\">\n <CardHeader>\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <AvatarStack people={[{ name: vendor.name }]} size={36} />\n <CardTitle className=\"truncate text-base\">{vendor.name}</CardTitle>\n </div>\n <div className=\"flex items-center gap-1\">\n <Badge\n variant={vendor.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(vendor.status)}\n </Badge>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n aria-label={`More actions for ${vendor.name}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onSelect={() => modal.openModal(vendor)}>\n <Pencil className=\"mr-2 h-4 w-4\" />\n Edit\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"text-status-error-text\"\n onSelect={() => setDeleteTarget(vendor)}\n >\n <Trash2 className=\"mr-2 h-4 w-4\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {vendor.category}\n </Badge>\n <div className=\"grid grid-cols-2 gap-2 mt-2\">\n <div>\n <p className=\"text-xs text-text-secondary\">Contact</p>\n <p className=\"text-sm\">{vendor.contactPerson || '—'}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Contract Value</p>\n <p className=\"text-sm font-medium tabular-nums\">\n ${vendor.contractValue.toLocaleString()}\n </p>\n </div>\n </div>\n <div className=\"flex items-center justify-between pt-2 border-t mt-2\">\n <div className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <Mail className=\"h-3 w-3\" />\n {vendor.email || '—'}\n </div>\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n <span className=\"text-sm tabular-nums\">{vendor.rating}</span>\n </div>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n ))\n )}\n </div>\n {filtered.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n )}\n\n <FormModal\n open={modal.open}\n onOpenChange={modal.onOpenChange}\n title={modal.context ? 'Edit vendor' : 'Add vendor'}\n description=\"Track the vendor, its contract value, and primary contact.\"\n fields={VENDOR_FIELDS}\n initialValues={modal.context ? toInitialValues(modal.context) : undefined}\n submitLabel={modal.context ? 'Save changes' : 'Add vendor'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete vendor?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `This removes “${deleteTarget.name}” from your vendor directory. This action cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n if (deleteTarget) {\n void removeVendor(deleteTarget.id);\n }\n setDeleteTarget(null);\n }}\n >\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HelpApiDocumentationPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/HelpApiDocumentationPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HelpApiDocumentationPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/HelpApiDocumentationPagePage.tsx"],"names":[],"mappings":"AA0OA,wBAAgB,4BAA4B,gCA6S3C"}
|