frontend_nyla/src/components/QuickActionBoard/QuickActionBoard.tsx
2026-04-11 00:07:30 +02:00

166 lines
5.1 KiB
TypeScript

/**
* QuickActionBoard — reusable card grid for feature dashboards.
*
* Renders a set of Quick Action cards grouped by category.
* Each card dispatches via the onDispatch callback; the parent
* decides what happens (navigate to workspace, trigger workflow, etc.).
*/
import React from 'react';
import styles from './QuickActionBoard.module.css';
import { useLanguage } from '../../providers/language/LanguageContext';
// ============================================================================
// TYPES
// ============================================================================
export interface QuickAction {
id: string;
label: string;
description: string;
icon: string;
color: string;
category: string;
actionType: 'agentPrompt' | 'workflow' | 'link';
config: Record<string, any>;
sortOrder: number;
}
export interface QuickActionCategory {
id: string;
label: string;
sortOrder: number;
}
export interface QuickActionBoardProps {
actions: QuickAction[];
categories?: QuickActionCategory[];
onDispatch: (action: QuickAction) => void;
loading?: boolean;
grouped?: boolean;
}
// ============================================================================
// ICON MAP (mdi name → unicode/emoji fallback)
// ============================================================================
const _ICON_MAP: Record<string, string> = {
'mdi-file-document-check-outline': '\uD83D\uDCCB',
'mdi-sync': '\uD83D\uDD04',
'mdi-chart-bar': '\uD83D\uDCCA',
'mdi-view-dashboard-outline': '\uD83D\uDCF0',
'mdi-cash-multiple': '\uD83D\uDCB0',
'mdi-clipboard-check-outline': '\u2705',
'mdi-chart-timeline-variant': '\uD83D\uDCC8',
'mdi-camera-document-outline': '\uD83D\uDCF7',
};
function _renderIcon(icon: string, color: string): React.ReactNode {
const fallback = _ICON_MAP[icon] || '\u26A1';
return (
<span className={styles.actionIcon} style={{ color }}>
{fallback}
</span>
);
}
// ============================================================================
// COMPONENT
// ============================================================================
export const QuickActionBoard: React.FC<QuickActionBoardProps> = ({
actions,
categories,
onDispatch,
loading = false,
grouped = true,
}) => {
const { t } = useLanguage();
if (loading) {
return (
<div className={styles.board}>
<h3 className={styles.boardTitle}>{t('Schnellaktionen')}</h3>
<div className={styles.grid}>
{[1, 2, 3, 4].map((i) => (
<div key={i} className={`${styles.card} ${styles.cardSkeleton}`}>
<div className={styles.skeletonIcon} />
<div className={styles.skeletonText} />
<div className={styles.skeletonTextShort} />
</div>
))}
</div>
</div>
);
}
if (!actions || actions.length === 0) {
return null;
}
const _handleClick = (action: QuickAction) => (e: React.MouseEvent) => {
e.preventDefault();
onDispatch(action);
};
if (!grouped || !categories || categories.length === 0) {
return (
<div className={styles.board}>
<h3 className={styles.boardTitle}>{t('Schnellaktionen')}</h3>
<div className={styles.grid}>
{actions.map((action) => (
<button
key={action.id}
className={styles.card}
onClick={_handleClick(action)}
title={action.description}
>
{_renderIcon(action.icon, action.color)}
<span className={styles.actionLabel}>{action.label}</span>
<span className={styles.actionDescription}>{action.description}</span>
</button>
))}
</div>
</div>
);
}
const sortedCategories = [...categories].sort((a, b) => a.sortOrder - b.sortOrder);
const actionsByCategory = new Map<string, QuickAction[]>();
for (const action of actions) {
const cat = action.category || '_uncategorized';
if (!actionsByCategory.has(cat)) actionsByCategory.set(cat, []);
actionsByCategory.get(cat)!.push(action);
}
return (
<div className={styles.board}>
<h3 className={styles.boardTitle}>{t('Schnellaktionen')}</h3>
{sortedCategories.map((cat) => {
const catActions = actionsByCategory.get(cat.id);
if (!catActions || catActions.length === 0) return null;
return (
<div key={cat.id} className={styles.categorySection}>
<h4 className={styles.categoryTitle}>{cat.label}</h4>
<div className={styles.grid}>
{catActions.map((action) => (
<button
key={action.id}
className={styles.card}
onClick={_handleClick(action)}
title={action.description}
>
{_renderIcon(action.icon, action.color)}
<span className={styles.actionLabel}>{action.label}</span>
<span className={styles.actionDescription}>{action.description}</span>
</button>
))}
</div>
</div>
);
})}
</div>
);
};
export default QuickActionBoard;