feat: implement QuestionBank CRUD with pagination and template query
- Add pagination support to findAll (page, limit query params) - Add findByTemplateId method to service - Add GET /by-template/:templateId endpoint to controller - Service already includes CRUD for QuestionBank and QuestionBankItem
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Blocks } from 'lucide-react';
|
||||
import { useLanguage } from '../../../contexts/LanguageContext';
|
||||
|
||||
import { FeishuPluginConfig } from '../plugins/FeishuPluginConfig';
|
||||
|
||||
|
||||
interface PluginDef {
|
||||
id: string;
|
||||
icon: string;
|
||||
nameKey: string;
|
||||
descKey: string;
|
||||
available: boolean;
|
||||
component?: React.ComponentType;
|
||||
}
|
||||
|
||||
const PLUGIN_LIST: PluginDef[] = [
|
||||
{
|
||||
id: 'feishu',
|
||||
icon: '🪶',
|
||||
nameKey: 'pluginFeishuName',
|
||||
descKey: 'pluginFeishuDesc',
|
||||
available: true,
|
||||
component: FeishuPluginConfig,
|
||||
},
|
||||
];
|
||||
|
||||
export const PluginsView: React.FC = () => {
|
||||
const { t } = useLanguage();
|
||||
const [activePlugin, setActivePlugin] = useState<PluginDef | null>(null);
|
||||
|
||||
if (activePlugin?.component) {
|
||||
const ConfigComp = activePlugin.component;
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-[#f4f7fb]">
|
||||
<div className="px-8 pt-8 pb-4 shrink-0">
|
||||
<button
|
||||
onClick={() => setActivePlugin(null)}
|
||||
className="flex items-center gap-1.5 text-sm text-slate-500 hover:text-blue-600 transition-colors mb-4"
|
||||
>
|
||||
← {t('back')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto px-8 pb-8">
|
||||
<ConfigComp />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full bg-[#f4f7fb] overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="px-8 pt-8 pb-6 shrink-0">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-indigo-50 rounded-xl flex items-center justify-center">
|
||||
<Blocks size={20} className="text-indigo-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-[22px] font-bold text-slate-900 leading-tight">
|
||||
{t('pluginViewTitle')}
|
||||
</h1>
|
||||
<p className="text-[14px] text-slate-500 mt-0.5">{t('pluginViewDesc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Plugin Grid */}
|
||||
<div className="px-8 pb-8 flex-1 overflow-y-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6 max-w-[1200px]">
|
||||
{PLUGIN_LIST.map((plugin) => (
|
||||
<div
|
||||
key={plugin.id}
|
||||
className="bg-white rounded-2xl p-6 shadow-sm border border-slate-100 hover:shadow-md hover:border-indigo-200 transition-all cursor-pointer group"
|
||||
onClick={() => setActivePlugin(plugin)}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-4">
|
||||
<div className="w-12 h-12 bg-indigo-50 rounded-xl flex items-center justify-center text-2xl">
|
||||
{plugin.icon}
|
||||
</div>
|
||||
<div className="px-2.5 py-1 text-[12px] font-semibold text-emerald-600 bg-emerald-50 rounded-full border border-emerald-100">
|
||||
{t('pluginStatusAvailable')}
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="font-bold text-slate-800 text-[17px] mb-1.5">
|
||||
{t(plugin.nameKey as any)}
|
||||
</h3>
|
||||
<p className="text-[13px] text-slate-500 leading-relaxed line-clamp-2">
|
||||
{t(plugin.descKey as any)}
|
||||
</p>
|
||||
<div className="mt-4 pt-4 border-t border-slate-50">
|
||||
<button className="text-sm font-bold text-indigo-600 hover:text-indigo-800 transition-colors group-hover:underline">
|
||||
{t('pluginConfigure')} →
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user