Files

126 lines
4.0 KiB
TypeScript

'use client';
import { useState } from 'react';
import {
ChevronDown,
ListChecks,
PanelRightClose,
PanelRightOpen,
} from 'lucide-react';
import { useScopedI18n } from '@/locales/client';
import { Option } from '@/types';
interface Question {
id: number;
content: string;
options: Option[];
}
interface ProgressPanelProps {
questions: Question[];
answers: { [key: number]: string };
activePanelQuestion: number | null;
goToQuestion: (questionId: number) => void;
showProgressPanel: boolean;
toggleProgressPanel: () => void;
completionPercentage: number;
}
export function ProgressPanel({
questions,
answers,
activePanelQuestion,
goToQuestion,
showProgressPanel,
toggleProgressPanel,
completionPercentage,
}: ProgressPanelProps) {
const t = useScopedI18n('component.questionnaire.test.public.progressPanel');
const [showMobileQuestions, setShowMobileQuestions] = useState(false);
const questionGrid = (
<div className="grid grid-cols-5 gap-2 overflow-y-auto sm:grid-cols-8 lg:mb-4 lg:max-h-[400px] lg:grid-cols-5 lg:gap-1">
{questions.map((_, index) => {
const questionNumber = index + 1;
return (
<button
type="button"
key={questionNumber}
className={`flex h-11 min-w-11 items-center justify-center rounded-md text-sm lg:h-9 lg:w-9 lg:min-w-0 lg:text-xs
${answers[questionNumber] ? 'border-2 border-green-600 bg-green-50' : 'border bg-white'}
${questionNumber === activePanelQuestion ? 'border-green-700 bg-green-200' : ''}
active:bg-gray-100 lg:hover:bg-gray-100`}
onClick={() => {
goToQuestion(questionNumber);
setShowMobileQuestions(false);
}}
title={t('jumpToQuestion', { questionNumber })}
>
{questionNumber}
</button>
);
})}
</div>
);
return (
<>
<div className="mb-4 lg:hidden">
<button
type="button"
className="flex min-h-12 w-full items-center justify-between rounded-md border bg-muted/30 px-4 text-base"
aria-expanded={showMobileQuestions}
onClick={() => setShowMobileQuestions((current) => !current)}
>
<span className="flex items-center gap-2">
<ListChecks className="h-5 w-5" />
题目进度 {Object.keys(answers).length} / {questions.length}
</span>
<ChevronDown
className={`h-5 w-5 transition-transform ${showMobileQuestions ? 'rotate-180' : ''}`}
/>
</button>
{showMobileQuestions && (
<div className="mt-3 border bg-muted/20 p-3">{questionGrid}</div>
)}
</div>
<button
type="button"
className="fixed right-4 top-16 z-40 hidden h-11 w-11 items-center justify-center rounded-full bg-blue-600 text-white transition-colors hover:bg-blue-700 lg:flex"
onClick={toggleProgressPanel}
aria-label={showProgressPanel ? t('hideNav') : t('showNav')}
title={showProgressPanel ? t('hideNav') : t('showNav')}
>
{showProgressPanel ? (
<PanelRightClose className="h-5 w-5" />
) : (
<PanelRightOpen className="h-5 w-5" />
)}
</button>
<div
className={`fixed right-4 top-28 z-30 hidden w-64 rounded-lg border bg-white p-4 shadow-lg transition-transform duration-300 lg:block ${
showProgressPanel
? 'translate-x-0'
: 'translate-x-[calc(100%+2rem)]'
}`}
>
<div className="mb-4">
<div className="mb-2 text-sm font-medium">
{t('completionProgress')}
{Math.round(completionPercentage)}%
</div>
<div className="h-2 w-full rounded-full bg-gray-200">
<div
className="h-2 rounded-full bg-blue-500 transition-all duration-300"
style={{ width: `${completionPercentage}%` }}
/>
</div>
</div>
{questionGrid}
</div>
</>
);
}