feat(ai-admin-console): complete implementation and resolve lint compilation errors
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// File: components/ai/AiStatusBanner.tsx
|
||||
// Change Log
|
||||
// - 2026-05-14: เพิ่ม banner สำหรับ graceful degradation ของ AI staging.
|
||||
// - 2026-05-21: รองรับ global banner เมื่อ Superadmin ปิด AI features.
|
||||
'use client';
|
||||
|
||||
import { AlertTriangle, CheckCircle2 } from 'lucide-react';
|
||||
@@ -8,19 +9,20 @@ import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { useTranslations } from '@/hooks/use-translations';
|
||||
|
||||
interface AiStatusBannerProps {
|
||||
isOffline: boolean;
|
||||
isOffline?: boolean;
|
||||
aiEnabled?: boolean;
|
||||
queuePaused?: boolean;
|
||||
}
|
||||
|
||||
export function AiStatusBanner({ isOffline, queuePaused = false }: AiStatusBannerProps) {
|
||||
export function AiStatusBanner({ isOffline = false, aiEnabled = true, queuePaused = false }: AiStatusBannerProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
if (isOffline) {
|
||||
if (isOffline || !aiEnabled) {
|
||||
return (
|
||||
<Alert variant="destructive">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertTitle>{t('ai.service_unavailable')}</AlertTitle>
|
||||
<AlertDescription>{t('ai.status.offlineDescription')}</AlertDescription>
|
||||
<AlertTitle>{t('ai.status.offlineTitle')}</AlertTitle>
|
||||
<AlertDescription>{t('ai.status.disabledDescription')}</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// File: components/ai/__tests__/ai-suggestion-button.test.tsx
|
||||
// Change Log
|
||||
// - 2026-05-21: เพิ่ม unit tests สำหรับ soft fallback ของปุ่ม AI suggestion.
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { AiSuggestionButton } from '../ai-suggestion-button';
|
||||
|
||||
describe('AiSuggestionButton', () => {
|
||||
it('ควร disable และแสดงข้อความ fallback เมื่อ AI ถูกปิด', () => {
|
||||
const onClick = vi.fn();
|
||||
render(<AiSuggestionButton aiEnabled={false} onClick={onClick} />);
|
||||
|
||||
const button = screen.getByRole('button', { name: /AI Suggestion/i });
|
||||
expect(button).toBeDisabled();
|
||||
expect(screen.getByText('ระบบ AI ไม่พร้อมใช้งานชั่วคราว กรุณากรอกข้อมูลด้วยตนเอง')).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(button);
|
||||
expect(onClick).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('ควรเรียก onClick เมื่อ AI เปิดใช้งาน', () => {
|
||||
const onClick = vi.fn();
|
||||
render(<AiSuggestionButton aiEnabled={true} onClick={onClick} />);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /AI Suggestion/i }));
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
// File: components/ai/ai-status-banner-host.tsx
|
||||
// Change Log
|
||||
// - 2026-05-21: เพิ่ม host สำหรับ global AI disabled banner เฉพาะผู้ใช้ที่มีสิทธิ์ AI.
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { AiStatusBanner } from './AiStatusBanner';
|
||||
import { useCurrentUserAiStatus } from '@/hooks/use-ai-status';
|
||||
import { AI_FEATURES_UNAVAILABLE_EVENT } from '@/lib/api/client';
|
||||
|
||||
/** แสดง global banner เมื่อ AI ถูกปิดสำหรับผู้ใช้ที่มีสิทธิ์ AI */
|
||||
export function AiStatusBannerHost() {
|
||||
const [serviceUnavailable, setServiceUnavailable] = useState(false);
|
||||
const { data, isLoading } = useCurrentUserAiStatus();
|
||||
|
||||
useEffect(() => {
|
||||
const handleAiUnavailable = () => setServiceUnavailable(true);
|
||||
window.addEventListener(AI_FEATURES_UNAVAILABLE_EVENT, handleAiUnavailable);
|
||||
return () => window.removeEventListener(AI_FEATURES_UNAVAILABLE_EVENT, handleAiUnavailable);
|
||||
}, []);
|
||||
|
||||
if (isLoading || (data?.shouldShowBanner !== true && !serviceUnavailable)) return null;
|
||||
return (
|
||||
<div className="sticky top-0 z-40 border-b bg-background px-4 py-2">
|
||||
<AiStatusBanner aiEnabled={serviceUnavailable ? false : data?.aiFeaturesEnabled} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// File: components/ai/ai-suggestion-button.tsx
|
||||
// Change Log
|
||||
// - 2026-05-21: เพิ่มปุ่ม AI Suggestion พร้อม soft fallback เมื่อ AI ถูกปิด.
|
||||
'use client';
|
||||
|
||||
import { Sparkles } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
||||
|
||||
const DEFAULT_DISABLED_MESSAGE = 'ระบบ AI ไม่พร้อมใช้งานชั่วคราว กรุณากรอกข้อมูลด้วยตนเอง';
|
||||
|
||||
interface AiSuggestionButtonProps {
|
||||
aiEnabled: boolean;
|
||||
isLoading?: boolean;
|
||||
label?: string;
|
||||
disabledMessage?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
/** ปุ่มเรียก AI suggestion ที่แสดง fallback ชัดเจนเมื่อระบบ AI ปิด */
|
||||
export function AiSuggestionButton({
|
||||
aiEnabled,
|
||||
isLoading = false,
|
||||
label = 'AI Suggestion',
|
||||
disabledMessage = DEFAULT_DISABLED_MESSAGE,
|
||||
onClick,
|
||||
}: AiSuggestionButtonProps) {
|
||||
const disabled = !aiEnabled || isLoading;
|
||||
const button = (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
aria-label={label}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
className="gap-2"
|
||||
>
|
||||
<Sparkles className="h-4 w-4" />
|
||||
{label}
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (aiEnabled) return button;
|
||||
|
||||
return (
|
||||
<HoverCard openDelay={100}>
|
||||
<HoverCardTrigger asChild>
|
||||
<span className="inline-flex cursor-not-allowed">
|
||||
{button}
|
||||
<span className="sr-only">{disabledMessage}</span>
|
||||
</span>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="border-amber-200 bg-amber-50 text-amber-900">
|
||||
<p className="text-sm">{disabledMessage}</p>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user