frontend_nyla/src/pages/views/teamsbot/TeamsbotModulesView.tsx
2026-05-06 23:28:15 +02:00

189 lines
7.2 KiB
TypeScript

/**
* TeamsBot Modules View
*
* CRUD list of MeetingModules with expandable session lists per module.
*/
import React, { useState, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
import * as teamsbotApi from '../../../api/teamsbotApi';
import { useLanguage } from '../../../providers/language/LanguageContext';
import styles from './Teamsbot.module.css';
const SERIES_TYPE_LABELS: Record<string, string> = {
weekly: 'Wöchentlich',
biweekly: 'Zweiwöchentlich',
monthly: 'Monatlich',
adhoc: 'Adhoc',
project: 'Projekt',
};
const STATUS_LABELS: Record<string, string> = {
active: 'Aktiv',
archived: 'Archiviert',
completed: 'Abgeschlossen',
};
export const TeamsbotModulesView: React.FC = () => {
const { t } = useLanguage();
const { instance, mandateId } = useCurrentInstance();
const instanceId = instance?.id || '';
const navigate = useNavigate();
const [modules, setModules] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [expandedId, setExpandedId] = useState<string | null>(null);
const [moduleSessions, setModuleSessions] = useState<Record<string, any[]>>({});
const [deleteConfirm, setDeleteConfirm] = useState<string | null>(null);
const [editingModule, setEditingModule] = useState<any | null>(null);
const _loadModules = useCallback(async () => {
if (!instanceId) return;
setLoading(true);
try {
const result = await teamsbotApi.listModules(instanceId);
setModules(result || []);
} catch (err) {
console.error('Failed to load modules:', err);
} finally {
setLoading(false);
}
}, [instanceId]);
useEffect(() => { _loadModules(); }, [_loadModules]);
const _loadModuleSessions = useCallback(async (moduleId: string) => {
if (!instanceId) return;
try {
const detail = await teamsbotApi.getModuleDetail(instanceId, moduleId);
setModuleSessions(prev => ({ ...prev, [moduleId]: detail?.sessions || [] }));
} catch (err) {
console.error('Failed to load module sessions:', err);
}
}, [instanceId]);
const _toggleExpand = (moduleId: string) => {
if (expandedId === moduleId) {
setExpandedId(null);
} else {
setExpandedId(moduleId);
if (!moduleSessions[moduleId]) _loadModuleSessions(moduleId);
}
};
const _handleDelete = async (moduleId: string) => {
try {
await teamsbotApi.deleteModule(instanceId, moduleId);
setDeleteConfirm(null);
_loadModules();
} catch (err) {
console.error('Delete failed:', err);
}
};
const _handleUpdate = async (moduleId: string, updates: any) => {
try {
await teamsbotApi.updateModule(instanceId, moduleId, updates);
setEditingModule(null);
_loadModules();
} catch (err) {
console.error('Update failed:', err);
}
};
return (
<div className={styles.modulesContainer}>
<div className={styles.modulesHeader}>
<h2>{t('Meeting-Module')}</h2>
<button
className={styles.btnPrimary}
onClick={() => navigate(`/mandates/${mandateId}/teamsbot/${instanceId}/assistant`)}
>
{t('Neues Modul')}
</button>
</div>
{loading && <div className={styles.loading}>{t('Laden...')}</div>}
<div className={styles.modulesList}>
{modules.map(mod => (
<div key={mod.id} className={`${styles.moduleCard} ${expandedId === mod.id ? styles.moduleExpanded : ''}`}>
<div className={styles.moduleRow} onClick={() => _toggleExpand(mod.id)}>
<span className={styles.moduleType}>{t(SERIES_TYPE_LABELS[mod.seriesType] || mod.seriesType)}</span>
<span className={styles.moduleTitle}>{mod.title}</span>
<span className={styles.moduleStatus}>{t(STATUS_LABELS[mod.status] || mod.status)}</span>
<div className={styles.moduleActions}>
<button className={styles.btnPrimary} style={{ padding: '0.3rem 0.7rem', fontSize: '0.8rem' }} onClick={e => {
e.stopPropagation();
navigate(`/mandates/${mandateId}/teamsbot/${instanceId}/assistant?moduleId=${mod.id}`);
}}>{t('Meeting starten')}</button>
<button className={styles.btnSmall} onClick={e => { e.stopPropagation(); setEditingModule(mod); }}>{t('Bearbeiten')}</button>
<button className={styles.btnSmallDanger} onClick={e => { e.stopPropagation(); setDeleteConfirm(mod.id); }}>{t('Löschen')}</button>
</div>
</div>
{expandedId === mod.id && (
<div className={styles.moduleSessionsList}>
{(moduleSessions[mod.id] || []).length === 0 ? (
<p className={styles.noSessions}>{t('Keine Sitzungen')}</p>
) : (
(moduleSessions[mod.id] || []).map((sess: any) => (
<div
key={sess.id}
className={styles.sessionRow}
onClick={() => navigate(`/mandates/${mandateId}/teamsbot/${instanceId}/sessions?sessionId=${sess.id}`)}
>
<span>{sess.botName || 'Bot'}</span>
<span className={styles.sessionStatus}>{sess.status}</span>
<span>{sess.startedAt ? new Date(sess.startedAt * 1000).toLocaleDateString() : '-'}</span>
</div>
))
)}
</div>
)}
</div>
))}
</div>
{deleteConfirm && (
<div className={styles.confirmOverlay}>
<div className={styles.confirmDialog}>
<p>{t('Modul wirklich löschen? Sessions werden dem Modul entkoppelt.')}</p>
<div className={styles.confirmActions}>
<button className={styles.btnSecondary} onClick={() => setDeleteConfirm(null)}>{t('Abbrechen')}</button>
<button className={styles.btnDanger} onClick={() => _handleDelete(deleteConfirm)}>{t('Löschen')}</button>
</div>
</div>
</div>
)}
{editingModule && (
<div className={styles.confirmOverlay}>
<div className={styles.editDialog}>
<h3>{t('Modul bearbeiten')}</h3>
<input
type="text"
defaultValue={editingModule.title}
className={styles.wizardInput}
onBlur={e => setEditingModule({ ...editingModule, title: e.target.value })}
/>
<textarea
defaultValue={editingModule.goals || ''}
className={styles.wizardTextarea}
placeholder={t('Ziele')}
rows={3}
onBlur={e => setEditingModule({ ...editingModule, goals: e.target.value })}
/>
<div className={styles.confirmActions}>
<button className={styles.btnSecondary} onClick={() => setEditingModule(null)}>{t('Abbrechen')}</button>
<button className={styles.btnPrimary} onClick={() => _handleUpdate(editingModule.id, {
title: editingModule.title,
goals: editingModule.goals,
})}>{t('Speichern')}</button>
</div>
</div>
</div>
)}
</div>
);
};