admin
This commit is contained in:
parent
c2a36842b6
commit
7604fe2004
2 changed files with 91 additions and 12 deletions
|
|
@ -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">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<Label>Questions</Label>
|
<Label>Questions</Label>
|
||||||
<p className="text-sm text-muted-foreground">
|
<Button
|
||||||
{formData.questions.length} question(s) in this pack
|
type="button"
|
||||||
</p>
|
variant="outline"
|
||||||
<p className="text-xs text-muted-foreground">
|
size="sm"
|
||||||
Questions can be managed through the question editor
|
onClick={handleAddQuestion}
|
||||||
</p>
|
>
|
||||||
|
<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>
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue