@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/actions/ActionCard.js +2 -2
- package/dist/components/actions/ActionCard.js.map +1 -1
- package/dist/components/actions/ExecutionHistory.js +1 -1
- package/dist/components/actions/ExecutionHistory.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerAdminBar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerChatPane.js +2 -2
- package/dist/components/agent-manager/AgentManagerChatPane.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerInputArea.js +4 -4
- package/dist/components/agent-manager/AgentManagerInputArea.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js +1 -1
- package/dist/components/agent-manager/AgentManagerLayout.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js +1 -1
- package/dist/components/agent-manager/AgentManagerMessageItem.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerSidebar.js +5 -5
- package/dist/components/agent-manager/AgentManagerSidebar.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js +1 -1
- package/dist/components/agent-manager/AgentManagerTab.js.map +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js +1 -1
- package/dist/components/agent-manager/AgentManagerTabBar.js.map +1 -1
- package/dist/components/agent-manager/BridleConfigPanel.js +23 -23
- package/dist/components/agent-manager/BridleConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js +1 -1
- package/dist/components/agent-manager/PluginInstallModal.js.map +1 -1
- package/dist/components/agent-manager/ProfilesPanel.js +5 -5
- package/dist/components/agent-manager/ProfilesPanel.js.map +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js +1 -1
- package/dist/components/agent-manager/controls/AutonomySelector.js.map +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js +1 -1
- package/dist/components/agent-manager/controls/SdkSelector.js.map +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js +1 -1
- package/dist/components/agent-manager/controls/VoiceToggle.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionConfigPanel.js +6 -6
- package/dist/components/agent-manager/detail/SessionConfigPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionLogsPanel.js +5 -5
- package/dist/components/agent-manager/detail/SessionLogsPanel.js.map +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js +1 -1
- package/dist/components/agent-manager/detail/SessionToolsPanel.js.map +1 -1
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js +2 -2
- package/dist/components/agent-manager/dialogs/NewSessionDialog.js.map +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js +1 -1
- package/dist/components/agent-manager/dialogs/RenameSessionDialog.js.map +1 -1
- package/dist/components/agents/AgentCard.js +14 -11
- package/dist/components/agents/AgentCard.js.map +1 -1
- package/dist/components/ai/AIContextsTab.js +1 -1
- package/dist/components/ai/AIContextsTab.js.map +1 -1
- package/dist/components/ai/AIOverviewTab.js +4 -4
- package/dist/components/ai/AIOverviewTab.js.map +1 -1
- package/dist/components/ai/AIPlanPanel.js +4 -4
- package/dist/components/ai/AIPlanPanel.js.map +1 -1
- package/dist/components/ai/AIPlaygroundTab.js +1 -1
- package/dist/components/ai/AIPlaygroundTab.js.map +1 -1
- package/dist/components/ai/AIPromptsTab.js +2 -2
- package/dist/components/ai/AIPromptsTab.js.map +1 -1
- package/dist/components/ai/AIProviderCredentialsCard.js +4 -4
- package/dist/components/ai/AIProviderCredentialsCard.js.map +1 -1
- package/dist/components/ai/AIQueueTab.js +4 -4
- package/dist/components/ai/AIQueueTab.js.map +1 -1
- package/dist/components/ai/AISessionsTab.js +4 -4
- package/dist/components/ai/AISessionsTab.js.map +1 -1
- package/dist/components/ai/AITasksTab.js +1 -1
- package/dist/components/ai/AITasksTab.js.map +1 -1
- package/dist/components/ai/ClaudeCredentialsCard.js +3 -3
- package/dist/components/ai/ClaudeCredentialsCard.js.map +1 -1
- package/dist/components/ai/PluginHarnessPicker.js +3 -3
- package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
- package/dist/components/analytics/AnalyticsBreakdownChart.js +8 -8
- package/dist/components/analytics/AnalyticsBreakdownChart.js.map +1 -1
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js +5 -5
- package/dist/components/analytics/AnalyticsTimeSeriesChart.js.map +1 -1
- package/dist/components/audit/AuditDeleteDialog.js +2 -2
- package/dist/components/audit/AuditDeleteDialog.js.map +1 -1
- package/dist/components/audit/AuditExportButton.js +5 -5
- package/dist/components/audit/AuditExportButton.js.map +1 -1
- package/dist/components/audit/AuditLogDetail.js +9 -9
- package/dist/components/audit/AuditLogDetail.js.map +1 -1
- package/dist/components/audit/AuditLogFilters.js +1 -1
- package/dist/components/audit/AuditLogFilters.js.map +1 -1
- package/dist/components/audit/AuditLogStats.js +1 -1
- package/dist/components/audit/AuditLogStats.js.map +1 -1
- package/dist/components/audit/AuditLogTable.js +28 -28
- package/dist/components/audit/AuditLogTable.js.map +1 -1
- package/dist/components/catalog/CatalogCard.js +3 -3
- package/dist/components/catalog/CatalogCard.js.map +1 -1
- package/dist/components/catalog/CatalogForm.js +1 -1
- package/dist/components/catalog/CatalogForm.js.map +1 -1
- package/dist/components/common/ActionAgentTargetNotice.js +6 -6
- package/dist/components/common/ActionAgentTargetNotice.js.map +1 -1
- package/dist/components/common/CommandEditor.js +1 -1
- package/dist/components/common/CommandEditor.js.map +1 -1
- package/dist/components/docs/DocsPageTree.js +1 -1
- package/dist/components/docs/DocsPageTree.js.map +1 -1
- package/dist/components/docs/DocsPreviewPanel.js +1 -1
- package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
- package/dist/components/docs/DocsRevisionHistory.js +4 -4
- package/dist/components/docs/DocsRevisionHistory.js.map +1 -1
- package/dist/components/docs/DocsSettingsPanel.js +7 -7
- package/dist/components/docs/DocsSettingsPanel.js.map +1 -1
- package/dist/components/docs/DocsTabPanel.js +1 -1
- package/dist/components/docs/DocsTabPanel.js.map +1 -1
- package/dist/components/graph/ConnectionPermissionsDialog.js +2 -2
- package/dist/components/graph/ConnectionPermissionsDialog.js.map +1 -1
- package/dist/components/graph/MeshDeployWizard.js +3 -3
- package/dist/components/graph/MeshDeployWizard.js.map +1 -1
- package/dist/components/graph/MeshRequestPanel.js +2 -2
- package/dist/components/graph/MeshRequestPanel.js.map +1 -1
- package/dist/components/idp-components/ComponentCard.js +5 -5
- package/dist/components/idp-components/ComponentCard.js.map +1 -1
- package/dist/components/idp-components/ComponentForm.js +1 -1
- package/dist/components/idp-components/ComponentForm.js.map +1 -1
- package/dist/components/idp-components/DependencyGraphTab.js +7 -7
- package/dist/components/idp-components/DependencyGraphTab.js.map +1 -1
- package/dist/components/logs/LogViewer.js +14 -14
- package/dist/components/logs/LogViewer.js.map +1 -1
- package/dist/components/notes/NoteCard.js +1 -1
- package/dist/components/notes/NoteCard.js.map +1 -1
- package/dist/components/notes/NoteEditor.js +2 -2
- package/dist/components/notes/NoteEditor.js.map +1 -1
- package/dist/components/notes/NoteList.js +1 -1
- package/dist/components/notes/NoteList.js.map +1 -1
- package/dist/components/notes/NotesTabPanel.js +1 -1
- package/dist/components/notes/NotesTabPanel.js.map +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js +1 -1
- package/dist/components/scorecard/ScorecardEmptyState.js.map +1 -1
- package/dist/components/scorecard/ScorecardPanel.js +3 -3
- package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
- package/dist/components/security/SecurityScanList.js +11 -11
- package/dist/components/security/SecurityScanList.js.map +1 -1
- package/dist/components/sessions/SessionCard.js +26 -19
- package/dist/components/sessions/SessionCard.js.map +1 -1
- package/dist/components/sessions/SessionStatusBadge.js +7 -7
- package/dist/components/sessions/SessionStatusBadge.js.map +1 -1
- package/dist/components/shared/AgentSelector.js +4 -4
- package/dist/components/shared/AgentSelector.js.map +1 -1
- package/dist/components/shared/AgentSetupBanner.js +3 -3
- package/dist/components/shared/AgentSetupBanner.js.map +1 -1
- package/dist/components/shared/Card.js +4 -4
- package/dist/components/shared/Card.js.map +1 -1
- package/dist/components/shared/ErrorState.js +15 -9
- package/dist/components/shared/ErrorState.js.map +1 -1
- package/dist/components/shared/FilterDropdown.js +2 -2
- package/dist/components/shared/FilterDropdown.js.map +1 -1
- package/dist/components/shared/PageSkeleton.js +6 -6
- package/dist/components/shared/PageSkeleton.js.map +1 -1
- package/dist/components/shared/SearchInput.js +1 -1
- package/dist/components/shared/SearchInput.js.map +1 -1
- package/dist/components/shared/StatCard.js +7 -7
- package/dist/components/shared/StatCard.js.map +1 -1
- package/dist/components/templates/PublishTemplateForm.js +2 -2
- package/dist/components/templates/PublishTemplateForm.js.map +1 -1
- package/dist/components/templates/TemplateCard.js +5 -5
- package/dist/components/templates/TemplateCard.js.map +1 -1
- package/dist/components/templates/TemplateWizard.js +2 -2
- package/dist/components/templates/TemplateWizard.js.map +1 -1
- package/dist/components/tunnels/DomainVerificationBadge.js +4 -4
- package/dist/components/tunnels/DomainVerificationBadge.js.map +1 -1
- package/dist/components/tunnels/ProviderBadge.js +1 -1
- package/dist/components/tunnels/ProviderBadge.js.map +1 -1
- package/dist/components/tunnels/TunnelCard.js +16 -16
- package/dist/components/tunnels/TunnelCard.js.map +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js +1 -1
- package/dist/components/tunnels/TunnelPublicAccessPanel.js.map +1 -1
- package/dist/components/tunnels/TunnelStatusBadge.js +4 -4
- package/dist/components/tunnels/TunnelStatusBadge.js.map +1 -1
- package/dist/components/vibecalendar/LogsDrawer.js +2 -2
- package/dist/components/vibecalendar/LogsDrawer.js.map +1 -1
- package/dist/components/vibecalendar/ScheduleTaskModal.js +5 -5
- package/dist/components/vibecalendar/ScheduleTaskModal.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalAgendaView.js +2 -2
- package/dist/components/vibecalendar/VibeCalAgendaView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalDayView.js +3 -3
- package/dist/components/vibecalendar/VibeCalDayView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js +1 -1
- package/dist/components/vibecalendar/VibeCalMonthView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalWeekView.js +4 -4
- package/dist/components/vibecalendar/VibeCalWeekView.js.map +1 -1
- package/dist/components/vibecalendar/VibeCalendarHeader.js +4 -4
- package/dist/components/vibecalendar/VibeCalendarHeader.js.map +1 -1
- package/dist/components/vibecalendar/VibeTaskBlock.js +2 -2
- package/dist/components/vibecalendar/VibeTaskBlock.js.map +1 -1
- package/dist/components/vibecalendar/types.js +6 -6
- package/dist/components/vibecalendar/types.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonForm.js +9 -9
- package/dist/components/vibedeck/VibeDeckButtonForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckButtonTile.js +6 -6
- package/dist/components/vibedeck/VibeDeckButtonTile.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckExecutionLog.js +10 -10
- package/dist/components/vibedeck/VibeDeckExecutionLog.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckForm.js +8 -8
- package/dist/components/vibedeck/VibeDeckForm.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckGrid.js +2 -2
- package/dist/components/vibedeck/VibeDeckGrid.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js +2 -2
- package/dist/components/vibedeck/VibeDeckPublicLinkPanel.js.map +1 -1
- package/dist/components/vibedeck/VibeDeckSwitcher.js +8 -8
- package/dist/components/vibedeck/VibeDeckSwitcher.js.map +1 -1
- package/dist/components/vibes/VibeCard.js +22 -18
- package/dist/components/vibes/VibeCard.js.map +1 -1
- package/dist/components/vibes/VibeList.js +16 -12
- package/dist/components/vibes/VibeList.js.map +1 -1
- package/dist/pages/ActionsPage.js +4 -3
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js +9 -9
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/AgentsLandingPage.js +4 -4
- package/dist/pages/AgentsLandingPage.js.map +1 -1
- package/dist/pages/AgentsPage.js +14 -14
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/AnalyticsPage.js +8 -8
- package/dist/pages/AnalyticsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +11 -11
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/CatalogPage.js +12 -9
- package/dist/pages/CatalogPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +7 -7
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ComponentsPage.js +12 -9
- package/dist/pages/ComponentsPage.js.map +1 -1
- package/dist/pages/EnvTopologyPage.js +2 -2
- package/dist/pages/EnvTopologyPage.js.map +1 -1
- package/dist/pages/GettingStartedPage.js +12 -12
- package/dist/pages/GettingStartedPage.js.map +1 -1
- package/dist/pages/HealthPage.js +12 -12
- package/dist/pages/HealthPage.js.map +1 -1
- package/dist/pages/LogsPage.js +4 -4
- package/dist/pages/LogsPage.js.map +1 -1
- package/dist/pages/NoteDetailsPage.js +5 -5
- package/dist/pages/NoteDetailsPage.js.map +1 -1
- package/dist/pages/OverviewPage.js +74 -41
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +6 -3
- package/dist/pages/PluginsCatalogPage.js.map +1 -1
- package/dist/pages/ScheduledJobDetailPage.js +7 -7
- package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
- package/dist/pages/SessionDetailsPage.js +21 -21
- package/dist/pages/SessionDetailsPage.js.map +1 -1
- package/dist/pages/SessionsPage.js +20 -20
- package/dist/pages/SessionsPage.js.map +1 -1
- package/dist/pages/SettingsPage.js +64 -52
- package/dist/pages/SettingsPage.js.map +1 -1
- package/dist/pages/SharedSessionPage.js +21 -21
- package/dist/pages/SharedSessionPage.js.map +1 -1
- package/dist/pages/SharedVibeDeckPage.js +5 -5
- package/dist/pages/SharedVibeDeckPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +56 -65
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TargetsPage.js +4 -4
- package/dist/pages/TargetsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +10 -10
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/TemplatesPage.js +6 -5
- package/dist/pages/TemplatesPage.js.map +1 -1
- package/dist/pages/VibeAuditPage.js +3 -3
- package/dist/pages/VibeAuditPage.js.map +1 -1
- package/dist/pages/VibeCalendarPage.js +3 -3
- package/dist/pages/VibeCalendarPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +3 -3
- package/dist/pages/VibeDeckPage.js.map +1 -1
- package/dist/pages/VibeDetailsPage.js +15 -15
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibeTaskDetailPage.js +5 -5
- package/dist/pages/VibeTaskDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js +18 -18
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/pages/WebhooksPage.js +11 -11
- package/dist/pages/WebhooksPage.js.map +1 -1
- package/dist/pages/ai/AIBookmarksPage.js +1 -1
- package/dist/pages/ai/AIBookmarksPage.js.map +1 -1
- package/dist/pages/ai/AISessionDetailPage.js +6 -6
- package/dist/pages/ai/AISessionDetailPage.js.map +1 -1
- package/dist/pages/tunnels/AuditTab.js +1 -1
- package/dist/pages/tunnels/AuditTab.js.map +1 -1
- package/dist/pages/tunnels/DomainsTab.js +1 -1
- package/dist/pages/tunnels/DomainsTab.js.map +1 -1
- package/dist/pages/tunnels/ProvidersTab.js +1 -1
- package/dist/pages/tunnels/ProvidersTab.js.map +1 -1
- package/dist/pages/tunnels/SessionsTab.js +1 -1
- package/dist/pages/tunnels/SessionsTab.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +3 -3
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/dist/pages/tunnels/UsageTab.js +1 -1
- package/dist/pages/tunnels/UsageTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js +16 -16
- package/dist/pages/tunnels/tabs/TunnelDomainsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js +1 -1
- package/dist/pages/tunnels/tabs/TunnelOverviewTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js +52 -49
- package/dist/pages/tunnels/tabs/TunnelSessionsTab.js.map +1 -1
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js +9 -9
- package/dist/pages/tunnels/tabs/TunnelSettingsTab.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NotesTabPanel.js","names":[],"sources":["../../../src/components/notes/NotesTabPanel.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useState, useMemo } from 'react';\nimport { Plus, FileText, Grid, List, Pin, RefreshCw, Lock, Globe } from 'lucide-react';\nimport { AnimatePresence } from 'framer-motion';\nimport { useNavigate } from 'react-router-dom';\nimport { useApolloClient } from '@apollo/client/react';\nimport { MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { NoteCard } from './NoteCard';\nimport { NoteList } from './NoteList';\nimport { NoteEditor } from './NoteEditor';\nimport type { Note, NoteVisibility } from './types';\nimport type { NoteEditorSubmitData } from './NoteEditor';\nimport {\n SearchInput,\n FilterDropdown,\n EmptyState,\n LoadingState,\n ErrorState,\n QuotaExhaustedDialog,\n} from '@/components/shared';\nimport {\n useListNotesQuery,\n usePinNoteMutation,\n useUnpinNoteMutation,\n useDeleteNoteMutation,\n useCreateNoteMutation,\n useUpdateNoteMutation,\n useReplaceNoteTagsMutation,\n useAvailableNoteTagsQuery,\n useShareNoteMutation,\n ListNotesDocument,\n} from '@/generated/wspace-operations';\nimport { NoteVisibility as GqlNoteVisibility } from '@/generated/wspace-types';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { isQuotaExhaustedError, markQuotaHandledLocally } from '@/utils/quotaUtils';\nimport { NoteShareDialog, SHARE_REGISTRY } from '@/components/sharing';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface NotesTabPanelProps {\n /**\n * 'vibe' = single vibe context; 'agent' = multi-vibe agent context;\n * 'session' = notes authored against a single terminal session (F1).\n */\n context: 'vibe' | 'agent' | 'session';\n /** For vibe context: the single vibeId to scope notes to */\n vibeId?: string;\n /** For session context: the single sessionId to scope notes to */\n sessionId?: string;\n /** For agent context: the vibes belonging to the agent */\n agentVibes?: { id: string; name: string }[];\n}\n\n/**\n * Reusable notes panel for embedding as a tab in detail pages.\n * Supports vibe-scoped, agent-scoped, and session-scoped contexts.\n */\nexport function NotesTabPanel({ context, vibeId, sessionId, agentVibes = [] }: NotesTabPanelProps) {\n const { t } = useI18n();\n const navigate = useNavigate();\n const apolloClient = useApolloClient();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const canListNotes = hasPermission(VIBE_PERMISSIONS.NOTE_LIST);\n const canCreateNote = hasPermission(VIBE_PERMISSIONS.NOTE_CREATE);\n\n // Generic note sharing (full users/link/vibe dialog). Gated by the\n // `vibecontrols.sharing.note` flag (default ON) + note share permission.\n // Distinct from the legacy `onShare` visibility toggle, which stays as-is.\n const noteSharingFlagEnabled = useFeatureFlag(\n SHARE_REGISTRY.note.featureFlag,\n SHARE_REGISTRY.note.defaultEnabled\n );\n const canShareNote = hasPermission(SHARE_REGISTRY.note.permission);\n const noteSharingEnabled = noteSharingFlagEnabled && canShareNote;\n const [genericShareNote, setGenericShareNote] = useState<Note | null>(null);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [vibeFilter, setVibeFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [visibilityFilter, setVisibilityFilter] = useState<'all' | 'PRIVATE' | 'WORKSPACE'>('all');\n const [showPinnedOnly, setShowPinnedOnly] = useState(false);\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showEditor, setShowEditor] = useState(false);\n const [selectedNote, setSelectedNote] = useState<Note | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n\n // Evict cached notes list and refetch — bypasses stale cache-and-network data\n const evictAndRefetch = (refetch: () => void) => {\n apolloClient.cache.evict({ id: 'ROOT_QUERY', fieldName: 'notes' });\n apolloClient.cache.gc();\n refetch();\n };\n\n const [pinNote] = usePinNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [unpinNote] = useUnpinNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [deleteNote] = useDeleteNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [createNote] = useCreateNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [updateNote] = useUpdateNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [replaceNoteTags] = useReplaceNoteTagsMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [shareNote] = useShareNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n\n const shouldSkipQueries = permissionsLoading || !canListNotes;\n\n const { data: availableTagsData } = useAvailableNoteTagsQuery({ skip: shouldSkipQueries });\n\n // Build query filter based on context\n const queryFilter = useMemo(() => {\n const visibilityVal =\n visibilityFilter !== 'all' ? (visibilityFilter as GqlNoteVisibility) : undefined;\n\n if (context === 'session' && sessionId) {\n return {\n sessionId,\n isPinned: showPinnedOnly ? true : undefined,\n visibility: visibilityVal,\n };\n }\n if (context === 'vibe' && vibeId) {\n return {\n vibeId,\n isPinned: showPinnedOnly ? true : undefined,\n visibility: visibilityVal,\n };\n }\n // Agent context: fetch all notes, filter client-side\n return {\n vibeId: vibeFilter === 'none' ? null : vibeFilter !== 'all' ? vibeFilter : undefined,\n isPinned: showPinnedOnly ? true : undefined,\n visibility: visibilityVal,\n };\n }, [context, vibeId, sessionId, vibeFilter, showPinnedOnly, visibilityFilter]);\n\n const { data, loading, error, refetch } = useListNotesQuery({\n variables: { filter: queryFilter, pagination: { limit: 50 } },\n fetchPolicy: 'cache-and-network',\n skip: shouldSkipQueries,\n });\n\n // Vibe filter options (agent context only)\n const vibeOptions = useMemo(() => {\n if (context !== 'agent') return [];\n return [\n { value: 'all', label: 'All Vibes' },\n { value: 'none', label: 'No Vibe (General)' },\n ...agentVibes.map((v) => ({ value: v.id, label: v.name })),\n ];\n }, [context, agentVibes]);\n\n // Tag label lookup by ID — used when federation doesn't return nested tag object\n type AvailableTag = { id: string; key: string; label: string; color?: string | null };\n const tagLabelMap = useMemo((): Map<string, AvailableTag> => {\n const tags = (availableTagsData?.availableNoteTags ?? []) as AvailableTag[];\n return new Map(tags.map((tag) => [tag.id, tag]));\n }, [availableTagsData?.availableNoteTags]);\n\n // Map GraphQL response to Note type\n const notes: Note[] = useMemo(() => {\n const rawNotes = data?.notes?.items ?? [];\n const mapped = rawNotes.map((note: NonNullable<typeof rawNotes>[number]) => ({\n id: note.id,\n workspaceId: note.workspaceId,\n vibeId: note.vibeId || null,\n title: note.title,\n content: note.content,\n tags: note.tags || [],\n tagAssignments:\n note.tagAssignments?.map((a: NonNullable<typeof note.tagAssignments>[number]) => {\n // `a.tag` may be null when federation doesn't resolve the tag entity.\n // Fall back to the availableNoteTags lookup so the label always shows.\n const resolvedTag = a.tag\n ? { id: a.tag.id, label: a.tag.label, key: a.tag.key, color: a.tag.color }\n : tagLabelMap.has(a.tagId)\n ? {\n id: a.tagId,\n label: tagLabelMap.get(a.tagId)!.label,\n key: tagLabelMap.get(a.tagId)!.key,\n color: tagLabelMap.get(a.tagId)!.color ?? null,\n }\n : undefined;\n return {\n id: a.id,\n tagId: a.tagId,\n tag: resolvedTag,\n entityType: a.entityType,\n entityId: a.entityId,\n source: a.source,\n assignedBy: a.assignedBy,\n assignedAt: a.assignedAt,\n };\n }) || [],\n isPinned: note.isPinned,\n visibility: (note.visibility ?? 'WORKSPACE') as NoteVisibility,\n createdBy: note.createdBy ?? '',\n metadata: (note.metadata as Record<string, unknown>) || {},\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n vibe: note.vibe ? { id: note.vibe.id, name: note.vibe.name } : undefined,\n }));\n\n // Agent context: client-side filter to only agent's vibes\n if (context === 'agent' && agentVibes.length > 0) {\n const vibeIdSet = new Set(agentVibes.map((v) => v.id));\n return mapped.filter((n: Note) => n.vibeId && vibeIdSet.has(n.vibeId));\n }\n\n return mapped;\n }, [data?.notes?.items, context, agentVibes, tagLabelMap]);\n\n // Tag filter options\n const tagOptions = useMemo(() => {\n const tags = availableTagsData?.availableNoteTags ?? [];\n return [\n { value: 'all', label: 'All Tags' },\n ...tags.map((tag: AvailableTag) => ({\n value: tag.id,\n label: tag.label,\n })),\n ];\n }, [availableTagsData?.availableNoteTags]);\n\n // Client-side search and tag filtering\n const filteredNotes = useMemo(() => {\n return notes.filter((note) => {\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n note.title.toLowerCase().includes(query) ||\n note.content.toLowerCase().includes(query) ||\n note.tagAssignments?.some((a) => a.tag?.label?.toLowerCase().includes(query));\n if (!matchesSearch) return false;\n }\n if (tagFilter !== 'all') {\n const hasTag = note.tagAssignments?.some((a) => a.tagId === tagFilter);\n if (!hasTag) return false;\n }\n return true;\n });\n }, [notes, searchQuery, tagFilter]);\n\n // Sort: pinned first, then by updatedAt\n const sortedNotes = useMemo(() => {\n return [...filteredNotes].sort((a, b) => {\n if (a.isPinned && !b.isPinned) return -1;\n if (!a.isPinned && b.isPinned) return 1;\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n });\n }, [filteredNotes]);\n\n const handleNoteSelect = (note: Note) => {\n setSelectedNote(note);\n setShowEditor(true);\n };\n\n const handleCreateNote = () => {\n setSelectedNote(null);\n setShowEditor(true);\n };\n\n const handlePinNote = async (note: Note) => {\n try {\n if (note.isPinned) {\n await unpinNote({ variables: { id: note.id } });\n } else {\n await pinNote({ variables: { id: note.id } });\n }\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to toggle pin for note:', err);\n }\n };\n\n const handleDeleteNote = async (note: Note) => {\n try {\n await deleteNote({ variables: { id: note.id } });\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to delete note:', err);\n }\n };\n\n const handleSaveNote = async (noteData: NoteEditorSubmitData) => {\n const closeEditorForQuota = () => {\n setShowEditor(false);\n setSelectedNote(null);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n };\n\n try {\n let noteId: string | undefined;\n\n // In vibe context, always force the vibeId\n const effectiveVibeId = context === 'vibe' ? vibeId : noteData.vibeId;\n\n if (noteData.id) {\n let result;\n try {\n result = await updateNote({\n variables: {\n id: noteData.id,\n input: {\n title: noteData.title,\n content: noteData.content,\n visibility: noteData.visibility\n ? (noteData.visibility as GqlNoteVisibility)\n : undefined,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.updateNote?.id;\n } else {\n let result;\n try {\n result = await createNote({\n variables: {\n input: {\n title: noteData.title || '',\n content: noteData.content || '',\n vibeId: effectiveVibeId,\n // F1 — stamp the session so the note shows in this session's\n // drawer; it stays workspace-reusable everywhere else.\n sessionId: context === 'session' ? sessionId : undefined,\n visibility: noteData.visibility\n ? (noteData.visibility as GqlNoteVisibility)\n : GqlNoteVisibility.Workspace,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.createNote?.id;\n }\n\n // Assign tags\n if (noteId && noteData.selectedTagIds && noteData.selectedTagIds.length > 0) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: noteData.selectedTagIds } },\n });\n } else if (noteId && noteData.selectedTagIds?.length === 0 && noteData.id) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: [] } },\n });\n }\n\n setShowEditor(false);\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to save note:', err);\n }\n };\n\n const handleShareNote = async (note: Note) => {\n const newVisibility =\n note.visibility === 'PRIVATE' ? GqlNoteVisibility.Workspace : GqlNoteVisibility.Private;\n try {\n await shareNote({ variables: { id: note.id, visibility: newVisibility } });\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to toggle note visibility:', err);\n }\n };\n\n // Opens the generic Share dialog for a note (users / link / vibe).\n const handleShareNoteAccess = noteSharingEnabled\n ? (note: Note) => setGenericShareNote(note)\n : undefined;\n\n // Stats\n const stats = {\n total: notes.length,\n pinned: notes.filter((n) => n.isPinned).length,\n };\n\n // Vibe options for editor\n const vibeOptionsForEditor = useMemo(() => {\n if (context === 'vibe') return [];\n return agentVibes;\n }, [context, agentVibes]);\n\n // Permission guard\n if (!permissionsLoading && permissions && !canListNotes) {\n return (\n <AccessDenied\n title=\"Access Denied\"\n description=\"You don't have permission to view notes.\"\n details=\"Required permission: note:list\"\n />\n );\n }\n\n if (loading && notes.length === 0) {\n return <LoadingState message=\"Loading notes...\" />;\n }\n\n if (error && notes.length === 0) {\n return (\n <ErrorState title=\"Failed to load notes\" message={error.message} onRetry={() => refetch()} />\n );\n }\n\n const isFiltered =\n searchQuery ||\n (context === 'agent' && vibeFilter !== 'all') ||\n tagFilter !== 'all' ||\n showPinnedOnly;\n\n return (\n <div className=\"space-y-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">Notes</h3>\n <p className=\"text-sm text-text-secondary\">\n {stats.total} {stats.total === 1 ? 'note' : 'notes'}, {stats.pinned} pinned\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title=\"Refresh notes\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <PermissionButton permission={VIBE_PERMISSIONS.NOTE_CREATE}>\n <button\n type=\"button\"\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n New Note\n </button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder=\"Search notes...\"\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"Grid view\"\n >\n <Grid className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"List view\"\n >\n <List className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n {context === 'agent' && (\n <FilterDropdown\n label=\"Vibe\"\n value={vibeFilter}\n options={vibeOptions}\n onChange={setVibeFilter}\n />\n )}\n <FilterDropdown\n label=\"Tag\"\n value={tagFilter}\n options={tagOptions}\n onChange={setTagFilter}\n />\n {/* Visibility filter */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-0.5\">\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('all')}\n className={`px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'all' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n All\n </button>\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('WORKSPACE')}\n className={`flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'WORKSPACE' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n <Globe className=\"size-3\" />\n Shared\n </button>\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('PRIVATE')}\n className={`flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'PRIVATE' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n <Lock className=\"size-3\" />\n Private\n </button>\n </div>\n </div>\n </div>\n\n {/* Pinned toggle */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowPinnedOnly(!showPinnedOnly)}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n showPinnedOnly\n ? 'bg-status-warning-bg text-status-warning-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80'\n }`}\n >\n <Pin className=\"size-3\" />\n Pinned ({stats.pinned})\n </button>\n </div>\n\n {/* Results count */}\n {isFiltered ? (\n <p className=\"text-sm text-text-secondary\">\n Showing {sortedNotes.length} of {notes.length} notes\n {loading && <span className=\"ml-2 text-text-muted\">(refreshing…)</span>}\n </p>\n ) : null}\n\n {/* Notes display */}\n <AnimatePresence mode=\"wait\">\n {sortedNotes.length > 0 ? (\n viewMode === 'grid' ? (\n <MotionList\n key=\"grid\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {sortedNotes.map((note) => (\n <MotionItem key={note.id}>\n <NoteCard\n note={note}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n onShare={handleShareNote}\n onShareAccess={handleShareNoteAccess}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <NoteList\n key=\"list\"\n notes={sortedNotes}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n onShare={handleShareNote}\n onShareAccess={handleShareNoteAccess}\n />\n )\n ) : (\n <EmptyState\n icon={<FileText className=\"size-8 text-text-secondary\" />}\n title={isFiltered ? 'No notes found' : 'No notes yet'}\n description={\n isFiltered\n ? 'Try adjusting your search or filters'\n : 'Create your first note to start documenting your work'\n }\n action={\n !isFiltered && canCreateNote ? (\n <button\n type=\"button\"\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n New Note\n </button>\n ) : undefined\n }\n />\n )}\n </AnimatePresence>\n\n {/* Note Editor Modal */}\n {showEditor && (\n <NoteEditor\n note={selectedNote || undefined}\n vibeOptions={vibeOptionsForEditor}\n defaultVibeId={context === 'vibe' ? vibeId : undefined}\n hideVibeSelect={context === 'vibe'}\n onSubmit={handleSaveNote}\n onCancel={() => setShowEditor(false)}\n />\n )}\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Note Quota Reached\"\n message=\"You've used all available note quota on your current plan. Upgrade your plan to create or update more notes.\"\n />\n\n {/* Generic note sharing dialog (users / link / vibe) */}\n {genericShareNote && (\n <NoteShareDialog\n note={{\n id: genericShareNote.id,\n title: genericShareNote.title,\n vibeId: genericShareNote.vibeId,\n }}\n open={Boolean(genericShareNote)}\n onOpenChange={(next) => {\n if (!next) setGenericShareNote(null);\n }}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NotesTabPanel.js","names":[],"sources":["../../../src/components/notes/NotesTabPanel.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useState, useMemo } from 'react';\nimport { Plus, FileText, Grid, List, Pin, RefreshCw, Lock, Globe } from 'lucide-react';\nimport { AnimatePresence } from 'framer-motion';\nimport { useNavigate } from 'react-router-dom';\nimport { useApolloClient } from '@apollo/client/react';\nimport { MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { NoteCard } from './NoteCard';\nimport { NoteList } from './NoteList';\nimport { NoteEditor } from './NoteEditor';\nimport type { Note, NoteVisibility } from './types';\nimport type { NoteEditorSubmitData } from './NoteEditor';\nimport {\n SearchInput,\n FilterDropdown,\n EmptyState,\n LoadingState,\n ErrorState,\n QuotaExhaustedDialog,\n} from '@/components/shared';\nimport {\n useListNotesQuery,\n usePinNoteMutation,\n useUnpinNoteMutation,\n useDeleteNoteMutation,\n useCreateNoteMutation,\n useUpdateNoteMutation,\n useReplaceNoteTagsMutation,\n useAvailableNoteTagsQuery,\n useShareNoteMutation,\n ListNotesDocument,\n} from '@/generated/wspace-operations';\nimport { NoteVisibility as GqlNoteVisibility } from '@/generated/wspace-types';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { isQuotaExhaustedError, markQuotaHandledLocally } from '@/utils/quotaUtils';\nimport { NoteShareDialog, SHARE_REGISTRY } from '@/components/sharing';\n\ntype ViewMode = 'grid' | 'list';\n\ninterface NotesTabPanelProps {\n /**\n * 'vibe' = single vibe context; 'agent' = multi-vibe agent context;\n * 'session' = notes authored against a single terminal session (F1).\n */\n context: 'vibe' | 'agent' | 'session';\n /** For vibe context: the single vibeId to scope notes to */\n vibeId?: string;\n /** For session context: the single sessionId to scope notes to */\n sessionId?: string;\n /** For agent context: the vibes belonging to the agent */\n agentVibes?: { id: string; name: string }[];\n}\n\n/**\n * Reusable notes panel for embedding as a tab in detail pages.\n * Supports vibe-scoped, agent-scoped, and session-scoped contexts.\n */\nexport function NotesTabPanel({ context, vibeId, sessionId, agentVibes = [] }: NotesTabPanelProps) {\n const { t } = useI18n();\n const navigate = useNavigate();\n const apolloClient = useApolloClient();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n\n const canListNotes = hasPermission(VIBE_PERMISSIONS.NOTE_LIST);\n const canCreateNote = hasPermission(VIBE_PERMISSIONS.NOTE_CREATE);\n\n // Generic note sharing (full users/link/vibe dialog). Gated by the\n // `vibecontrols.sharing.note` flag (default ON) + note share permission.\n // Distinct from the legacy `onShare` visibility toggle, which stays as-is.\n const noteSharingFlagEnabled = useFeatureFlag(\n SHARE_REGISTRY.note.featureFlag,\n SHARE_REGISTRY.note.defaultEnabled\n );\n const canShareNote = hasPermission(SHARE_REGISTRY.note.permission);\n const noteSharingEnabled = noteSharingFlagEnabled && canShareNote;\n const [genericShareNote, setGenericShareNote] = useState<Note | null>(null);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [vibeFilter, setVibeFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [visibilityFilter, setVisibilityFilter] = useState<'all' | 'PRIVATE' | 'WORKSPACE'>('all');\n const [showPinnedOnly, setShowPinnedOnly] = useState(false);\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showEditor, setShowEditor] = useState(false);\n const [selectedNote, setSelectedNote] = useState<Note | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n\n // Evict cached notes list and refetch — bypasses stale cache-and-network data\n const evictAndRefetch = (refetch: () => void) => {\n apolloClient.cache.evict({ id: 'ROOT_QUERY', fieldName: 'notes' });\n apolloClient.cache.gc();\n refetch();\n };\n\n const [pinNote] = usePinNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [unpinNote] = useUnpinNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [deleteNote] = useDeleteNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [createNote] = useCreateNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [updateNote] = useUpdateNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [replaceNoteTags] = useReplaceNoteTagsMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n const [shareNote] = useShareNoteMutation({\n refetchQueries: [ListNotesDocument],\n awaitRefetchQueries: true,\n });\n\n const shouldSkipQueries = permissionsLoading || !canListNotes;\n\n const { data: availableTagsData } = useAvailableNoteTagsQuery({ skip: shouldSkipQueries });\n\n // Build query filter based on context\n const queryFilter = useMemo(() => {\n const visibilityVal =\n visibilityFilter !== 'all' ? (visibilityFilter as GqlNoteVisibility) : undefined;\n\n if (context === 'session' && sessionId) {\n return {\n sessionId,\n isPinned: showPinnedOnly ? true : undefined,\n visibility: visibilityVal,\n };\n }\n if (context === 'vibe' && vibeId) {\n return {\n vibeId,\n isPinned: showPinnedOnly ? true : undefined,\n visibility: visibilityVal,\n };\n }\n // Agent context: fetch all notes, filter client-side\n return {\n vibeId: vibeFilter === 'none' ? null : vibeFilter !== 'all' ? vibeFilter : undefined,\n isPinned: showPinnedOnly ? true : undefined,\n visibility: visibilityVal,\n };\n }, [context, vibeId, sessionId, vibeFilter, showPinnedOnly, visibilityFilter]);\n\n const { data, loading, error, refetch } = useListNotesQuery({\n variables: { filter: queryFilter, pagination: { limit: 50 } },\n fetchPolicy: 'cache-and-network',\n skip: shouldSkipQueries,\n });\n\n // Vibe filter options (agent context only)\n const vibeOptions = useMemo(() => {\n if (context !== 'agent') return [];\n return [\n { value: 'all', label: 'All Vibes' },\n { value: 'none', label: 'No Vibe (General)' },\n ...agentVibes.map((v) => ({ value: v.id, label: v.name })),\n ];\n }, [context, agentVibes]);\n\n // Tag label lookup by ID — used when federation doesn't return nested tag object\n type AvailableTag = { id: string; key: string; label: string; color?: string | null };\n const tagLabelMap = useMemo((): Map<string, AvailableTag> => {\n const tags = (availableTagsData?.availableNoteTags ?? []) as AvailableTag[];\n return new Map(tags.map((tag) => [tag.id, tag]));\n }, [availableTagsData?.availableNoteTags]);\n\n // Map GraphQL response to Note type\n const notes: Note[] = useMemo(() => {\n const rawNotes = data?.notes?.items ?? [];\n const mapped = rawNotes.map((note: NonNullable<typeof rawNotes>[number]) => ({\n id: note.id,\n workspaceId: note.workspaceId,\n vibeId: note.vibeId || null,\n title: note.title,\n content: note.content,\n tags: note.tags || [],\n tagAssignments:\n note.tagAssignments?.map((a: NonNullable<typeof note.tagAssignments>[number]) => {\n // `a.tag` may be null when federation doesn't resolve the tag entity.\n // Fall back to the availableNoteTags lookup so the label always shows.\n const resolvedTag = a.tag\n ? { id: a.tag.id, label: a.tag.label, key: a.tag.key, color: a.tag.color }\n : tagLabelMap.has(a.tagId)\n ? {\n id: a.tagId,\n label: tagLabelMap.get(a.tagId)!.label,\n key: tagLabelMap.get(a.tagId)!.key,\n color: tagLabelMap.get(a.tagId)!.color ?? null,\n }\n : undefined;\n return {\n id: a.id,\n tagId: a.tagId,\n tag: resolvedTag,\n entityType: a.entityType,\n entityId: a.entityId,\n source: a.source,\n assignedBy: a.assignedBy,\n assignedAt: a.assignedAt,\n };\n }) || [],\n isPinned: note.isPinned,\n visibility: (note.visibility ?? 'WORKSPACE') as NoteVisibility,\n createdBy: note.createdBy ?? '',\n metadata: (note.metadata as Record<string, unknown>) || {},\n createdAt: note.createdAt,\n updatedAt: note.updatedAt,\n vibe: note.vibe ? { id: note.vibe.id, name: note.vibe.name } : undefined,\n }));\n\n // Agent context: client-side filter to only agent's vibes\n if (context === 'agent' && agentVibes.length > 0) {\n const vibeIdSet = new Set(agentVibes.map((v) => v.id));\n return mapped.filter((n: Note) => n.vibeId && vibeIdSet.has(n.vibeId));\n }\n\n return mapped;\n }, [data?.notes?.items, context, agentVibes, tagLabelMap]);\n\n // Tag filter options\n const tagOptions = useMemo(() => {\n const tags = availableTagsData?.availableNoteTags ?? [];\n return [\n { value: 'all', label: 'All Tags' },\n ...tags.map((tag: AvailableTag) => ({\n value: tag.id,\n label: tag.label,\n })),\n ];\n }, [availableTagsData?.availableNoteTags]);\n\n // Client-side search and tag filtering\n const filteredNotes = useMemo(() => {\n return notes.filter((note) => {\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n note.title.toLowerCase().includes(query) ||\n note.content.toLowerCase().includes(query) ||\n note.tagAssignments?.some((a) => a.tag?.label?.toLowerCase().includes(query));\n if (!matchesSearch) return false;\n }\n if (tagFilter !== 'all') {\n const hasTag = note.tagAssignments?.some((a) => a.tagId === tagFilter);\n if (!hasTag) return false;\n }\n return true;\n });\n }, [notes, searchQuery, tagFilter]);\n\n // Sort: pinned first, then by updatedAt\n const sortedNotes = useMemo(() => {\n return [...filteredNotes].sort((a, b) => {\n if (a.isPinned && !b.isPinned) return -1;\n if (!a.isPinned && b.isPinned) return 1;\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n });\n }, [filteredNotes]);\n\n const handleNoteSelect = (note: Note) => {\n setSelectedNote(note);\n setShowEditor(true);\n };\n\n const handleCreateNote = () => {\n setSelectedNote(null);\n setShowEditor(true);\n };\n\n const handlePinNote = async (note: Note) => {\n try {\n if (note.isPinned) {\n await unpinNote({ variables: { id: note.id } });\n } else {\n await pinNote({ variables: { id: note.id } });\n }\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to toggle pin for note:', err);\n }\n };\n\n const handleDeleteNote = async (note: Note) => {\n try {\n await deleteNote({ variables: { id: note.id } });\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to delete note:', err);\n }\n };\n\n const handleSaveNote = async (noteData: NoteEditorSubmitData) => {\n const closeEditorForQuota = () => {\n setShowEditor(false);\n setSelectedNote(null);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n };\n\n try {\n let noteId: string | undefined;\n\n // In vibe context, always force the vibeId\n const effectiveVibeId = context === 'vibe' ? vibeId : noteData.vibeId;\n\n if (noteData.id) {\n let result;\n try {\n result = await updateNote({\n variables: {\n id: noteData.id,\n input: {\n title: noteData.title,\n content: noteData.content,\n visibility: noteData.visibility\n ? (noteData.visibility as GqlNoteVisibility)\n : undefined,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.updateNote?.id;\n } else {\n let result;\n try {\n result = await createNote({\n variables: {\n input: {\n title: noteData.title || '',\n content: noteData.content || '',\n vibeId: effectiveVibeId,\n // F1 — stamp the session so the note shows in this session's\n // drawer; it stays workspace-reusable everywhere else.\n sessionId: context === 'session' ? sessionId : undefined,\n visibility: noteData.visibility\n ? (noteData.visibility as GqlNoteVisibility)\n : GqlNoteVisibility.Workspace,\n },\n },\n });\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n closeEditorForQuota();\n return;\n }\n\n throw err;\n }\n noteId = result.data?.createNote?.id;\n }\n\n // Assign tags\n if (noteId && noteData.selectedTagIds && noteData.selectedTagIds.length > 0) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: noteData.selectedTagIds } },\n });\n } else if (noteId && noteData.selectedTagIds?.length === 0 && noteData.id) {\n await replaceNoteTags({\n variables: { input: { noteId, tagIds: [] } },\n });\n }\n\n setShowEditor(false);\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to save note:', err);\n }\n };\n\n const handleShareNote = async (note: Note) => {\n const newVisibility =\n note.visibility === 'PRIVATE' ? GqlNoteVisibility.Workspace : GqlNoteVisibility.Private;\n try {\n await shareNote({ variables: { id: note.id, visibility: newVisibility } });\n evictAndRefetch(refetch);\n } catch (err) {\n console.error('Failed to toggle note visibility:', err);\n }\n };\n\n // Opens the generic Share dialog for a note (users / link / vibe).\n const handleShareNoteAccess = noteSharingEnabled\n ? (note: Note) => setGenericShareNote(note)\n : undefined;\n\n // Stats\n const stats = {\n total: notes.length,\n pinned: notes.filter((n) => n.isPinned).length,\n };\n\n // Vibe options for editor\n const vibeOptionsForEditor = useMemo(() => {\n if (context === 'vibe') return [];\n return agentVibes;\n }, [context, agentVibes]);\n\n // Permission guard\n if (!permissionsLoading && permissions && !canListNotes) {\n return (\n <AccessDenied\n title=\"Access Denied\"\n description=\"You don't have permission to view notes.\"\n details=\"Required permission: note:list\"\n />\n );\n }\n\n if (loading && notes.length === 0) {\n return <LoadingState message=\"Loading notes...\" />;\n }\n\n if (error && notes.length === 0) {\n return (\n <ErrorState title=\"Failed to load notes\" message={error.message} onRetry={() => refetch()} />\n );\n }\n\n const isFiltered =\n searchQuery ||\n (context === 'agent' && vibeFilter !== 'all') ||\n tagFilter !== 'all' ||\n showPinnedOnly;\n\n return (\n <div className=\"space-y-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">Notes</h3>\n <p className=\"text-sm text-text-secondary\">\n {stats.total} {stats.total === 1 ? 'note' : 'notes'}, {stats.pinned} pinned\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title=\"Refresh notes\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <PermissionButton permission={VIBE_PERMISSIONS.NOTE_CREATE}>\n <button\n type=\"button\"\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n New Note\n </button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder=\"Search notes...\"\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n type=\"button\"\n onClick={() => setViewMode('grid')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"Grid view\"\n >\n <Grid className=\"size-4 text-text-secondary\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setViewMode('list')}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title=\"List view\"\n >\n <List className=\"size-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n {context === 'agent' && (\n <FilterDropdown\n label=\"Vibe\"\n value={vibeFilter}\n options={vibeOptions}\n onChange={setVibeFilter}\n />\n )}\n <FilterDropdown\n label=\"Tag\"\n value={tagFilter}\n options={tagOptions}\n onChange={setTagFilter}\n />\n {/* Visibility filter */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-0.5\">\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('all')}\n className={`px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'all' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n All\n </button>\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('WORKSPACE')}\n className={`flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'WORKSPACE' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n <Globe className=\"size-3\" />\n Shared\n </button>\n <button\n type=\"button\"\n onClick={() => setVisibilityFilter('PRIVATE')}\n className={`flex items-center gap-1 px-2.5 py-1 text-xs rounded transition-colors ${visibilityFilter === 'PRIVATE' ? 'bg-bg-surface text-text-primary shadow-sm' : 'text-text-muted hover:text-text-secondary'}`}\n >\n <Lock className=\"size-3\" />\n Private\n </button>\n </div>\n </div>\n </div>\n\n {/* Pinned toggle */}\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setShowPinnedOnly(!showPinnedOnly)}\n className={`flex items-center gap-2 px-3 py-1.5 text-sm rounded-full transition-colors ${\n showPinnedOnly\n ? 'bg-status-warning-bg-subtle text-status-warning-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-sunken/80'\n }`}\n >\n <Pin className=\"size-3\" />\n Pinned ({stats.pinned})\n </button>\n </div>\n\n {/* Results count */}\n {isFiltered ? (\n <p className=\"text-sm text-text-secondary\">\n Showing {sortedNotes.length} of {notes.length} notes\n {loading && <span className=\"ml-2 text-text-muted\">(refreshing…)</span>}\n </p>\n ) : null}\n\n {/* Notes display */}\n <AnimatePresence mode=\"wait\">\n {sortedNotes.length > 0 ? (\n viewMode === 'grid' ? (\n <MotionList\n key=\"grid\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {sortedNotes.map((note) => (\n <MotionItem key={note.id}>\n <NoteCard\n note={note}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n onShare={handleShareNote}\n onShareAccess={handleShareNoteAccess}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <NoteList\n key=\"list\"\n notes={sortedNotes}\n onOpen={handleNoteSelect}\n onEdit={handleNoteSelect}\n onPin={handlePinNote}\n onUnpin={handlePinNote}\n onDelete={handleDeleteNote}\n onShare={handleShareNote}\n onShareAccess={handleShareNoteAccess}\n />\n )\n ) : (\n <EmptyState\n icon={<FileText className=\"size-8 text-text-secondary\" />}\n title={isFiltered ? 'No notes found' : 'No notes yet'}\n description={\n isFiltered\n ? 'Try adjusting your search or filters'\n : 'Create your first note to start documenting your work'\n }\n action={\n !isFiltered && canCreateNote ? (\n <button\n type=\"button\"\n onClick={handleCreateNote}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n New Note\n </button>\n ) : undefined\n }\n />\n )}\n </AnimatePresence>\n\n {/* Note Editor Modal */}\n {showEditor && (\n <NoteEditor\n note={selectedNote || undefined}\n vibeOptions={vibeOptionsForEditor}\n defaultVibeId={context === 'vibe' ? vibeId : undefined}\n hideVibeSelect={context === 'vibe'}\n onSubmit={handleSaveNote}\n onCancel={() => setShowEditor(false)}\n />\n )}\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Note Quota Reached\"\n message=\"You've used all available note quota on your current plan. Upgrade your plan to create or update more notes.\"\n />\n\n {/* Generic note sharing dialog (users / link / vibe) */}\n {genericShareNote && (\n <NoteShareDialog\n note={{\n id: genericShareNote.id,\n title: genericShareNote.title,\n vibeId: genericShareNote.vibeId,\n }}\n open={Boolean(genericShareNote)}\n onOpenChange={(next) => {\n if (!next) setGenericShareNote(null);\n }}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -156,7 +156,7 @@ var h = ({ ownerType: h, ownerId: g, workspaceId: _, applyingTemplateId: v, onAp
|
|
|
156
156
|
})]
|
|
157
157
|
}),
|
|
158
158
|
D && /* @__PURE__ */ p("p", {
|
|
159
|
-
className: "mt-2 rounded-md border border-status-warning-border bg-status-warning-bg px-2 py-1.5 text-[11px] text-status-warning-text",
|
|
159
|
+
className: "mt-2 rounded-md border border-status-warning-border bg-status-warning-bg-subtle px-2 py-1.5 text-[11px] text-status-warning-text",
|
|
160
160
|
children: "Copy this token now — it is shown only once. Store it in your CI secrets."
|
|
161
161
|
})
|
|
162
162
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScorecardEmptyState.js","names":[],"sources":["../../../src/components/scorecard/ScorecardEmptyState.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport {\n BarChart3,\n Plus,\n LayoutTemplate,\n TerminalSquare,\n Copy,\n Check,\n KeyRound,\n HelpCircle,\n} from 'lucide-react';\nimport { Card } from '@/components/shared';\nimport {\n useGetScorecardTemplatesQuery,\n useGetScorecardPushTokenInfoQuery,\n useRotateScorecardPushTokenMutation,\n type ScorecardTemplateFieldsFragment,\n} from '@/generated/wspace-operations';\n\ninterface ScorecardEmptyStateProps {\n ownerType: 'CATALOG' | 'COMPONENT' | 'VIBE';\n ownerId: string;\n workspaceId: string | null;\n applyingTemplateId: string | null;\n onApplyTemplate: (templateId: string) => void;\n onAddManual: () => void;\n}\n\n/**\n * Onboarding surface shown when a scorecard has no metrics. Explains what a\n * scorecard is and the three ways to populate it: apply a template, add a\n * metric by hand, or push from CI (with a copyable command + token control).\n */\nexport const ScorecardEmptyState: FC<ScorecardEmptyStateProps> = ({\n ownerType,\n ownerId,\n workspaceId,\n applyingTemplateId,\n onApplyTemplate,\n onAddManual,\n}) => {\n const { data: templatesData, loading: templatesLoading } = useGetScorecardTemplatesQuery({\n fetchPolicy: 'cache-and-network',\n });\n const { data: tokenData, refetch: refetchToken } = useGetScorecardPushTokenInfoQuery({\n fetchPolicy: 'cache-and-network',\n });\n const [rotateToken, { loading: rotating }] = useRotateScorecardPushTokenMutation();\n\n const [revealedToken, setRevealedToken] = useState<string | null>(null);\n const [copied, setCopied] = useState<string | null>(null);\n const [showHelp, setShowHelp] = useState(false);\n\n const templates: ScorecardTemplateFieldsFragment[] =\n templatesData?.vibecontrolsScorecardTemplates ?? [];\n const tokenInfo = tokenData?.vibecontrolsScorecardPushTokenInfo;\n\n const tokenForSnippet = revealedToken ?? tokenInfo?.tokenPrefix ?? '<SCORECARD_PUSH_TOKEN>';\n\n const curlSnippet = `curl -X POST \"$WORKSPACES_API_BASE/api/scorecard/push\" \\\\\n -H \"Authorization: Bearer ${tokenForSnippet}\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\n \"workspaceId\": \"${workspaceId ?? '<WORKSPACE_ID>'}\",\n \"ownerType\": \"${ownerType}\",\n \"ownerId\": \"${ownerId}\",\n \"metrics\": [\n { \"name\": \"Test coverage\", \"type\": \"TEST_COVERAGE\", \"value\": 86, \"threshold\": 80, \"unit\": \"%\", \"direction\": \"HIGHER_IS_BETTER\" },\n { \"name\": \"Open incidents\", \"type\": \"INCIDENT_COUNT\", \"value\": 0, \"threshold\": 0, \"unit\": \"count\", \"direction\": \"LOWER_IS_BETTER\" }\n ]\n }'`;\n\n const copy = async (text: string, key: string): Promise<void> => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(key);\n window.setTimeout(() => setCopied((c) => (c === key ? null : c)), 1500);\n } catch {\n // Clipboard may be unavailable (insecure context) — ignore silently.\n }\n };\n\n const handleGenerateToken = async (): Promise<void> => {\n const result = await rotateToken();\n const token = result.data?.rotateVibecontrolsScorecardPushToken.token;\n if (token) {\n setRevealedToken(token);\n await refetchToken();\n }\n };\n\n return (\n <div className=\"space-y-6\">\n {/* Intro */}\n <Card padding=\"lg\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex size-10 flex-shrink-0 items-center justify-center rounded-full bg-bg-sunken\">\n <BarChart3 className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">\n Track this {ownerType.toLowerCase()}'s health\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n A scorecard rolls up the quality and reliability signals for this entity into a single\n 0–100 score. Each metric has a value, a target threshold, and a direction; its status\n (passing / warning / failing) is derived automatically from how the value compares to\n the threshold. Populate it one of three ways:\n </p>\n </div>\n </div>\n </Card>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n {/* 1. Apply a template */}\n <Card padding=\"lg\">\n <div className=\"flex items-center gap-2\">\n <LayoutTemplate className=\"size-4 text-action-primary-text\" />\n <h4 className=\"font-medium text-text-primary\">Apply a template</h4>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Start from a standard set of checks defined for your workspace.\n </p>\n <div className=\"mt-3 space-y-2\">\n {templatesLoading && templates.length === 0 && (\n <p className=\"text-xs text-text-muted\">Loading templates…</p>\n )}\n {templates.map((tpl) => (\n <button\n key={tpl.id}\n type=\"button\"\n onClick={() => onApplyTemplate(tpl.id)}\n disabled={applyingTemplateId !== null}\n className=\"flex w-full items-center justify-between rounded-md border border-border-default px-3 py-2 text-left text-sm text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\"\n >\n <span className=\"min-w-0\">\n <span className=\"block truncate font-medium\">{tpl.name}</span>\n <span className=\"block truncate text-xs text-text-muted\">\n {tpl.metrics.length} metric{tpl.metrics.length === 1 ? '' : 's'}\n {tpl.isBuiltIn ? ' · starter' : ''}\n </span>\n </span>\n <span className=\"ml-2 flex-shrink-0 text-xs text-action-primary-text\">\n {applyingTemplateId === tpl.id ? 'Applying…' : 'Apply'}\n </span>\n </button>\n ))}\n {!templatesLoading && templates.length === 0 && (\n <p className=\"text-xs text-text-muted\">No templates yet.</p>\n )}\n </div>\n </Card>\n\n {/* 2. Add a metric manually */}\n <Card padding=\"lg\">\n <div className=\"flex items-center gap-2\">\n <Plus className=\"size-4 text-action-primary-text\" />\n <h4 className=\"font-medium text-text-primary\">Add a metric</h4>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Define a single metric by hand — name, value, threshold, and direction.\n </p>\n <button\n type=\"button\"\n onClick={onAddManual}\n className=\"mt-3 flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"size-4\" />\n Add metric\n </button>\n </Card>\n\n {/* 3. Push from CI */}\n <Card padding=\"lg\">\n <div className=\"flex items-center gap-2\">\n <TerminalSquare className=\"size-4 text-action-primary-text\" />\n <h4 className=\"font-medium text-text-primary\">Push from CI</h4>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Keep metrics fresh automatically from your pipeline via the push webhook.\n </p>\n <div className=\"mt-3 flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleGenerateToken}\n disabled={rotating}\n className=\"flex items-center gap-1.5 rounded-md border border-border-default px-3 py-1.5 text-xs text-text-primary hover:bg-bg-sunken disabled:opacity-60\"\n >\n <KeyRound className=\"size-3.5\" />\n {tokenInfo?.exists ? 'Regenerate token' : 'Generate token'}\n </button>\n {revealedToken && (\n <button\n type=\"button\"\n onClick={() => copy(revealedToken, 'token')}\n className=\"flex items-center gap-1 text-xs text-action-primary-text\"\n >\n {copied === 'token' ? (\n <Check className=\"size-3.5\" />\n ) : (\n <Copy className=\"size-3.5\" />\n )}\n Copy token\n </button>\n )}\n </div>\n {revealedToken && (\n <p className=\"mt-2 rounded-md border border-status-warning-border bg-status-warning-bg px-2 py-1.5 text-[11px] text-status-warning-text\">\n Copy this token now — it is shown only once. Store it in your CI secrets.\n </p>\n )}\n </Card>\n </div>\n\n {/* CI snippet */}\n <Card padding=\"lg\">\n <div className=\"flex items-center justify-between\">\n <h4 className=\"text-sm font-medium text-text-primary\">CI push example</h4>\n <button\n type=\"button\"\n onClick={() => copy(curlSnippet, 'curl')}\n className=\"flex items-center gap-1 text-xs text-action-primary-text\"\n >\n {copied === 'curl' ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n {copied === 'curl' ? 'Copied' : 'Copy'}\n </button>\n </div>\n <pre className=\"mt-2 overflow-x-auto rounded-md bg-bg-sunken p-3 text-[11px] leading-relaxed text-text-primary\">\n <code>{curlSnippet}</code>\n </pre>\n <p className=\"mt-2 text-[11px] text-text-muted\">\n Set <code className=\"rounded bg-bg-sunken px-1\">WORKSPACES_API_BASE</code> to your\n workspaces API base URL. Status is derived from value vs threshold; pass an explicit{' '}\n <code className=\"rounded bg-bg-sunken px-1\">status</code> only to override it.\n </p>\n </Card>\n\n {/* How scoring works */}\n <Card padding=\"lg\">\n <button\n type=\"button\"\n onClick={() => setShowHelp((s) => !s)}\n className=\"flex w-full items-center gap-2 text-left text-sm font-medium text-text-primary\"\n >\n <HelpCircle className=\"size-4 text-text-secondary\" />\n How metrics are measured\n </button>\n {showHelp && (\n <div className=\"mt-3 space-y-2 text-xs text-text-secondary\">\n <p>\n <strong className=\"text-text-primary\">Score</strong> — each metric is scored 0–100.\n For <em>higher-is-better</em> metrics the score is{' '}\n <code className=\"rounded bg-bg-sunken px-1\">value / threshold × 100</code>; for{' '}\n <em>lower-is-better</em> metrics (incidents, latency) it is{' '}\n <code className=\"rounded bg-bg-sunken px-1\">threshold / value × 100</code>. Both are\n capped at 100.\n </p>\n <p>\n <strong className=\"text-text-primary\">Status</strong> — meeting the threshold passes;\n within 10% of it warns; below that fails. You can pin a status manually if you need\n to.\n </p>\n <p>\n <strong className=\"text-text-primary\">Overall score</strong> — the weighted average of\n all metric scores. Give critical metrics a higher weight to make them count for more.\n </p>\n </div>\n )}\n </Card>\n </div>\n );\n};\n"],"mappings":";;;;;;;AAkCA,IAAa,KAAqD,EAChE,cACA,YACA,gBACA,uBACA,oBACA,qBACI;CACJ,IAAM,EAAE,MAAM,GAAe,SAAS,MAAqB,EAA8B,EACvF,aAAa,qBACd,CAAC,EACI,EAAE,MAAM,GAAW,SAAS,MAAiB,EAAkC,EACnF,aAAa,qBACd,CAAC,EACI,CAAC,GAAa,EAAE,SAAS,OAAc,GAAqC,EAE5E,CAAC,GAAe,KAAoB,EAAwB,KAAK,EACjE,CAAC,GAAQ,KAAa,EAAwB,KAAK,EACnD,CAAC,GAAU,KAAe,EAAS,GAAM,EAEzC,IACJ,GAAe,kCAAkC,EAAE,EAC/C,IAAY,GAAW,oCAIvB,IAAc;8BAFI,KAAiB,GAAW,eAAe,yBAGvB;;;sBAGxB,KAAe,iBAAiB;oBAClC,EAAU;kBACZ,EAAQ;;;;;OAOlB,IAAO,OAAO,GAAc,MAA+B;AAC/D,MAAI;AAGF,GAFA,MAAM,UAAU,UAAU,UAAU,EAAK,EACzC,EAAU,EAAI,EACd,OAAO,iBAAiB,GAAW,MAAO,MAAM,IAAM,OAAO,EAAG,EAAE,KAAK;UACjE;;AAcV,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,GAAD;IAAM,SAAQ;cACZ,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD,EAAW,WAAU,8BAA+B,CAAA;MAChD,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;gBAAd;OAAwD;OAC1C,EAAU,aAAa;OAAC;OACjC;SACL,kBAAC,KAAD;MAAG,WAAU;gBAAmC;MAK5C,CAAA,CACA,EAAA,CAAA,CACF;;IACD,CAAA;GAEP,kBAAC,OAAD;IAAK,WAAU;cAAf;KAEE,kBAAC,GAAD;MAAM,SAAQ;gBAAd;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAgB,WAAU,mCAAoC,CAAA,EAC9D,kBAAC,MAAD;SAAI,WAAU;mBAAgC;SAAqB,CAAA,CAC/D;;OACN,kBAAC,KAAD;QAAG,WAAU;kBAAmC;QAE5C,CAAA;OACJ,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACG,KAAoB,EAAU,WAAW,KACxC,kBAAC,KAAD;UAAG,WAAU;oBAA0B;UAAsB,CAAA;SAE9D,EAAU,KAAK,MACd,kBAAC,UAAD;UAEE,MAAK;UACL,eAAe,EAAgB,EAAI,GAAG;UACtC,UAAU,MAAuB;UACjC,WAAU;oBALZ,CAOE,kBAAC,QAAD;WAAM,WAAU;qBAAhB,CACE,kBAAC,QAAD;YAAM,WAAU;sBAA8B,EAAI;YAAY,CAAA,EAC9D,kBAAC,QAAD;YAAM,WAAU;sBAAhB;aACG,EAAI,QAAQ;aAAO;aAAQ,EAAI,QAAQ,WAAW,IAAI,KAAK;aAC3D,EAAI,YAAY,eAAe;aAC3B;cACF;cACP,kBAAC,QAAD;WAAM,WAAU;qBACb,MAAuB,EAAI,KAAK,cAAc;WAC1C,CAAA,CACA;YAhBF,EAAI,GAgBF,CACT;SACD,CAAC,KAAoB,EAAU,WAAW,KACzC,kBAAC,KAAD;UAAG,WAAU;oBAA0B;UAAqB,CAAA;SAE1D;;OACD;;KAGP,kBAAC,GAAD;MAAM,SAAQ;gBAAd;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAM,WAAU,mCAAoC,CAAA,EACpD,kBAAC,MAAD;SAAI,WAAU;mBAAgC;SAAiB,CAAA,CAC3D;;OACN,kBAAC,KAAD;QAAG,WAAU;kBAAmC;QAE5C,CAAA;OACJ,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,WAAU;kBAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAAA,aAEpB;;OACJ;;KAGP,kBAAC,GAAD;MAAM,SAAQ;gBAAd;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAgB,WAAU,mCAAoC,CAAA,EAC9D,kBAAC,MAAD;SAAI,WAAU;mBAAgC;SAAiB,CAAA,CAC3D;;OACN,kBAAC,KAAD;QAAG,WAAU;kBAAmC;QAE5C,CAAA;OACJ,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,UAAD;SACE,MAAK;SACL,SAtGgB,YAA2B;UAErD,IAAM,KADS,MAAM,GAAa,EACb,MAAM,qCAAqC;AAChE,UAAI,MACF,EAAiB,EAAM,EACvB,MAAM,GAAc;;SAkGZ,UAAU;SACV,WAAU;mBAJZ,CAME,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA,EAChC,GAAW,SAAS,qBAAqB,iBACnC;YACR,KACC,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAK,GAAe,QAAQ;SAC3C,WAAU;mBAHZ,CAMI,EADD,MAAW,UACT,IAEA,GAFD,EAAO,WAAU,YAAa,CAED,EAC7B,aAEK;WAEP;;OACL,KACC,kBAAC,KAAD;QAAG,WAAU;kBAA4H;QAErI,CAAA;OAED;;KACH;;GAGN,kBAAC,GAAD;IAAM,SAAQ;cAAd;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAwC;OAAoB,CAAA,EAC1E,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAK,GAAa,OAAO;OACxC,WAAU;iBAHZ,CAKuB,EAApB,MAAW,SAAU,IAAiC,GAAlC,EAAO,WAAU,YAAa,CAAgC,EAClF,MAAW,SAAS,WAAW,OACzB;SACL;;KACN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,QAAD,EAAA,UAAO,GAAmB,CAAA;MACtB,CAAA;KACN,kBAAC,KAAD;MAAG,WAAU;gBAAb;OAAgD;OAC1C,kBAAC,QAAD;QAAM,WAAU;kBAA4B;QAA0B,CAAA;;OACW;OACrF,kBAAC,QAAD;QAAM,WAAU;kBAA4B;QAAa,CAAA;;OACvD;;KACC;;GAGP,kBAAC,GAAD;IAAM,SAAQ;cAAd,CACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,GAAa,MAAM,CAAC,EAAE;KACrC,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAY,WAAU,8BAA+B,CAAA,EAAA,2BAE9C;QACR,KACC,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,KAAD,EAAA,UAAA;OACE,kBAAC,UAAD;QAAQ,WAAU;kBAAoB;QAAc,CAAA;;OAChD,kBAAC,MAAD,EAAA,UAAI,oBAAqB,CAAA;;OAAsB;OACnD,kBAAC,QAAD;QAAM,WAAU;kBAA4B;QAA8B,CAAA;;OAAM;OAChF,kBAAC,MAAD,EAAA,UAAI,mBAAoB,CAAA;;OAAoC;OAC5D,kBAAC,QAAD;QAAM,WAAU;kBAA4B;QAA8B,CAAA;;OAExE,EAAA,CAAA;MACJ,kBAAC,KAAD,EAAA,UAAA,CACE,kBAAC,UAAD;OAAQ,WAAU;iBAAoB;OAAe,CAAA,EAAA,2HAGnD,EAAA,CAAA;MACJ,kBAAC,KAAD,EAAA,UAAA,CACE,kBAAC,UAAD;OAAQ,WAAU;iBAAoB;OAAsB,CAAA,EAAA,mHAE1D,EAAA,CAAA;MACA;OAEH;;GACH"}
|
|
1
|
+
{"version":3,"file":"ScorecardEmptyState.js","names":[],"sources":["../../../src/components/scorecard/ScorecardEmptyState.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState } from 'react';\nimport {\n BarChart3,\n Plus,\n LayoutTemplate,\n TerminalSquare,\n Copy,\n Check,\n KeyRound,\n HelpCircle,\n} from 'lucide-react';\nimport { Card } from '@/components/shared';\nimport {\n useGetScorecardTemplatesQuery,\n useGetScorecardPushTokenInfoQuery,\n useRotateScorecardPushTokenMutation,\n type ScorecardTemplateFieldsFragment,\n} from '@/generated/wspace-operations';\n\ninterface ScorecardEmptyStateProps {\n ownerType: 'CATALOG' | 'COMPONENT' | 'VIBE';\n ownerId: string;\n workspaceId: string | null;\n applyingTemplateId: string | null;\n onApplyTemplate: (templateId: string) => void;\n onAddManual: () => void;\n}\n\n/**\n * Onboarding surface shown when a scorecard has no metrics. Explains what a\n * scorecard is and the three ways to populate it: apply a template, add a\n * metric by hand, or push from CI (with a copyable command + token control).\n */\nexport const ScorecardEmptyState: FC<ScorecardEmptyStateProps> = ({\n ownerType,\n ownerId,\n workspaceId,\n applyingTemplateId,\n onApplyTemplate,\n onAddManual,\n}) => {\n const { data: templatesData, loading: templatesLoading } = useGetScorecardTemplatesQuery({\n fetchPolicy: 'cache-and-network',\n });\n const { data: tokenData, refetch: refetchToken } = useGetScorecardPushTokenInfoQuery({\n fetchPolicy: 'cache-and-network',\n });\n const [rotateToken, { loading: rotating }] = useRotateScorecardPushTokenMutation();\n\n const [revealedToken, setRevealedToken] = useState<string | null>(null);\n const [copied, setCopied] = useState<string | null>(null);\n const [showHelp, setShowHelp] = useState(false);\n\n const templates: ScorecardTemplateFieldsFragment[] =\n templatesData?.vibecontrolsScorecardTemplates ?? [];\n const tokenInfo = tokenData?.vibecontrolsScorecardPushTokenInfo;\n\n const tokenForSnippet = revealedToken ?? tokenInfo?.tokenPrefix ?? '<SCORECARD_PUSH_TOKEN>';\n\n const curlSnippet = `curl -X POST \"$WORKSPACES_API_BASE/api/scorecard/push\" \\\\\n -H \"Authorization: Bearer ${tokenForSnippet}\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\n \"workspaceId\": \"${workspaceId ?? '<WORKSPACE_ID>'}\",\n \"ownerType\": \"${ownerType}\",\n \"ownerId\": \"${ownerId}\",\n \"metrics\": [\n { \"name\": \"Test coverage\", \"type\": \"TEST_COVERAGE\", \"value\": 86, \"threshold\": 80, \"unit\": \"%\", \"direction\": \"HIGHER_IS_BETTER\" },\n { \"name\": \"Open incidents\", \"type\": \"INCIDENT_COUNT\", \"value\": 0, \"threshold\": 0, \"unit\": \"count\", \"direction\": \"LOWER_IS_BETTER\" }\n ]\n }'`;\n\n const copy = async (text: string, key: string): Promise<void> => {\n try {\n await navigator.clipboard.writeText(text);\n setCopied(key);\n window.setTimeout(() => setCopied((c) => (c === key ? null : c)), 1500);\n } catch {\n // Clipboard may be unavailable (insecure context) — ignore silently.\n }\n };\n\n const handleGenerateToken = async (): Promise<void> => {\n const result = await rotateToken();\n const token = result.data?.rotateVibecontrolsScorecardPushToken.token;\n if (token) {\n setRevealedToken(token);\n await refetchToken();\n }\n };\n\n return (\n <div className=\"space-y-6\">\n {/* Intro */}\n <Card padding=\"lg\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex size-10 flex-shrink-0 items-center justify-center rounded-full bg-bg-sunken\">\n <BarChart3 className=\"size-5 text-text-secondary\" />\n </div>\n <div>\n <h3 className=\"text-lg font-semibold text-text-primary\">\n Track this {ownerType.toLowerCase()}'s health\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n A scorecard rolls up the quality and reliability signals for this entity into a single\n 0–100 score. Each metric has a value, a target threshold, and a direction; its status\n (passing / warning / failing) is derived automatically from how the value compares to\n the threshold. Populate it one of three ways:\n </p>\n </div>\n </div>\n </Card>\n\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n {/* 1. Apply a template */}\n <Card padding=\"lg\">\n <div className=\"flex items-center gap-2\">\n <LayoutTemplate className=\"size-4 text-action-primary-text\" />\n <h4 className=\"font-medium text-text-primary\">Apply a template</h4>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Start from a standard set of checks defined for your workspace.\n </p>\n <div className=\"mt-3 space-y-2\">\n {templatesLoading && templates.length === 0 && (\n <p className=\"text-xs text-text-muted\">Loading templates…</p>\n )}\n {templates.map((tpl) => (\n <button\n key={tpl.id}\n type=\"button\"\n onClick={() => onApplyTemplate(tpl.id)}\n disabled={applyingTemplateId !== null}\n className=\"flex w-full items-center justify-between rounded-md border border-border-default px-3 py-2 text-left text-sm text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\"\n >\n <span className=\"min-w-0\">\n <span className=\"block truncate font-medium\">{tpl.name}</span>\n <span className=\"block truncate text-xs text-text-muted\">\n {tpl.metrics.length} metric{tpl.metrics.length === 1 ? '' : 's'}\n {tpl.isBuiltIn ? ' · starter' : ''}\n </span>\n </span>\n <span className=\"ml-2 flex-shrink-0 text-xs text-action-primary-text\">\n {applyingTemplateId === tpl.id ? 'Applying…' : 'Apply'}\n </span>\n </button>\n ))}\n {!templatesLoading && templates.length === 0 && (\n <p className=\"text-xs text-text-muted\">No templates yet.</p>\n )}\n </div>\n </Card>\n\n {/* 2. Add a metric manually */}\n <Card padding=\"lg\">\n <div className=\"flex items-center gap-2\">\n <Plus className=\"size-4 text-action-primary-text\" />\n <h4 className=\"font-medium text-text-primary\">Add a metric</h4>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Define a single metric by hand — name, value, threshold, and direction.\n </p>\n <button\n type=\"button\"\n onClick={onAddManual}\n className=\"mt-3 flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"size-4\" />\n Add metric\n </button>\n </Card>\n\n {/* 3. Push from CI */}\n <Card padding=\"lg\">\n <div className=\"flex items-center gap-2\">\n <TerminalSquare className=\"size-4 text-action-primary-text\" />\n <h4 className=\"font-medium text-text-primary\">Push from CI</h4>\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n Keep metrics fresh automatically from your pipeline via the push webhook.\n </p>\n <div className=\"mt-3 flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={handleGenerateToken}\n disabled={rotating}\n className=\"flex items-center gap-1.5 rounded-md border border-border-default px-3 py-1.5 text-xs text-text-primary hover:bg-bg-sunken disabled:opacity-60\"\n >\n <KeyRound className=\"size-3.5\" />\n {tokenInfo?.exists ? 'Regenerate token' : 'Generate token'}\n </button>\n {revealedToken && (\n <button\n type=\"button\"\n onClick={() => copy(revealedToken, 'token')}\n className=\"flex items-center gap-1 text-xs text-action-primary-text\"\n >\n {copied === 'token' ? (\n <Check className=\"size-3.5\" />\n ) : (\n <Copy className=\"size-3.5\" />\n )}\n Copy token\n </button>\n )}\n </div>\n {revealedToken && (\n <p className=\"mt-2 rounded-md border border-status-warning-border bg-status-warning-bg-subtle px-2 py-1.5 text-[11px] text-status-warning-text\">\n Copy this token now — it is shown only once. Store it in your CI secrets.\n </p>\n )}\n </Card>\n </div>\n\n {/* CI snippet */}\n <Card padding=\"lg\">\n <div className=\"flex items-center justify-between\">\n <h4 className=\"text-sm font-medium text-text-primary\">CI push example</h4>\n <button\n type=\"button\"\n onClick={() => copy(curlSnippet, 'curl')}\n className=\"flex items-center gap-1 text-xs text-action-primary-text\"\n >\n {copied === 'curl' ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n {copied === 'curl' ? 'Copied' : 'Copy'}\n </button>\n </div>\n <pre className=\"mt-2 overflow-x-auto rounded-md bg-bg-sunken p-3 text-[11px] leading-relaxed text-text-primary\">\n <code>{curlSnippet}</code>\n </pre>\n <p className=\"mt-2 text-[11px] text-text-muted\">\n Set <code className=\"rounded bg-bg-sunken px-1\">WORKSPACES_API_BASE</code> to your\n workspaces API base URL. Status is derived from value vs threshold; pass an explicit{' '}\n <code className=\"rounded bg-bg-sunken px-1\">status</code> only to override it.\n </p>\n </Card>\n\n {/* How scoring works */}\n <Card padding=\"lg\">\n <button\n type=\"button\"\n onClick={() => setShowHelp((s) => !s)}\n className=\"flex w-full items-center gap-2 text-left text-sm font-medium text-text-primary\"\n >\n <HelpCircle className=\"size-4 text-text-secondary\" />\n How metrics are measured\n </button>\n {showHelp && (\n <div className=\"mt-3 space-y-2 text-xs text-text-secondary\">\n <p>\n <strong className=\"text-text-primary\">Score</strong> — each metric is scored 0–100.\n For <em>higher-is-better</em> metrics the score is{' '}\n <code className=\"rounded bg-bg-sunken px-1\">value / threshold × 100</code>; for{' '}\n <em>lower-is-better</em> metrics (incidents, latency) it is{' '}\n <code className=\"rounded bg-bg-sunken px-1\">threshold / value × 100</code>. Both are\n capped at 100.\n </p>\n <p>\n <strong className=\"text-text-primary\">Status</strong> — meeting the threshold passes;\n within 10% of it warns; below that fails. You can pin a status manually if you need\n to.\n </p>\n <p>\n <strong className=\"text-text-primary\">Overall score</strong> — the weighted average of\n all metric scores. Give critical metrics a higher weight to make them count for more.\n </p>\n </div>\n )}\n </Card>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -196,7 +196,7 @@ var b = {
|
|
|
196
196
|
className: "font-medium",
|
|
197
197
|
children: k.name
|
|
198
198
|
}), j && /* @__PURE__ */ v("p", {
|
|
199
|
-
className: "mt-3 rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text",
|
|
199
|
+
className: "mt-3 rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
200
200
|
children: j
|
|
201
201
|
})]
|
|
202
202
|
}),
|
|
@@ -259,7 +259,7 @@ var b = {
|
|
|
259
259
|
className: "space-y-4 overflow-y-auto px-5 py-4",
|
|
260
260
|
children: [
|
|
261
261
|
F && /* @__PURE__ */ v("div", {
|
|
262
|
-
className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text",
|
|
262
|
+
className: "rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
263
263
|
children: F
|
|
264
264
|
}),
|
|
265
265
|
/* @__PURE__ */ y("label", {
|
|
@@ -629,7 +629,7 @@ var b = {
|
|
|
629
629
|
})]
|
|
630
630
|
}),
|
|
631
631
|
s && /* @__PURE__ */ v("p", {
|
|
632
|
-
className: "mt-2 rounded-md border border-status-warning-border bg-status-warning-bg px-2 py-1.5 text-xs text-status-warning-text",
|
|
632
|
+
className: "mt-2 rounded-md border border-status-warning-border bg-status-warning-bg-subtle px-2 py-1.5 text-xs text-status-warning-text",
|
|
633
633
|
children: e.remediation
|
|
634
634
|
})
|
|
635
635
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScorecardPanel.js","names":[],"sources":["../../../src/components/scorecard/ScorecardPanel.tsx"],"sourcesContent":["import type { FC, FormEvent, MouseEvent } from 'react';\nimport { useState, useCallback, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Plus, TrendingUp, RefreshCw, Pencil, Trash2, Info, ShieldCheck } from 'lucide-react';\nimport { LoadingState, ErrorState, Card } from '@/components/shared';\nimport { EntityShareButton } from '@/components/sharing';\nimport { ScorecardTier } from '@/generated/wspace-types';\nimport { MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useActiveContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n useGetScorecardQuery,\n useUpsertScorecardMutation,\n useAddScorecardMetricMutation,\n useUpdateScorecardMetricMutation,\n useRemoveScorecardMetricMutation,\n useApplyScorecardTemplateMutation,\n} from '@/generated/wspace-operations';\nimport {\n type ScorecardMetricType,\n type ScorecardMetricStatus,\n type ScorecardMetricDirection,\n METRIC_TYPE_OPTIONS,\n DIRECTION_OPTIONS,\n STATUS_OPTIONS,\n statusConfig,\n deriveStatus,\n} from './scorecardConstants';\nimport { ScorecardEmptyState } from './ScorecardEmptyState';\nimport { ScorecardsOverviewPanel } from './ScorecardsOverviewPanel';\nimport { MetricTrend } from './MetricTrend';\n\ninterface ScorecardMetric {\n id: string;\n name: string;\n description: string | null;\n type: ScorecardMetricType;\n value: number | null;\n threshold: number | null;\n unit: string | null;\n direction: ScorecardMetricDirection;\n weight: number;\n status: ScorecardMetricStatus;\n statusOverride: ScorecardMetricStatus | null;\n remediation: string | null;\n iframeUrl: string | null;\n iframeHeight: number | null;\n}\n\nexport interface ScorecardPanelProps {\n ownerType: 'CATALOG' | 'COMPONENT' | 'VIBE';\n ownerId: string;\n onAddMetric?: () => void;\n}\n\ntype StatusMode = 'AUTO' | ScorecardMetricStatus;\n\ninterface MetricFormState {\n name: string;\n description: string;\n type: ScorecardMetricType;\n value: string;\n threshold: string;\n unit: string;\n direction: ScorecardMetricDirection;\n weight: string;\n statusMode: StatusMode;\n remediation: string;\n iframeUrl: string;\n iframeHeight: string;\n}\n\nconst DEFAULT_METRIC_FORM: MetricFormState = {\n name: '',\n description: '',\n type: 'CUSTOM',\n value: '',\n threshold: '',\n unit: '',\n direction: 'HIGHER_IS_BETTER',\n weight: '1',\n statusMode: 'AUTO',\n remediation: '',\n iframeUrl: '',\n iframeHeight: '',\n};\n\nconst inputClass =\n 'w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-bg';\n\nexport const ScorecardPanel: FC<ScorecardPanelProps> = ({ ownerType, ownerId, onAddMetric }) => {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n },\n [t]\n );\n\n const { workspaceId } = useActiveContext();\n\n const [expandedMetric, setExpandedMetric] = useState<string | null>(null);\n const [isAddMetricOpen, setIsAddMetricOpen] = useState(false);\n const [editingMetricId, setEditingMetricId] = useState<string | null>(null);\n const [deletingMetric, setDeletingMetric] = useState<{ id: string; name: string } | null>(null);\n const [deleteError, setDeleteError] = useState<string | null>(null);\n const [metricForm, setMetricForm] = useState<MetricFormState>(DEFAULT_METRIC_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n const [applyingTemplateId, setApplyingTemplateId] = useState<string | null>(null);\n\n const { data, loading, error, refetch } = useGetScorecardQuery({\n variables: { ownerType, ownerId },\n skip: !ownerId,\n fetchPolicy: 'cache-and-network',\n });\n\n const [upsertScorecard, { loading: upsertingScorecard }] = useUpsertScorecardMutation();\n const [addScorecardMetric, { loading: addingMetric }] = useAddScorecardMetricMutation();\n const [updateScorecardMetric, { loading: updatingMetric }] = useUpdateScorecardMetricMutation();\n const [removeScorecardMetric, { loading: removingMetric }] = useRemoveScorecardMetricMutation();\n const [applyTemplate] = useApplyScorecardTemplateMutation();\n\n const scorecard = data?.vibecontrolsScorecard ?? null;\n const isSavingMetric = upsertingScorecard || addingMetric || updatingMetric;\n const isEditing = editingMetricId !== null;\n\n const resetMetricForm = (): void => {\n setMetricForm(DEFAULT_METRIC_FORM);\n setFormError(null);\n setEditingMetricId(null);\n };\n\n const handleAddMetricClick = (): void => {\n if (onAddMetric) {\n onAddMetric();\n return;\n }\n resetMetricForm();\n setIsAddMetricOpen(true);\n };\n\n const handleEditMetricClick = (event: MouseEvent, metric: ScorecardMetric): void => {\n event.stopPropagation();\n setMetricForm({\n name: metric.name,\n description: metric.description ?? '',\n type: metric.type,\n value: metric.value !== null && metric.value !== undefined ? String(metric.value) : '',\n threshold:\n metric.threshold !== null && metric.threshold !== undefined ? String(metric.threshold) : '',\n unit: metric.unit ?? '',\n direction: metric.direction ?? 'HIGHER_IS_BETTER',\n weight: metric.weight !== null && metric.weight !== undefined ? String(metric.weight) : '1',\n statusMode: metric.statusOverride ?? 'AUTO',\n remediation: metric.remediation ?? '',\n iframeUrl: metric.iframeUrl ?? '',\n iframeHeight:\n metric.iframeHeight !== null && metric.iframeHeight !== undefined\n ? String(metric.iframeHeight)\n : '',\n });\n setFormError(null);\n setEditingMetricId(metric.id);\n setIsAddMetricOpen(true);\n };\n\n const handleRemoveMetricClick = (event: MouseEvent, metric: ScorecardMetric): void => {\n event.stopPropagation();\n setDeleteError(null);\n setDeletingMetric({ id: metric.id, name: metric.name });\n };\n\n const closeAddMetricDialog = (): void => {\n if (isSavingMetric) return;\n setIsAddMetricOpen(false);\n resetMetricForm();\n };\n\n const closeDeleteDialog = useCallback((): void => {\n if (removingMetric) return;\n setDeletingMetric(null);\n setDeleteError(null);\n }, [removingMetric]);\n\n useEffect(() => {\n if (!deletingMetric) return undefined;\n const handler = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') closeDeleteDialog();\n };\n window.addEventListener('keydown', handler);\n return () => window.removeEventListener('keydown', handler);\n }, [deletingMetric, closeDeleteDialog]);\n\n const confirmRemoveMetric = async (): Promise<void> => {\n if (!deletingMetric) return;\n setDeleteError(null);\n try {\n await removeScorecardMetric({ variables: { id: deletingMetric.id } });\n await refetch();\n setDeletingMetric(null);\n } catch (caughtError) {\n setDeleteError(\n caughtError instanceof Error ? caughtError.message : 'Failed to remove metric'\n );\n }\n };\n\n const updateMetricForm = (field: keyof MetricFormState, value: string): void => {\n setMetricForm((current) => ({ ...current, [field]: value }));\n };\n\n const parseOptionalNumber = (value: string, label: string): number | undefined => {\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n const parsed = Number(trimmed);\n if (!Number.isFinite(parsed)) throw new Error(`${label} must be a valid number`);\n return parsed;\n };\n\n const ensureScorecardId = async (): Promise<string> => {\n if (scorecard?.id) return scorecard.id;\n const created = await upsertScorecard({ variables: { input: { ownerType, ownerId } } });\n const id = created.data?.upsertVibecontrolsScorecard.id;\n if (!id) throw new Error(tr('scorecard.scorecardCreateFailed', 'Unable to create scorecard'));\n return id;\n };\n\n const handleApplyTemplate = async (templateId: string): Promise<void> => {\n setApplyingTemplateId(templateId);\n try {\n await applyTemplate({ variables: { templateId, ownerType, ownerId } });\n await refetch();\n } finally {\n setApplyingTemplateId(null);\n }\n };\n\n const handleAddMetricSubmit = async (event: FormEvent<HTMLFormElement>): Promise<void> => {\n event.preventDefault();\n setFormError(null);\n\n const name = metricForm.name.trim();\n if (!name) {\n setFormError(tr('scorecard.metricNameRequired', 'Metric name is required'));\n return;\n }\n\n // 'AUTO' → derive status (send no explicit status). Otherwise pin it.\n const statusOverride =\n metricForm.statusMode === 'AUTO'\n ? undefined\n : (metricForm.statusMode as ScorecardMetricStatus);\n\n try {\n const sharedInput = {\n name,\n description: metricForm.description.trim() || null,\n type: metricForm.type,\n value: parseOptionalNumber(metricForm.value, tr('scorecard.value', 'Value')) ?? null,\n threshold:\n parseOptionalNumber(metricForm.threshold, tr('scorecard.threshold', 'Threshold')) ?? null,\n unit: metricForm.unit.trim() || null,\n direction: metricForm.direction,\n weight: parseOptionalNumber(metricForm.weight, tr('scorecard.weight', 'Weight')) ?? 1,\n status: statusOverride ?? null,\n remediation: metricForm.remediation.trim() || null,\n iframeUrl: metricForm.iframeUrl.trim() || null,\n iframeHeight:\n parseOptionalNumber(\n metricForm.iframeHeight,\n tr('scorecard.iframeHeight', 'Iframe height')\n ) ?? null,\n };\n\n if (editingMetricId) {\n await updateScorecardMetric({ variables: { id: editingMetricId, input: sharedInput } });\n } else {\n const scorecardId = await ensureScorecardId();\n await addScorecardMetric({ variables: { scorecardId, input: sharedInput } });\n }\n\n await refetch();\n setIsAddMetricOpen(false);\n resetMetricForm();\n } catch (caughtError) {\n setFormError(\n caughtError instanceof Error\n ? caughtError.message\n : editingMetricId\n ? 'Failed to update metric'\n : 'Failed to add metric'\n );\n }\n };\n\n // Live status preview for the form.\n const previewStatus =\n metricForm.statusMode === 'AUTO'\n ? deriveStatus(\n metricForm.value.trim() ? Number(metricForm.value) : null,\n metricForm.threshold.trim() ? Number(metricForm.threshold) : null,\n metricForm.direction\n )\n : (metricForm.statusMode as ScorecardMetricStatus);\n\n const renderDeleteMetricDialog = () => (\n <AnimatePresence>\n {deletingMetric && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\"\n onClick={closeDeleteDialog}\n >\n <motion.div\n initial={{ scale: 0.96, y: 12 }}\n animate={{ scale: 1, y: 0 }}\n exit={{ scale: 0.96, y: 12 }}\n className=\"w-full max-w-md rounded-xl border border-border-default bg-bg-surface shadow-xl\"\n onClick={(event) => event.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"scorecard-delete-title\"\n >\n <div className=\"border-b border-border-default px-5 py-4\">\n <h3 id=\"scorecard-delete-title\" className=\"text-lg font-semibold text-text-primary\">\n {tr('scorecard.removeMetricTitle', 'Remove metric')}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n 'scorecard.removeMetricConfirm',\n 'Are you sure you want to remove this metric? This cannot be undone.'\n )}\n </p>\n </div>\n <div className=\"px-5 py-4 text-sm text-text-primary\">\n <p className=\"font-medium\">{deletingMetric.name}</p>\n {deleteError && (\n <p className=\"mt-3 rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {deleteError}\n </p>\n )}\n </div>\n <div className=\"flex items-center justify-end gap-3 border-t border-border-default px-5 py-4\">\n <button\n type=\"button\"\n onClick={closeDeleteDialog}\n disabled={removingMetric}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={confirmRemoveMetric}\n disabled={removingMetric}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm text-status-error-text hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {removingMetric\n ? tr('scorecard.removingMetric', 'Removing...')\n : tr('scorecard.removeMetric', 'Remove metric')}\n </button>\n </div>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n\n const renderAddMetricDialog = () => {\n const previewConf = statusConfig[previewStatus] ?? statusConfig.UNKNOWN;\n return (\n <AnimatePresence>\n {isAddMetricOpen && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\"\n onClick={closeAddMetricDialog}\n >\n <motion.div\n initial={{ scale: 0.96, y: 12 }}\n animate={{ scale: 1, y: 0 }}\n exit={{ scale: 0.96, y: 12 }}\n className=\"w-full max-w-2xl rounded-xl border border-border-default bg-bg-surface shadow-xl\"\n onClick={(event) => event.stopPropagation()}\n >\n <form onSubmit={handleAddMetricSubmit} className=\"flex max-h-[85vh] flex-col\">\n <div className=\"border-b border-border-default px-5 py-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {isEditing\n ? tr('scorecard.editMetricTitle', 'Edit scorecard metric')\n : tr('scorecard.addMetricTitle', 'Add scorecard metric')}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n 'scorecard.addMetricDescription',\n 'Define a metric. Status is derived from value vs threshold unless you pin it.'\n )}\n </p>\n </div>\n\n <div className=\"space-y-4 overflow-y-auto px-5 py-4\">\n {formError && (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricName', 'Metric name')}\n </span>\n <input\n aria-label=\"control\"\n value={metricForm.name}\n onChange={(event) => updateMetricForm('name', event.target.value)}\n className={inputClass}\n placeholder={tr('scorecard.metricNamePlaceholder', 'Test coverage')}\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricDescription', 'Description')}\n </span>\n <textarea\n aria-label=\"control\"\n value={metricForm.description}\n onChange={(event) => updateMetricForm('description', event.target.value)}\n className={`min-h-16 ${inputClass}`}\n placeholder={tr(\n 'scorecard.metricDescriptionPlaceholder',\n 'Explain what this metric measures'\n )}\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricType', 'Type')}\n </span>\n <select\n value={metricForm.type}\n onChange={(event) =>\n updateMetricForm('type', event.target.value as ScorecardMetricType)\n }\n className={inputClass}\n >\n {METRIC_TYPE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricDirection', 'Direction')}\n </span>\n <select\n value={metricForm.direction}\n onChange={(event) =>\n updateMetricForm(\n 'direction',\n event.target.value as ScorecardMetricDirection\n )\n }\n className={inputClass}\n >\n {DIRECTION_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-4\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.value', 'Value')}\n </span>\n <input\n aria-label=\"value\"\n value={metricForm.value}\n onChange={(event) => updateMetricForm('value', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"86\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.threshold', 'Threshold')}\n </span>\n <input\n aria-label=\"threshold\"\n value={metricForm.threshold}\n onChange={(event) => updateMetricForm('threshold', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"80\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.unit', 'Unit')}\n </span>\n <input\n aria-label=\"unit\"\n value={metricForm.unit}\n onChange={(event) => updateMetricForm('unit', event.target.value)}\n className={inputClass}\n placeholder=\"%\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.weight', 'Weight')}\n </span>\n <input\n aria-label=\"weight\"\n value={metricForm.weight}\n onChange={(event) => updateMetricForm('weight', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"1\"\n />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricStatus', 'Status')}\n </span>\n <select\n value={metricForm.statusMode}\n onChange={(event) =>\n updateMetricForm('statusMode', event.target.value as StatusMode)\n }\n className={inputClass}\n >\n {STATUS_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n\n <div className=\"flex items-end\">\n <div\n className={`flex items-center gap-2 rounded-md px-3 py-2 text-sm ${previewConf.bg} ${previewConf.text}`}\n >\n <previewConf.icon className=\"size-4\" />\n <span>\n {metricForm.statusMode === 'AUTO' ? 'Derived: ' : 'Pinned: '}\n {previewConf.label}\n </span>\n </div>\n </div>\n </div>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.remediation', 'Remediation guidance')}\n </span>\n <textarea\n aria-label=\"control\"\n value={metricForm.remediation}\n onChange={(event) => updateMetricForm('remediation', event.target.value)}\n className={`min-h-16 ${inputClass}`}\n placeholder={tr(\n 'scorecard.remediationPlaceholder',\n 'What should someone do when this metric is failing?'\n )}\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-[1fr_10rem]\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.iframeUrl', 'Embed URL')}\n </span>\n <input\n aria-label=\"control\"\n value={metricForm.iframeUrl}\n onChange={(event) => updateMetricForm('iframeUrl', event.target.value)}\n className={inputClass}\n placeholder={tr('scorecard.iframeUrlPlaceholder', 'Optional dashboard URL')}\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.iframeHeight', 'Iframe height')}\n </span>\n <input\n aria-label=\"iframe height\"\n value={metricForm.iframeHeight}\n onChange={(event) => updateMetricForm('iframeHeight', event.target.value)}\n inputMode=\"numeric\"\n className={inputClass}\n placeholder=\"320\"\n />\n </label>\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-3 border-t border-border-default px-5 py-4\">\n <button\n type=\"button\"\n onClick={closeAddMetricDialog}\n disabled={isSavingMetric}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isSavingMetric}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text hover:bg-action-primary-bg-hover disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {isSavingMetric\n ? tr('scorecard.savingMetric', 'Saving...')\n : isEditing\n ? tr('scorecard.updateMetric', 'Update Metric')\n : tr('scorecard.saveMetric', 'Save Metric')}\n </button>\n </div>\n </form>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n };\n\n if (loading && !scorecard) {\n return <LoadingState message={tr('scorecard.loading', 'Loading scorecard...')} />;\n }\n\n if (error && !scorecard) {\n return (\n <ErrorState\n title={tr('scorecard.failedLoad', 'Failed to load scorecard')}\n message={error.message}\n onRetry={() => refetch()}\n />\n );\n }\n\n if (!scorecard || scorecard.metrics.length === 0) {\n return (\n <>\n <ScorecardEmptyState\n ownerType={ownerType}\n ownerId={ownerId}\n workspaceId={workspaceId}\n applyingTemplateId={applyingTemplateId}\n onApplyTemplate={handleApplyTemplate}\n onAddManual={handleAddMetricClick}\n />\n {renderAddMetricDialog()}\n {renderDeleteMetricDialog()}\n </>\n );\n }\n\n const overallScore = scorecard.overallScore ?? 0;\n const scoreColor =\n overallScore >= 80\n ? 'text-status-success-text'\n : overallScore >= 50\n ? 'text-status-warning-text'\n : 'text-status-error-text';\n\n return (\n <div className=\"space-y-6\">\n {/* Overall Score */}\n <Card padding=\"lg\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"flex items-center gap-1.5 text-sm font-medium text-text-secondary\">\n {tr('scorecard.overallScore', 'Overall Score')}\n <span title=\"Weighted average of each metric's score (0–100). Status is derived from value vs threshold.\">\n <Info className=\"size-3.5 text-text-muted\" />\n </span>\n </p>\n <p className={`text-4xl font-bold ${scoreColor}`}>\n {overallScore}\n <span className=\"text-lg text-text-muted\">/100</span>\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {scorecard.securityTier && <TierBadge tier={scorecard.securityTier} />}\n <TrendingUp className={`size-6 ${scoreColor}`} />\n <EntityShareButton\n entity=\"scorecard\"\n resource={{\n id: scorecard.id,\n // Scorecard has no display-name field; use its id as the title.\n name: scorecard.id,\n owner: { ownerId, ownerType },\n }}\n label={tr('scorecard.share', 'Share')}\n />\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"rounded-md p-2 text-text-secondary transition-colors hover:bg-bg-sunken\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={handleAddMetricClick}\n className=\"flex items-center gap-2 rounded-md bg-action-primary-bg px-3 py-2 text-sm text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"size-4\" />\n {tr('scorecard.addMetric', 'Add Metric')}\n </button>\n </div>\n </div>\n </Card>\n\n {/* Metrics Grid */}\n <MotionList stagger={0.04} className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {scorecard.metrics.map((metric: ScorecardMetric) => {\n const config = statusConfig[metric.status] || statusConfig.UNKNOWN;\n const StatusIcon = config.icon;\n const value = metric.value ?? 0;\n const threshold = metric.threshold ?? 100;\n const percentage =\n threshold > 0 ? Math.min(100, Math.max(0, (value / threshold) * 100)) : 0;\n const isExpanded = expandedMetric === metric.id;\n const showRemediation =\n metric.remediation && (metric.status === 'WARNING' || metric.status === 'FAILING');\n\n return (\n <MotionItem key={metric.id}>\n <Card hoverable padding=\"none\">\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => setExpandedMetric(isExpanded ? null : metric.id)}\n onKeyDown={(event) => {\n if (event.target !== event.currentTarget) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n setExpandedMetric(isExpanded ? null : metric.id);\n }\n }}\n className=\"cursor-pointer rounded-lg p-4 focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n aria-expanded={isExpanded}\n aria-label={`${metric.name} metric details`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {metric.name}\n </p>\n <p className=\"mt-0.5 flex items-center gap-1.5 text-xs text-text-muted\">\n <span>\n {metric.direction === 'LOWER_IS_BETTER'\n ? '↓ lower better'\n : '↑ higher better'}\n </span>\n {metric.weight && metric.weight !== 1 ? (\n <span>· ×{metric.weight}</span>\n ) : null}\n {metric.statusOverride ? <span>· pinned</span> : null}\n </p>\n </div>\n <div className=\"flex flex-shrink-0 items-center gap-1\">\n <button\n type=\"button\"\n onClick={(event) => handleEditMetricClick(event, metric)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n aria-label={tr('scorecard.editMetric', 'Edit metric')}\n title={tr('scorecard.editMetric', 'Edit metric')}\n >\n <Pencil className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={(event) => handleRemoveMetricClick(event, metric)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text focus:outline-none focus:ring-2 focus:ring-status-error-text\"\n aria-label={tr('scorecard.removeMetric', 'Remove metric')}\n title={tr('scorecard.removeMetric', 'Remove metric')}\n >\n <Trash2 className=\"size-4\" />\n </button>\n <div\n className={`flex size-7 items-center justify-center rounded-full ${config.bg}`}\n >\n <StatusIcon className={`size-4 ${config.text}`} />\n </div>\n </div>\n </div>\n\n {/* Value and progress */}\n <div className=\"mt-3\">\n <div className=\"flex items-baseline justify-between\">\n <span className=\"text-lg font-bold text-text-primary\">\n {value}\n {metric.unit ? ` ${metric.unit}` : ''}\n </span>\n <span className=\"text-xs text-text-muted\">\n / {threshold}\n {metric.unit ? ` ${metric.unit}` : ''}\n </span>\n </div>\n <div className=\"mt-1.5 h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <motion.div\n initial={{ width: 0 }}\n animate={{ width: `${percentage}%` }}\n transition={{ duration: 0.5, ease: 'easeOut' }}\n className={`h-full rounded-full ${config.bar}`}\n />\n </div>\n </div>\n\n {showRemediation && (\n <p className=\"mt-2 rounded-md border border-status-warning-border bg-status-warning-bg px-2 py-1.5 text-xs text-status-warning-text\">\n {metric.remediation}\n </p>\n )}\n </div>\n\n {/* Expanded: trend + optional embed */}\n <AnimatePresence>\n {isExpanded && (\n <motion.div\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: 'auto', opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={{ duration: 0.2 }}\n className=\"overflow-hidden border-t border-border-default\"\n >\n <div className=\"px-4 py-3\">\n <MetricTrend metricId={metric.id} colorClass={config.text} />\n </div>\n {metric.iframeUrl && (\n <iframe\n src={metric.iframeUrl}\n className=\"w-full border-0\"\n style={{\n height: metric.iframeHeight ? `${metric.iframeHeight}px` : '200px',\n }}\n title={`${metric.name} embed`}\n sandbox=\"allow-scripts allow-same-origin\"\n />\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </Card>\n </MotionItem>\n );\n })}\n </MotionList>\n\n <ScorecardsOverviewPanel ownerType={ownerType} highlightOwnerId={ownerId} />\n\n {renderAddMetricDialog()}\n {renderDeleteMetricDialog()}\n </div>\n );\n};\n\nconst TIER_STYLE: Record<ScorecardTier, { label: string; cls: string }> = {\n [ScorecardTier.Bronze]: {\n label: 'Bronze',\n cls: 'bg-status-warning-bg-subtle text-status-warning-text border border-status-warning-border',\n },\n [ScorecardTier.Silver]: {\n label: 'Silver',\n cls: 'bg-bg-sunken text-text-secondary border border-border-strong',\n },\n [ScorecardTier.Gold]: {\n label: 'Gold',\n cls: 'bg-status-success-bg-subtle text-status-success-text border border-status-success-border',\n },\n};\n\n/**\n * TierBadge — shows the scorecard's derived production-readiness tier (Bronze/Silver/Gold).\n * The tier is computed server-side from the tier-tagged metrics; this is read-only.\n */\nconst TierBadge: FC<{ tier: ScorecardTier }> = ({ tier }) => {\n const style = TIER_STYLE[tier];\n return (\n <span\n className={`inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-xs font-semibold ${style.cls}`}\n title={`Security readiness tier: ${style.label}`}\n >\n <ShieldCheck className=\"size-3.5\" />\n {style.label}\n </span>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ScorecardPanel.js","names":[],"sources":["../../../src/components/scorecard/ScorecardPanel.tsx"],"sourcesContent":["import type { FC, FormEvent, MouseEvent } from 'react';\nimport { useState, useCallback, useEffect } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport { Plus, TrendingUp, RefreshCw, Pencil, Trash2, Info, ShieldCheck } from 'lucide-react';\nimport { LoadingState, ErrorState, Card } from '@/components/shared';\nimport { EntityShareButton } from '@/components/sharing';\nimport { ScorecardTier } from '@/generated/wspace-types';\nimport { MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useActiveContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n useGetScorecardQuery,\n useUpsertScorecardMutation,\n useAddScorecardMetricMutation,\n useUpdateScorecardMetricMutation,\n useRemoveScorecardMetricMutation,\n useApplyScorecardTemplateMutation,\n} from '@/generated/wspace-operations';\nimport {\n type ScorecardMetricType,\n type ScorecardMetricStatus,\n type ScorecardMetricDirection,\n METRIC_TYPE_OPTIONS,\n DIRECTION_OPTIONS,\n STATUS_OPTIONS,\n statusConfig,\n deriveStatus,\n} from './scorecardConstants';\nimport { ScorecardEmptyState } from './ScorecardEmptyState';\nimport { ScorecardsOverviewPanel } from './ScorecardsOverviewPanel';\nimport { MetricTrend } from './MetricTrend';\n\ninterface ScorecardMetric {\n id: string;\n name: string;\n description: string | null;\n type: ScorecardMetricType;\n value: number | null;\n threshold: number | null;\n unit: string | null;\n direction: ScorecardMetricDirection;\n weight: number;\n status: ScorecardMetricStatus;\n statusOverride: ScorecardMetricStatus | null;\n remediation: string | null;\n iframeUrl: string | null;\n iframeHeight: number | null;\n}\n\nexport interface ScorecardPanelProps {\n ownerType: 'CATALOG' | 'COMPONENT' | 'VIBE';\n ownerId: string;\n onAddMetric?: () => void;\n}\n\ntype StatusMode = 'AUTO' | ScorecardMetricStatus;\n\ninterface MetricFormState {\n name: string;\n description: string;\n type: ScorecardMetricType;\n value: string;\n threshold: string;\n unit: string;\n direction: ScorecardMetricDirection;\n weight: string;\n statusMode: StatusMode;\n remediation: string;\n iframeUrl: string;\n iframeHeight: string;\n}\n\nconst DEFAULT_METRIC_FORM: MetricFormState = {\n name: '',\n description: '',\n type: 'CUSTOM',\n value: '',\n threshold: '',\n unit: '',\n direction: 'HIGHER_IS_BETTER',\n weight: '1',\n statusMode: 'AUTO',\n remediation: '',\n iframeUrl: '',\n iframeHeight: '',\n};\n\nconst inputClass =\n 'w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary outline-none focus:border-action-primary-bg';\n\nexport const ScorecardPanel: FC<ScorecardPanelProps> = ({ ownerType, ownerId, onAddMetric }) => {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n },\n [t]\n );\n\n const { workspaceId } = useActiveContext();\n\n const [expandedMetric, setExpandedMetric] = useState<string | null>(null);\n const [isAddMetricOpen, setIsAddMetricOpen] = useState(false);\n const [editingMetricId, setEditingMetricId] = useState<string | null>(null);\n const [deletingMetric, setDeletingMetric] = useState<{ id: string; name: string } | null>(null);\n const [deleteError, setDeleteError] = useState<string | null>(null);\n const [metricForm, setMetricForm] = useState<MetricFormState>(DEFAULT_METRIC_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n const [applyingTemplateId, setApplyingTemplateId] = useState<string | null>(null);\n\n const { data, loading, error, refetch } = useGetScorecardQuery({\n variables: { ownerType, ownerId },\n skip: !ownerId,\n fetchPolicy: 'cache-and-network',\n });\n\n const [upsertScorecard, { loading: upsertingScorecard }] = useUpsertScorecardMutation();\n const [addScorecardMetric, { loading: addingMetric }] = useAddScorecardMetricMutation();\n const [updateScorecardMetric, { loading: updatingMetric }] = useUpdateScorecardMetricMutation();\n const [removeScorecardMetric, { loading: removingMetric }] = useRemoveScorecardMetricMutation();\n const [applyTemplate] = useApplyScorecardTemplateMutation();\n\n const scorecard = data?.vibecontrolsScorecard ?? null;\n const isSavingMetric = upsertingScorecard || addingMetric || updatingMetric;\n const isEditing = editingMetricId !== null;\n\n const resetMetricForm = (): void => {\n setMetricForm(DEFAULT_METRIC_FORM);\n setFormError(null);\n setEditingMetricId(null);\n };\n\n const handleAddMetricClick = (): void => {\n if (onAddMetric) {\n onAddMetric();\n return;\n }\n resetMetricForm();\n setIsAddMetricOpen(true);\n };\n\n const handleEditMetricClick = (event: MouseEvent, metric: ScorecardMetric): void => {\n event.stopPropagation();\n setMetricForm({\n name: metric.name,\n description: metric.description ?? '',\n type: metric.type,\n value: metric.value !== null && metric.value !== undefined ? String(metric.value) : '',\n threshold:\n metric.threshold !== null && metric.threshold !== undefined ? String(metric.threshold) : '',\n unit: metric.unit ?? '',\n direction: metric.direction ?? 'HIGHER_IS_BETTER',\n weight: metric.weight !== null && metric.weight !== undefined ? String(metric.weight) : '1',\n statusMode: metric.statusOverride ?? 'AUTO',\n remediation: metric.remediation ?? '',\n iframeUrl: metric.iframeUrl ?? '',\n iframeHeight:\n metric.iframeHeight !== null && metric.iframeHeight !== undefined\n ? String(metric.iframeHeight)\n : '',\n });\n setFormError(null);\n setEditingMetricId(metric.id);\n setIsAddMetricOpen(true);\n };\n\n const handleRemoveMetricClick = (event: MouseEvent, metric: ScorecardMetric): void => {\n event.stopPropagation();\n setDeleteError(null);\n setDeletingMetric({ id: metric.id, name: metric.name });\n };\n\n const closeAddMetricDialog = (): void => {\n if (isSavingMetric) return;\n setIsAddMetricOpen(false);\n resetMetricForm();\n };\n\n const closeDeleteDialog = useCallback((): void => {\n if (removingMetric) return;\n setDeletingMetric(null);\n setDeleteError(null);\n }, [removingMetric]);\n\n useEffect(() => {\n if (!deletingMetric) return undefined;\n const handler = (event: KeyboardEvent): void => {\n if (event.key === 'Escape') closeDeleteDialog();\n };\n window.addEventListener('keydown', handler);\n return () => window.removeEventListener('keydown', handler);\n }, [deletingMetric, closeDeleteDialog]);\n\n const confirmRemoveMetric = async (): Promise<void> => {\n if (!deletingMetric) return;\n setDeleteError(null);\n try {\n await removeScorecardMetric({ variables: { id: deletingMetric.id } });\n await refetch();\n setDeletingMetric(null);\n } catch (caughtError) {\n setDeleteError(\n caughtError instanceof Error ? caughtError.message : 'Failed to remove metric'\n );\n }\n };\n\n const updateMetricForm = (field: keyof MetricFormState, value: string): void => {\n setMetricForm((current) => ({ ...current, [field]: value }));\n };\n\n const parseOptionalNumber = (value: string, label: string): number | undefined => {\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n const parsed = Number(trimmed);\n if (!Number.isFinite(parsed)) throw new Error(`${label} must be a valid number`);\n return parsed;\n };\n\n const ensureScorecardId = async (): Promise<string> => {\n if (scorecard?.id) return scorecard.id;\n const created = await upsertScorecard({ variables: { input: { ownerType, ownerId } } });\n const id = created.data?.upsertVibecontrolsScorecard.id;\n if (!id) throw new Error(tr('scorecard.scorecardCreateFailed', 'Unable to create scorecard'));\n return id;\n };\n\n const handleApplyTemplate = async (templateId: string): Promise<void> => {\n setApplyingTemplateId(templateId);\n try {\n await applyTemplate({ variables: { templateId, ownerType, ownerId } });\n await refetch();\n } finally {\n setApplyingTemplateId(null);\n }\n };\n\n const handleAddMetricSubmit = async (event: FormEvent<HTMLFormElement>): Promise<void> => {\n event.preventDefault();\n setFormError(null);\n\n const name = metricForm.name.trim();\n if (!name) {\n setFormError(tr('scorecard.metricNameRequired', 'Metric name is required'));\n return;\n }\n\n // 'AUTO' → derive status (send no explicit status). Otherwise pin it.\n const statusOverride =\n metricForm.statusMode === 'AUTO'\n ? undefined\n : (metricForm.statusMode as ScorecardMetricStatus);\n\n try {\n const sharedInput = {\n name,\n description: metricForm.description.trim() || null,\n type: metricForm.type,\n value: parseOptionalNumber(metricForm.value, tr('scorecard.value', 'Value')) ?? null,\n threshold:\n parseOptionalNumber(metricForm.threshold, tr('scorecard.threshold', 'Threshold')) ?? null,\n unit: metricForm.unit.trim() || null,\n direction: metricForm.direction,\n weight: parseOptionalNumber(metricForm.weight, tr('scorecard.weight', 'Weight')) ?? 1,\n status: statusOverride ?? null,\n remediation: metricForm.remediation.trim() || null,\n iframeUrl: metricForm.iframeUrl.trim() || null,\n iframeHeight:\n parseOptionalNumber(\n metricForm.iframeHeight,\n tr('scorecard.iframeHeight', 'Iframe height')\n ) ?? null,\n };\n\n if (editingMetricId) {\n await updateScorecardMetric({ variables: { id: editingMetricId, input: sharedInput } });\n } else {\n const scorecardId = await ensureScorecardId();\n await addScorecardMetric({ variables: { scorecardId, input: sharedInput } });\n }\n\n await refetch();\n setIsAddMetricOpen(false);\n resetMetricForm();\n } catch (caughtError) {\n setFormError(\n caughtError instanceof Error\n ? caughtError.message\n : editingMetricId\n ? 'Failed to update metric'\n : 'Failed to add metric'\n );\n }\n };\n\n // Live status preview for the form.\n const previewStatus =\n metricForm.statusMode === 'AUTO'\n ? deriveStatus(\n metricForm.value.trim() ? Number(metricForm.value) : null,\n metricForm.threshold.trim() ? Number(metricForm.threshold) : null,\n metricForm.direction\n )\n : (metricForm.statusMode as ScorecardMetricStatus);\n\n const renderDeleteMetricDialog = () => (\n <AnimatePresence>\n {deletingMetric && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\"\n onClick={closeDeleteDialog}\n >\n <motion.div\n initial={{ scale: 0.96, y: 12 }}\n animate={{ scale: 1, y: 0 }}\n exit={{ scale: 0.96, y: 12 }}\n className=\"w-full max-w-md rounded-xl border border-border-default bg-bg-surface shadow-xl\"\n onClick={(event) => event.stopPropagation()}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"scorecard-delete-title\"\n >\n <div className=\"border-b border-border-default px-5 py-4\">\n <h3 id=\"scorecard-delete-title\" className=\"text-lg font-semibold text-text-primary\">\n {tr('scorecard.removeMetricTitle', 'Remove metric')}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n 'scorecard.removeMetricConfirm',\n 'Are you sure you want to remove this metric? This cannot be undone.'\n )}\n </p>\n </div>\n <div className=\"px-5 py-4 text-sm text-text-primary\">\n <p className=\"font-medium\">{deletingMetric.name}</p>\n {deleteError && (\n <p className=\"mt-3 rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {deleteError}\n </p>\n )}\n </div>\n <div className=\"flex items-center justify-end gap-3 border-t border-border-default px-5 py-4\">\n <button\n type=\"button\"\n onClick={closeDeleteDialog}\n disabled={removingMetric}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={confirmRemoveMetric}\n disabled={removingMetric}\n className=\"rounded-md bg-status-error-bg px-4 py-2 text-sm text-status-error-text hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {removingMetric\n ? tr('scorecard.removingMetric', 'Removing...')\n : tr('scorecard.removeMetric', 'Remove metric')}\n </button>\n </div>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n\n const renderAddMetricDialog = () => {\n const previewConf = statusConfig[previewStatus] ?? statusConfig.UNKNOWN;\n return (\n <AnimatePresence>\n {isAddMetricOpen && (\n <motion.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\"\n onClick={closeAddMetricDialog}\n >\n <motion.div\n initial={{ scale: 0.96, y: 12 }}\n animate={{ scale: 1, y: 0 }}\n exit={{ scale: 0.96, y: 12 }}\n className=\"w-full max-w-2xl rounded-xl border border-border-default bg-bg-surface shadow-xl\"\n onClick={(event) => event.stopPropagation()}\n >\n <form onSubmit={handleAddMetricSubmit} className=\"flex max-h-[85vh] flex-col\">\n <div className=\"border-b border-border-default px-5 py-4\">\n <h3 className=\"text-lg font-semibold text-text-primary\">\n {isEditing\n ? tr('scorecard.editMetricTitle', 'Edit scorecard metric')\n : tr('scorecard.addMetricTitle', 'Add scorecard metric')}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n 'scorecard.addMetricDescription',\n 'Define a metric. Status is derived from value vs threshold unless you pin it.'\n )}\n </p>\n </div>\n\n <div className=\"space-y-4 overflow-y-auto px-5 py-4\">\n {formError && (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {formError}\n </div>\n )}\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricName', 'Metric name')}\n </span>\n <input\n aria-label=\"control\"\n value={metricForm.name}\n onChange={(event) => updateMetricForm('name', event.target.value)}\n className={inputClass}\n placeholder={tr('scorecard.metricNamePlaceholder', 'Test coverage')}\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricDescription', 'Description')}\n </span>\n <textarea\n aria-label=\"control\"\n value={metricForm.description}\n onChange={(event) => updateMetricForm('description', event.target.value)}\n className={`min-h-16 ${inputClass}`}\n placeholder={tr(\n 'scorecard.metricDescriptionPlaceholder',\n 'Explain what this metric measures'\n )}\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricType', 'Type')}\n </span>\n <select\n value={metricForm.type}\n onChange={(event) =>\n updateMetricForm('type', event.target.value as ScorecardMetricType)\n }\n className={inputClass}\n >\n {METRIC_TYPE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricDirection', 'Direction')}\n </span>\n <select\n value={metricForm.direction}\n onChange={(event) =>\n updateMetricForm(\n 'direction',\n event.target.value as ScorecardMetricDirection\n )\n }\n className={inputClass}\n >\n {DIRECTION_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-4\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.value', 'Value')}\n </span>\n <input\n aria-label=\"value\"\n value={metricForm.value}\n onChange={(event) => updateMetricForm('value', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"86\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.threshold', 'Threshold')}\n </span>\n <input\n aria-label=\"threshold\"\n value={metricForm.threshold}\n onChange={(event) => updateMetricForm('threshold', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"80\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.unit', 'Unit')}\n </span>\n <input\n aria-label=\"unit\"\n value={metricForm.unit}\n onChange={(event) => updateMetricForm('unit', event.target.value)}\n className={inputClass}\n placeholder=\"%\"\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.weight', 'Weight')}\n </span>\n <input\n aria-label=\"weight\"\n value={metricForm.weight}\n onChange={(event) => updateMetricForm('weight', event.target.value)}\n inputMode=\"decimal\"\n className={inputClass}\n placeholder=\"1\"\n />\n </label>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.metricStatus', 'Status')}\n </span>\n <select\n value={metricForm.statusMode}\n onChange={(event) =>\n updateMetricForm('statusMode', event.target.value as StatusMode)\n }\n className={inputClass}\n >\n {STATUS_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </label>\n\n <div className=\"flex items-end\">\n <div\n className={`flex items-center gap-2 rounded-md px-3 py-2 text-sm ${previewConf.bg} ${previewConf.text}`}\n >\n <previewConf.icon className=\"size-4\" />\n <span>\n {metricForm.statusMode === 'AUTO' ? 'Derived: ' : 'Pinned: '}\n {previewConf.label}\n </span>\n </div>\n </div>\n </div>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.remediation', 'Remediation guidance')}\n </span>\n <textarea\n aria-label=\"control\"\n value={metricForm.remediation}\n onChange={(event) => updateMetricForm('remediation', event.target.value)}\n className={`min-h-16 ${inputClass}`}\n placeholder={tr(\n 'scorecard.remediationPlaceholder',\n 'What should someone do when this metric is failing?'\n )}\n />\n </label>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-[1fr_10rem]\">\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.iframeUrl', 'Embed URL')}\n </span>\n <input\n aria-label=\"control\"\n value={metricForm.iframeUrl}\n onChange={(event) => updateMetricForm('iframeUrl', event.target.value)}\n className={inputClass}\n placeholder={tr('scorecard.iframeUrlPlaceholder', 'Optional dashboard URL')}\n />\n </label>\n\n <label className=\"block space-y-1.5\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('scorecard.iframeHeight', 'Iframe height')}\n </span>\n <input\n aria-label=\"iframe height\"\n value={metricForm.iframeHeight}\n onChange={(event) => updateMetricForm('iframeHeight', event.target.value)}\n inputMode=\"numeric\"\n className={inputClass}\n placeholder=\"320\"\n />\n </label>\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-3 border-t border-border-default px-5 py-4\">\n <button\n type=\"button\"\n onClick={closeAddMetricDialog}\n disabled={isSavingMetric}\n className=\"rounded-md border border-border-default px-4 py-2 text-sm text-text-primary hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={isSavingMetric}\n className=\"rounded-md bg-action-primary-bg px-4 py-2 text-sm text-action-primary-text hover:bg-action-primary-bg-hover disabled:cursor-not-allowed disabled:opacity-60\"\n >\n {isSavingMetric\n ? tr('scorecard.savingMetric', 'Saving...')\n : isEditing\n ? tr('scorecard.updateMetric', 'Update Metric')\n : tr('scorecard.saveMetric', 'Save Metric')}\n </button>\n </div>\n </form>\n </motion.div>\n </motion.div>\n )}\n </AnimatePresence>\n );\n };\n\n if (loading && !scorecard) {\n return <LoadingState message={tr('scorecard.loading', 'Loading scorecard...')} />;\n }\n\n if (error && !scorecard) {\n return (\n <ErrorState\n title={tr('scorecard.failedLoad', 'Failed to load scorecard')}\n message={error.message}\n onRetry={() => refetch()}\n />\n );\n }\n\n if (!scorecard || scorecard.metrics.length === 0) {\n return (\n <>\n <ScorecardEmptyState\n ownerType={ownerType}\n ownerId={ownerId}\n workspaceId={workspaceId}\n applyingTemplateId={applyingTemplateId}\n onApplyTemplate={handleApplyTemplate}\n onAddManual={handleAddMetricClick}\n />\n {renderAddMetricDialog()}\n {renderDeleteMetricDialog()}\n </>\n );\n }\n\n const overallScore = scorecard.overallScore ?? 0;\n const scoreColor =\n overallScore >= 80\n ? 'text-status-success-text'\n : overallScore >= 50\n ? 'text-status-warning-text'\n : 'text-status-error-text';\n\n return (\n <div className=\"space-y-6\">\n {/* Overall Score */}\n <Card padding=\"lg\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"flex items-center gap-1.5 text-sm font-medium text-text-secondary\">\n {tr('scorecard.overallScore', 'Overall Score')}\n <span title=\"Weighted average of each metric's score (0–100). Status is derived from value vs threshold.\">\n <Info className=\"size-3.5 text-text-muted\" />\n </span>\n </p>\n <p className={`text-4xl font-bold ${scoreColor}`}>\n {overallScore}\n <span className=\"text-lg text-text-muted\">/100</span>\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {scorecard.securityTier && <TierBadge tier={scorecard.securityTier} />}\n <TrendingUp className={`size-6 ${scoreColor}`} />\n <EntityShareButton\n entity=\"scorecard\"\n resource={{\n id: scorecard.id,\n // Scorecard has no display-name field; use its id as the title.\n name: scorecard.id,\n owner: { ownerId, ownerType },\n }}\n label={tr('scorecard.share', 'Share')}\n />\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"rounded-md p-2 text-text-secondary transition-colors hover:bg-bg-sunken\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={handleAddMetricClick}\n className=\"flex items-center gap-2 rounded-md bg-action-primary-bg px-3 py-2 text-sm text-action-primary-text transition-colors hover:bg-action-primary-bg-hover\"\n >\n <Plus className=\"size-4\" />\n {tr('scorecard.addMetric', 'Add Metric')}\n </button>\n </div>\n </div>\n </Card>\n\n {/* Metrics Grid */}\n <MotionList stagger={0.04} className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {scorecard.metrics.map((metric: ScorecardMetric) => {\n const config = statusConfig[metric.status] || statusConfig.UNKNOWN;\n const StatusIcon = config.icon;\n const value = metric.value ?? 0;\n const threshold = metric.threshold ?? 100;\n const percentage =\n threshold > 0 ? Math.min(100, Math.max(0, (value / threshold) * 100)) : 0;\n const isExpanded = expandedMetric === metric.id;\n const showRemediation =\n metric.remediation && (metric.status === 'WARNING' || metric.status === 'FAILING');\n\n return (\n <MotionItem key={metric.id}>\n <Card hoverable padding=\"none\">\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => setExpandedMetric(isExpanded ? null : metric.id)}\n onKeyDown={(event) => {\n if (event.target !== event.currentTarget) return;\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n setExpandedMetric(isExpanded ? null : metric.id);\n }\n }}\n className=\"cursor-pointer rounded-lg p-4 focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n aria-expanded={isExpanded}\n aria-label={`${metric.name} metric details`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {metric.name}\n </p>\n <p className=\"mt-0.5 flex items-center gap-1.5 text-xs text-text-muted\">\n <span>\n {metric.direction === 'LOWER_IS_BETTER'\n ? '↓ lower better'\n : '↑ higher better'}\n </span>\n {metric.weight && metric.weight !== 1 ? (\n <span>· ×{metric.weight}</span>\n ) : null}\n {metric.statusOverride ? <span>· pinned</span> : null}\n </p>\n </div>\n <div className=\"flex flex-shrink-0 items-center gap-1\">\n <button\n type=\"button\"\n onClick={(event) => handleEditMetricClick(event, metric)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-action-primary-bg\"\n aria-label={tr('scorecard.editMetric', 'Edit metric')}\n title={tr('scorecard.editMetric', 'Edit metric')}\n >\n <Pencil className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={(event) => handleRemoveMetricClick(event, metric)}\n className=\"rounded-md p-1 text-text-secondary hover:bg-status-error-bg hover:text-status-error-text focus:outline-none focus:ring-2 focus:ring-status-error-text\"\n aria-label={tr('scorecard.removeMetric', 'Remove metric')}\n title={tr('scorecard.removeMetric', 'Remove metric')}\n >\n <Trash2 className=\"size-4\" />\n </button>\n <div\n className={`flex size-7 items-center justify-center rounded-full ${config.bg}`}\n >\n <StatusIcon className={`size-4 ${config.text}`} />\n </div>\n </div>\n </div>\n\n {/* Value and progress */}\n <div className=\"mt-3\">\n <div className=\"flex items-baseline justify-between\">\n <span className=\"text-lg font-bold text-text-primary\">\n {value}\n {metric.unit ? ` ${metric.unit}` : ''}\n </span>\n <span className=\"text-xs text-text-muted\">\n / {threshold}\n {metric.unit ? ` ${metric.unit}` : ''}\n </span>\n </div>\n <div className=\"mt-1.5 h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <motion.div\n initial={{ width: 0 }}\n animate={{ width: `${percentage}%` }}\n transition={{ duration: 0.5, ease: 'easeOut' }}\n className={`h-full rounded-full ${config.bar}`}\n />\n </div>\n </div>\n\n {showRemediation && (\n <p className=\"mt-2 rounded-md border border-status-warning-border bg-status-warning-bg-subtle px-2 py-1.5 text-xs text-status-warning-text\">\n {metric.remediation}\n </p>\n )}\n </div>\n\n {/* Expanded: trend + optional embed */}\n <AnimatePresence>\n {isExpanded && (\n <motion.div\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: 'auto', opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={{ duration: 0.2 }}\n className=\"overflow-hidden border-t border-border-default\"\n >\n <div className=\"px-4 py-3\">\n <MetricTrend metricId={metric.id} colorClass={config.text} />\n </div>\n {metric.iframeUrl && (\n <iframe\n src={metric.iframeUrl}\n className=\"w-full border-0\"\n style={{\n height: metric.iframeHeight ? `${metric.iframeHeight}px` : '200px',\n }}\n title={`${metric.name} embed`}\n sandbox=\"allow-scripts allow-same-origin\"\n />\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </Card>\n </MotionItem>\n );\n })}\n </MotionList>\n\n <ScorecardsOverviewPanel ownerType={ownerType} highlightOwnerId={ownerId} />\n\n {renderAddMetricDialog()}\n {renderDeleteMetricDialog()}\n </div>\n );\n};\n\nconst TIER_STYLE: Record<ScorecardTier, { label: string; cls: string }> = {\n [ScorecardTier.Bronze]: {\n label: 'Bronze',\n cls: 'bg-status-warning-bg-subtle text-status-warning-text border border-status-warning-border',\n },\n [ScorecardTier.Silver]: {\n label: 'Silver',\n cls: 'bg-bg-sunken text-text-secondary border border-border-strong',\n },\n [ScorecardTier.Gold]: {\n label: 'Gold',\n cls: 'bg-status-success-bg-subtle text-status-success-text border border-status-success-border',\n },\n};\n\n/**\n * TierBadge — shows the scorecard's derived production-readiness tier (Bronze/Silver/Gold).\n * The tier is computed server-side from the tier-tagged metrics; this is read-only.\n */\nconst TierBadge: FC<{ tier: ScorecardTier }> = ({ tier }) => {\n const style = TIER_STYLE[tier];\n return (\n <span\n className={`inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-xs font-semibold ${style.cls}`}\n title={`Security readiness tier: ${style.label}`}\n >\n <ShieldCheck className=\"size-3.5\" />\n {style.label}\n </span>\n );\n};\n"],"mappings":"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"}
|