dev day #65,5

This commit is contained in:
Tomas Dvorak
2025-10-20 10:40:55 +02:00
parent 9ccca365b3
commit 68e69e00cc
41 changed files with 981 additions and 1376 deletions
+121 -46
View File
@@ -36,6 +36,12 @@ const MatchLinkBadge: React.FC<{ articleId: number }> = ({ articleId }) => {
staleTime: 60_000,
retry: false,
});
// Show loading state while fetching
if (linkQ.isLoading) {
return <Badge colorScheme="gray">Načítání...</Badge>;
}
const mid = (linkQ.data as any)?.external_match_id;
if (!mid) return <Badge colorScheme="gray">Nepropojeno</Badge>;
@@ -651,38 +657,19 @@ const ArticlesAdminPage = () => {
// Forward the payload as-is so new fields (youtube, gallery) are persisted
createArticle(payload),
onSuccess: async (created: any) => {
try {
// If a match was selected (from temp storage), link it now that we have an article ID
const matchRaw = tempMatchLink || matchIdInput;
const matchToLink = typeof matchRaw === 'string' ? matchRaw : String(matchRaw || '');
const matchId = matchToLink.trim();
if (matchId && created?.id) {
await putArticleMatchLink(created.id, { external_match_id: matchId, title: (editing as any)?.title || '' });
setLinkedMatchId(matchId);
toast({
title: 'Článek vytvořen a propojen se zápasem',
description: `Match ID: ${matchId}`,
status: 'success',
duration: 3000,
isClosable: true
});
} else {
toast({
title: 'Článek byl úspěšně vytvořen',
status: 'success',
duration: 3000,
isClosable: true
});
}
// Clear temporary storage after successful creation
setTempMatchLink('');
} finally {
qc.invalidateQueries({ queryKey: ['admin-articles'] });
qc.invalidateQueries({ queryKey: ['articles'] });
qc.invalidateQueries({ queryKey: ['recentArticles'] });
qc.invalidateQueries({ queryKey: ['article-match-link'] }); // Invalidate match links
closeModal();
}
console.log('Article created successfully in mutation callback:', created);
// Note: Match linking is now handled in onSubmit() to avoid race conditions
// Clear temporary storage
setTempMatchLink('');
setMatchIdInput('');
// Invalidate queries to refresh the list
qc.invalidateQueries({ queryKey: ['admin-articles'] });
qc.invalidateQueries({ queryKey: ['articles'] });
qc.invalidateQueries({ queryKey: ['recentArticles'] });
qc.invalidateQueries({ queryKey: ['article-match-link'] }); // Invalidate match links
// Don't close modal here - let onSubmit handle it after match linking
},
onError: (e: any) => {
console.error('Error creating article:', e);
@@ -702,18 +689,16 @@ const ArticlesAdminPage = () => {
updateArticle(id, payload),
onSuccess: (_, variables) => {
const articleId = variables.id;
toast({
title: 'Článek byl úspěšně aktualizován',
status: 'success',
duration: 3000,
isClosable: true
});
console.log('Article updated successfully in mutation callback:', articleId);
// Invalidate queries to refresh the list
qc.invalidateQueries({ queryKey: ['admin-articles'] });
qc.invalidateQueries({ queryKey: ['articles'] });
qc.invalidateQueries({ queryKey: ['recentArticles'] });
qc.invalidateQueries({ queryKey: ['article-match-link', articleId] }); // Invalidate specific match link
qc.invalidateQueries({ queryKey: ['article', `id:${articleId}`] }); // Invalidate article detail
closeModal();
// Success toast and modal closing handled in onSubmit()
},
onError: (e: any) => {
console.error('Error updating article:', e);
@@ -864,7 +849,7 @@ const ArticlesAdminPage = () => {
} catch { return undefined; }
};
const onSubmit = async () => {
const onSubmit = async (options: { keepOpen?: boolean } = {}) => {
if (!editing) return;
// Require category selection by name (kategorie je povinná)
if (!String((editing as any)?.category_name || '').trim()) {
@@ -932,10 +917,83 @@ const ArticlesAdminPage = () => {
// Log the payload for debugging
console.log('Saving article with payload:', JSON.stringify(payload, null, 2));
// Debug: Log match link state before submission
console.log('Match link state before submit:', {
tempMatchLink,
matchIdInput,
linkedMatchId,
isNewArticle: !(editing as any)?.id
});
if ((editing as any)?.id) {
// Update existing article
await updateMut.mutateAsync({ id: (editing as any).id, payload });
// Handle match linking for existing articles (update or delete)
const matchRaw = matchIdInput || linkedMatchId;
const matchId = String(matchRaw || '').trim();
let matchLinked = false;
if (matchId) {
try {
await putArticleMatchLink((editing as any).id, { external_match_id: matchId, title: editing.title || '' });
console.log('Match link updated for existing article');
matchLinked = true;
} catch (err: any) {
console.error('Failed to update match link:', err);
}
}
// Show success message
toast({
title: matchLinked ? 'Článek aktualizován a propojen se zápasem' : 'Článek byl úspěšně aktualizován',
status: 'success',
duration: 3000,
isClosable: true
});
} else {
await createMut.mutateAsync(payload);
// Create new article
const created = await createMut.mutateAsync(payload);
// Handle match linking for new articles
const matchRaw = tempMatchLink || matchIdInput;
const matchId = String(matchRaw || '').trim();
if (matchId && created?.id) {
console.log('Linking new article', created.id, 'with match', matchId);
try {
await putArticleMatchLink(created.id, { external_match_id: matchId, title: editing.title || '' });
console.log('Match link created for new article');
setLinkedMatchId(matchId);
toast({
title: 'Článek vytvořen a propojen se zápasem',
description: `Match ID: ${matchId}`,
status: 'success',
duration: 3000,
isClosable: true
});
} catch (err: any) {
console.error('Failed to link match:', err);
toast({
title: 'Článek vytvořen, ale propojení se zápasem selhalo',
description: err?.response?.data?.error || err?.message || 'Zkuste propojit zápas ručně',
status: 'warning',
duration: 5000,
isClosable: true
});
}
} else if (created?.id) {
// No match to link, just show success
toast({
title: 'Článek byl úspěšně vytvořen',
status: 'success',
duration: 3000,
isClosable: true
});
}
}
// Close modal after successful save (unless keepOpen is true)
if (!options.keepOpen) {
closeModal();
}
} catch (error: any) {
@@ -1781,13 +1839,30 @@ const ArticlesAdminPage = () => {
qc.invalidateQueries({ queryKey: ['linked-polls'] });
}} />
) : (
<Alert status="info" borderRadius="md">
<Alert status="warning" borderRadius="md">
<AlertIcon />
<VStack align="start" spacing={1}>
<Text fontWeight="semibold">Nejprve uložte článek</Text>
<VStack align="start" spacing={2}>
<Text fontWeight="semibold">Článek ještě není uložen</Text>
<Text fontSize="sm">
Pro vytvoření nebo propojení ankety nejprve uložte článek tlačítkem "Uložit" níže. Poté se vrátíte do úprav a budete moci přidat ankety.
Pro propojení anket s článkem musíte nejprve článek uložit. Klikněte na "Uložit" níže - článek se uloží jako koncept a poté budete moci přidat ankety.
</Text>
<Button
size="sm"
colorScheme="blue"
onClick={async () => {
// Save article as draft first, keep modal open
try {
await onSubmit({ keepOpen: true });
// Switch to poll tab after save
setActiveTabIndex(5); // Poll tab is index 5
} catch (error) {
// Error is handled by onSubmit
}
}}
isLoading={createMut.isLoading}
>
Uložit jako koncept a přidat ankety
</Button>
</VStack>
</Alert>
)}
@@ -1798,7 +1873,7 @@ const ArticlesAdminPage = () => {
</ModalBody>
<ModalFooter>
<Button variant="ghost" mr={3} onClick={closeModal}>Zrušit</Button>
<Button colorScheme="blue" onClick={onSubmit} isLoading={createMut.isLoading || updateMut.isLoading}>Uložit</Button>
<Button colorScheme="blue" onClick={() => onSubmit()} isLoading={createMut.isLoading || updateMut.isLoading}>Uložit</Button>
</ModalFooter>
</ModalContent>
</Modal>