@burdenoff/microfe-vibecontrols 2026.601.21 → 2026.601.23
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 +77 -66
- package/dist/components/actions/ActionCard.js.map +1 -1
- package/dist/components/ai/AIContextsTab.js +186 -173
- package/dist/components/ai/AIContextsTab.js.map +1 -1
- package/dist/components/ai/AITemplatesTab.js +135 -125
- package/dist/components/ai/AITemplatesTab.js.map +1 -1
- package/dist/components/ai/InstructionFilesManager.js +151 -137
- package/dist/components/ai/InstructionFilesManager.js.map +1 -1
- package/dist/components/docs/DocsEditPanel.js +17 -16
- package/dist/components/docs/DocsEditPanel.js.map +1 -1
- package/dist/components/docs/DocsPreviewPanel.js +25 -24
- package/dist/components/docs/DocsPreviewPanel.js.map +1 -1
- package/dist/components/docs/DocsTabPanel.js +110 -94
- package/dist/components/docs/DocsTabPanel.js.map +1 -1
- package/dist/components/notes/NotesTabPanel.js +8 -8
- package/dist/components/scorecard/ScorecardPanel.js +271 -257
- package/dist/components/scorecard/ScorecardPanel.js.map +1 -1
- package/dist/components/sharing/EntityShareButton.js +58 -0
- package/dist/components/sharing/EntityShareButton.js.map +1 -0
- package/dist/components/sharing/entityShareOps.js +603 -0
- package/dist/components/sharing/entityShareOps.js.map +1 -0
- package/dist/components/sharing/index.js +3 -0
- package/dist/components/sharing/shareRegistry.js +37 -12
- package/dist/components/sharing/shareRegistry.js.map +1 -1
- package/dist/components/sharing/useEntityShareController.js +88 -0
- package/dist/components/sharing/useEntityShareController.js.map +1 -0
- package/dist/constants/permissions.js +11 -0
- package/dist/constants/permissions.js.map +1 -1
- package/dist/pages/ActionsPage.js +162 -162
- package/dist/pages/ActionsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +264 -250
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +124 -113
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/TargetDetailsPage.js +139 -126
- package/dist/pages/TargetDetailsPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +78 -67
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/dist/pages/VibeDeckPage.js +1 -1
- package/dist/pages/VibeDetailsPage.js +484 -484
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/tunnels/TunnelDetailsPage.js +91 -77
- package/dist/pages/tunnels/TunnelDetailsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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 } from 'lucide-react';\nimport { LoadingState, ErrorState, Card } from '@/components/shared';\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 <TrendingUp className={`size-6 ${scoreColor}`} />\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"],"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 } from 'lucide-react';\nimport { LoadingState, ErrorState, Card } from '@/components/shared';\nimport { EntityShareButton } from '@/components/sharing';\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 <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"],"mappings":"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"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { SHARE_REGISTRY as e } from "./shareRegistry.js";
|
|
2
|
+
import { useEntityShareController as t } from "./useEntityShareController.js";
|
|
3
|
+
import { useState as n } from "react";
|
|
4
|
+
import { Share2 as r } from "lucide-react";
|
|
5
|
+
import { getStoredWorkspaceId as i } from "@burdenoff/fe-libs/shared/graphql";
|
|
6
|
+
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
7
|
+
import { useFeatureFlag as s } from "@burdenoff/fe-libs/shared/feature-flags";
|
|
8
|
+
import { PermissionGate as c } from "@burdenoff/fe-libs/shared/components";
|
|
9
|
+
import { ShareDialog as l } from "@burdenoff/fe-libs/sharing";
|
|
10
|
+
//#region src/components/sharing/EntityShareButton.tsx
|
|
11
|
+
function u(t) {
|
|
12
|
+
return e[t];
|
|
13
|
+
}
|
|
14
|
+
function d({ entity: e, resource: d, label: f, variant: p = "icon" }) {
|
|
15
|
+
let [m, h] = n(!1), g = t(e, d), _ = u(e);
|
|
16
|
+
if (!s(_.featureFlag, _.defaultEnabled)) return null;
|
|
17
|
+
let v = `${e}-share-button`;
|
|
18
|
+
return /* @__PURE__ */ o(c, {
|
|
19
|
+
permission: _.permission,
|
|
20
|
+
children: [p === "button" ? /* @__PURE__ */ o("button", {
|
|
21
|
+
type: "button",
|
|
22
|
+
"data-testid": v,
|
|
23
|
+
onClick: (e) => {
|
|
24
|
+
e.stopPropagation(), h(!0);
|
|
25
|
+
},
|
|
26
|
+
title: f,
|
|
27
|
+
className: "flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 text-xs sm:text-sm bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken/80 transition-colors",
|
|
28
|
+
children: [/* @__PURE__ */ a(r, { className: "size-3.5 sm:size-4" }), /* @__PURE__ */ a("span", {
|
|
29
|
+
className: "hidden sm:inline",
|
|
30
|
+
children: f
|
|
31
|
+
})]
|
|
32
|
+
}) : /* @__PURE__ */ a("button", {
|
|
33
|
+
type: "button",
|
|
34
|
+
"data-testid": v,
|
|
35
|
+
onClick: (e) => {
|
|
36
|
+
e.stopPropagation(), h(!0);
|
|
37
|
+
},
|
|
38
|
+
title: f,
|
|
39
|
+
className: "p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors",
|
|
40
|
+
children: /* @__PURE__ */ a(r, { className: "size-3.5" })
|
|
41
|
+
}), /* @__PURE__ */ a(l, {
|
|
42
|
+
resource: {
|
|
43
|
+
type: e,
|
|
44
|
+
id: d.id,
|
|
45
|
+
name: d.name,
|
|
46
|
+
vibeId: d.vibeId ?? void 0
|
|
47
|
+
},
|
|
48
|
+
controller: g,
|
|
49
|
+
open: m,
|
|
50
|
+
onOpenChange: h,
|
|
51
|
+
workspaceId: i() ?? void 0
|
|
52
|
+
})]
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { d as EntityShareButton };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=EntityShareButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EntityShareButton.js","names":[],"sources":["../../../src/components/sharing/EntityShareButton.tsx"],"sourcesContent":["/**\n * `EntityShareButton` — the generic Share affordance for the five finishable\n * VibeControls entities (aiprompt / action / aicontext / instructionfile /\n * tunnel), mirroring {@link VibeShareButton}.\n *\n * Self-contained: it owns the open/close state, builds the share controller via\n * {@link useEntityShareController}, and renders the generic fe-libs\n * `<ShareDialog>`. Visibility is gated by the entity's entry in\n * {@link SHARE_REGISTRY} (RBAC permission + the `vibecontrols.sharing.<entity>`\n * feature flag, which defaults ON for these entities). Hooks run\n * unconditionally; the component returns `null` only after the flag check so\n * React hook ordering stays stable.\n *\n * Two render modes:\n * - `variant=\"icon\"` (default) — a compact icon button suited to per-row /\n * per-card action clusters (AI tabs, instruction-file rows, action cards).\n * - `variant=\"button\"` — an icon+label button matching the Vibe-detail header\n * affordance (tunnel detail page).\n */\n\nimport { useState } from 'react';\nimport { Share2 } from 'lucide-react';\nimport { ShareDialog } from '@burdenoff/fe-libs/sharing';\nimport { PermissionGate } from '@burdenoff/fe-libs/shared/components';\nimport { useFeatureFlag } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { getStoredWorkspaceId } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { SHARE_REGISTRY } from './shareRegistry';\nimport { useEntityShareController } from './useEntityShareController';\nimport type { GenericShareEntity } from './entityShareOps';\n\nexport interface EntityShareButtonProps {\n /** Which finishable entity is being shared. */\n entity: GenericShareEntity;\n /**\n * Stable id of the resource row plus its display name, optional vibe linkage,\n * and (for owner-keyed entities like scorecard / docs that have no read-by-id\n * query) the `(ownerId, ownerType)` pair used to read existing grants.\n */\n resource: {\n id: string;\n name: string;\n vibeId?: string | null;\n owner?: { ownerId: string; ownerType: string } | null;\n };\n /** Localized button label + tooltip. */\n label: string;\n /** Visual style — compact icon (default) vs icon+label button. */\n variant?: 'icon' | 'button';\n}\n\n/** Map the generic entity key onto the `SHARE_REGISTRY` entry. */\nfunction registryEntry(entity: GenericShareEntity) {\n return SHARE_REGISTRY[entity];\n}\n\nexport function EntityShareButton({\n entity,\n resource,\n label,\n variant = 'icon',\n}: EntityShareButtonProps) {\n const [open, setOpen] = useState(false);\n const controller = useEntityShareController(entity, resource);\n const entry = registryEntry(entity);\n const enabled = useFeatureFlag(entry.featureFlag, entry.defaultEnabled);\n\n if (!enabled) return null;\n\n const testId = `${entity}-share-button`;\n\n return (\n <PermissionGate permission={entry.permission}>\n {variant === 'button' ? (\n <button\n type=\"button\"\n data-testid={testId}\n onClick={(event) => {\n event.stopPropagation();\n setOpen(true);\n }}\n title={label}\n className=\"flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 text-xs sm:text-sm bg-bg-sunken text-text-primary rounded-md hover:bg-bg-sunken/80 transition-colors\"\n >\n <Share2 className=\"size-3.5 sm:size-4\" />\n <span className=\"hidden sm:inline\">{label}</span>\n </button>\n ) : (\n <button\n type=\"button\"\n data-testid={testId}\n onClick={(event) => {\n event.stopPropagation();\n setOpen(true);\n }}\n title={label}\n className=\"p-1.5 rounded-md hover:bg-bg-elevated text-text-secondary transition-colors\"\n >\n <Share2 className=\"size-3.5\" />\n </button>\n )}\n <ShareDialog\n resource={{\n type: entity,\n id: resource.id,\n name: resource.name,\n vibeId: resource.vibeId ?? undefined,\n }}\n controller={controller}\n open={open}\n onOpenChange={setOpen}\n workspaceId={getStoredWorkspaceId() ?? undefined}\n />\n </PermissionGate>\n );\n}\n"],"mappings":";;;;;;;;;;AAoDA,SAAS,EAAc,GAA4B;AACjD,QAAO,EAAe;;AAGxB,SAAgB,EAAkB,EAChC,WACA,aACA,UACA,aAAU,UACe;CACzB,IAAM,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,IAAa,EAAyB,GAAQ,EAAS,EACvD,IAAQ,EAAc,EAAO;AAGnC,KAAI,CAFY,EAAe,EAAM,aAAa,EAAM,eAAe,CAEzD,QAAO;CAErB,IAAM,IAAS,GAAG,EAAO;AAEzB,QACE,kBAAC,GAAD;EAAgB,YAAY,EAAM;YAAlC,CACG,MAAY,WACX,kBAAC,UAAD;GACE,MAAK;GACL,eAAa;GACb,UAAU,MAAU;AAElB,IADA,EAAM,iBAAiB,EACvB,EAAQ,GAAK;;GAEf,OAAO;GACP,WAAU;aARZ,CAUE,kBAAC,GAAD,EAAQ,WAAU,sBAAuB,CAAA,EACzC,kBAAC,QAAD;IAAM,WAAU;cAAoB;IAAa,CAAA,CAC1C;OAET,kBAAC,UAAD;GACE,MAAK;GACL,eAAa;GACb,UAAU,MAAU;AAElB,IADA,EAAM,iBAAiB,EACvB,EAAQ,GAAK;;GAEf,OAAO;GACP,WAAU;aAEV,kBAAC,GAAD,EAAQ,WAAU,YAAa,CAAA;GACxB,CAAA,EAEX,kBAAC,GAAD;GACE,UAAU;IACR,MAAM;IACN,IAAI,EAAS;IACb,MAAM,EAAS;IACf,QAAQ,EAAS,UAAU,KAAA;IAC5B;GACW;GACN;GACN,cAAc;GACd,aAAa,GAAsB,IAAI,KAAA;GACvC,CAAA,CACa"}
|