This commit is contained in:
Dmitry 2026-01-07 16:59:18 +03:00
parent c2a36842b6
commit 7604fe2004
2 changed files with 91 additions and 12 deletions

View file

@ -3,7 +3,9 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { toast } from 'sonner' import { toast } from 'sonner'
import { packsApi, isPacksApiError } from '@/api/packs' import { packsApi, isPacksApiError } from '@/api/packs'
import { formatApiError, getDetailedErrorMessage } from '@/lib/error-utils' import { formatApiError, getDetailedErrorMessage } from '@/lib/error-utils'
import type { EditCardPackDto, CardPackPreviewDto, PaginatedResponse, Question } from '@/types/models' import type { EditCardPackDto, CardPackPreviewDto, PaginatedResponse } from '@/types/models'
import type { Question } from '@/types/questions'
import { questionFromJson, questionToJson } from '@/types/questions'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input' import { Input } from '@/components/ui/input'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
@ -37,6 +39,8 @@ import { Label } from '@/components/ui/label'
import { Textarea } from '@/components/ui/textarea' import { Textarea } from '@/components/ui/textarea'
import { Checkbox } from '@/components/ui/checkbox' import { Checkbox } from '@/components/ui/checkbox'
import { Plus, Search, Edit, Trash2, ChevronLeft, ChevronRight } from 'lucide-react' import { Plus, Search, Edit, Trash2, ChevronLeft, ChevronRight } from 'lucide-react'
import { TestQuestionsManager } from '@/components/TestQuestionsManager'
import { QuestionEditorDialog } from '@/components/QuestionEditorDialog'
export default function PacksPage() { export default function PacksPage() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
@ -48,6 +52,13 @@ export default function PacksPage() {
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false)
const [packToDelete, setPackToDelete] = useState<CardPackPreviewDto | null>(null) const [packToDelete, setPackToDelete] = useState<CardPackPreviewDto | null>(null)
// Question editor state
const [isQuestionEditorOpen, setIsQuestionEditorOpen] = useState(false)
const [editingQuestion, setEditingQuestion] = useState<{
question: Question | null
index: number
} | null>(null)
// Form state // Form state
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
name: '', name: '',
@ -139,12 +150,18 @@ export default function PacksPage() {
try { try {
const fullPack = await packsApi.getPack(pack.id) const fullPack = await packsApi.getPack(pack.id)
setSelectedPack(fullPack) setSelectedPack(fullPack)
// Convert questions from backend format to frontend format
const questions: Question[] = Array.isArray(fullPack.questions)
? fullPack.questions.map((q: unknown) => questionFromJson(q))
: []
setFormData({ setFormData({
name: fullPack.name || '', name: fullPack.name || '',
description: fullPack.description || '', description: fullPack.description || '',
category: fullPack.category || '', category: fullPack.category || '',
isPublic: fullPack.isPublic ?? true, isPublic: fullPack.isPublic ?? true,
questions: fullPack.questions || [], questions,
}) })
setIsDialogOpen(true) setIsDialogOpen(true)
@ -180,6 +197,9 @@ export default function PacksPage() {
return return
} }
// Convert questions to backend format
const questionsForBackend = formData.questions.map(q => questionToJson(q))
const packData: EditCardPackDto = { const packData: EditCardPackDto = {
// Only include id for updates, not for new packs // Only include id for updates, not for new packs
...(selectedPack && { id: selectedPack.id }), ...(selectedPack && { id: selectedPack.id }),
@ -187,7 +207,7 @@ export default function PacksPage() {
description: formData.description.trim(), description: formData.description.trim(),
category: formData.category.trim(), category: formData.category.trim(),
isPublic: formData.isPublic, isPublic: formData.isPublic,
questions: formData.questions, questions: questionsForBackend as any, // Backend expects different format
} }
if (selectedPack) { if (selectedPack) {
@ -197,6 +217,42 @@ export default function PacksPage() {
} }
} }
// Question editor handlers
const handleAddQuestion = () => {
setEditingQuestion({ question: null, index: -1 })
setIsQuestionEditorOpen(true)
}
const handleEditQuestion = (question: Question, index: number) => {
setEditingQuestion({ question, index })
setIsQuestionEditorOpen(true)
}
const handleSaveQuestion = (question: Question) => {
if (editingQuestion) {
const newQuestions = [...formData.questions]
if (editingQuestion.index >= 0) {
// Update existing question
newQuestions[editingQuestion.index] = question
} else {
// Add new question
newQuestions.push(question)
}
setFormData(prev => ({ ...prev, questions: newQuestions }))
}
setIsQuestionEditorOpen(false)
setEditingQuestion(null)
}
const handleCloseQuestionEditor = () => {
setIsQuestionEditorOpen(false)
setEditingQuestion(null)
}
const handleQuestionsChange = (questions: Question[]) => {
setFormData(prev => ({ ...prev, questions }))
}
const handleDelete = (pack: CardPackPreviewDto) => { const handleDelete = (pack: CardPackPreviewDto) => {
setPackToDelete(pack) setPackToDelete(pack)
setIsDeleteDialogOpen(true) setIsDeleteDialogOpen(true)
@ -384,7 +440,7 @@ export default function PacksPage() {
{/* Create/Edit Dialog */} {/* Create/Edit Dialog */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="sm:max-w-[600px] max-h-[80vh] overflow-y-auto"> <DialogContent className="sm:max-w-[800px] max-h-[90vh] overflow-y-auto">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{selectedPack ? 'Edit Pack' : 'Create New Pack'} {selectedPack ? 'Edit Pack' : 'Create New Pack'}
@ -439,13 +495,23 @@ export default function PacksPage() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label>Questions</Label> <div className="flex items-center justify-between">
<p className="text-sm text-muted-foreground"> <Label>Questions</Label>
{formData.questions.length} question(s) in this pack <Button
</p> type="button"
<p className="text-xs text-muted-foreground"> variant="outline"
Questions can be managed through the question editor size="sm"
</p> onClick={handleAddQuestion}
>
<Plus className="h-4 w-4 mr-2" />
Add Question
</Button>
</div>
<TestQuestionsManager
questions={formData.questions}
onChange={handleQuestionsChange}
onEdit={handleEditQuestion}
/>
</div> </div>
</div> </div>
@ -461,6 +527,14 @@ export default function PacksPage() {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
{/* Question Editor Dialog */}
<QuestionEditorDialog
open={isQuestionEditorOpen}
question={editingQuestion?.question || null}
onSave={handleSaveQuestion}
onClose={handleCloseQuestionEditor}
/>
{/* Delete Confirmation Dialog */} {/* Delete Confirmation Dialog */}
<AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}> <AlertDialog open={isDeleteDialogOpen} onOpenChange={setIsDeleteDialogOpen}>
<AlertDialogContent> <AlertDialogContent>

View file

@ -251,7 +251,12 @@ const RoomPage = () => {
> >
Показать QR-код Показать QR-код
</button> </button>
{isHost && room.status === 'WAITING' && ( {isHost &&
(room.status === 'WAITING' ||
(room.status === 'PLAYING' &&
(!room.questionPack ||
room.questionPack.questionCount === 0 ||
room.currentQuestionIndex === 0))) && (
<button <button
onClick={handleStartGame} onClick={handleStartGame}
className="primary" className="primary"