mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-05 03:02:56 +00:00
dev day #79
This commit is contained in:
@@ -33,9 +33,10 @@ import { useSettings } from '../hooks/useSettings';
|
||||
import MainLayout from '../components/layout/MainLayout';
|
||||
import { FiMail, FiPhone, FiMapPin } from 'react-icons/fi';
|
||||
import { trackContactSubmit, trackFormSubmit } from '../utils/umami';
|
||||
import SponsorsSection from '../components/common/SponsorsSection';
|
||||
import ContactMap from '../components/home/ContactMap';
|
||||
import { getPublicContacts, GroupedContacts } from '../services/contactInfo';
|
||||
import { facrApi } from '../services/facr/facrApi';
|
||||
import { getCompetitionAliasesPublic } from '../services/competitionAliases';
|
||||
|
||||
type ContactFormData = {
|
||||
name: string;
|
||||
@@ -56,6 +57,9 @@ const ContactPage: React.FC = () => {
|
||||
// Public contacts (grouped by category)
|
||||
const [contactsData, setContactsData] = useState<GroupedContacts | null>(null);
|
||||
const [contactsLoading, setContactsLoading] = useState(true);
|
||||
// Club competitions (for tabs fallback) and aliases map
|
||||
const [competitions, setCompetitions] = useState<Array<{ code?: string; name: string }>>([]);
|
||||
const [aliasesMap, setAliasesMap] = useState<Record<string, string>>({});
|
||||
|
||||
const {
|
||||
register,
|
||||
@@ -121,6 +125,32 @@ const ContactPage: React.FC = () => {
|
||||
return () => { mounted = false; };
|
||||
}, []);
|
||||
|
||||
// Load club competitions + aliases (used to populate tabs when no contact categories are defined)
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const clubId = (settings as any)?.club_id || '';
|
||||
const clubType = ((settings as any)?.club_type || 'football') as 'football' | 'futsal';
|
||||
let comps: Array<{ code?: string; name: string }> = [];
|
||||
if (clubId) {
|
||||
try {
|
||||
const club = await facrApi.getClub(String(clubId), clubType);
|
||||
const arr = Array.isArray((club as any)?.competitions) ? (club as any).competitions : [];
|
||||
arr.forEach((c: any) => comps.push({ code: c.code, name: c.name || c.code }));
|
||||
} catch {}
|
||||
}
|
||||
let amap: Record<string, string> = {};
|
||||
try {
|
||||
const list = await getCompetitionAliasesPublic();
|
||||
list.forEach((a) => { if (a.code && a.alias) amap[a.code] = a.alias; });
|
||||
} catch {}
|
||||
const withAliases = comps.map((c) => ({ code: c.code, name: (c.code && amap[c.code]) ? amap[c.code] : c.name }));
|
||||
setAliasesMap(amap);
|
||||
setCompetitions(withAliases);
|
||||
} catch {}
|
||||
})();
|
||||
}, [settings]);
|
||||
|
||||
const onSubmit = (data: ContactFormData) => {
|
||||
mutate(data);
|
||||
};
|
||||
@@ -222,91 +252,148 @@ const ContactPage: React.FC = () => {
|
||||
{hasContacts && (
|
||||
<Box bg={bgColor} p={4} borderRadius="lg" borderWidth="1px" borderColor={borderColor} boxShadow="sm">
|
||||
<Heading size="md" mb={3}>Kontaktní osoby</Heading>
|
||||
<Tabs colorScheme="blue" isFitted>
|
||||
<TabList>
|
||||
{categories.map(([name]) => (
|
||||
<Tab key={name}>{name}</Tab>
|
||||
))}
|
||||
{uncategorized.length > 0 && <Tab>Ostatní</Tab>}
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
{categories.map(([name, persons]) => (
|
||||
<TabPanel key={name} pt={4}>
|
||||
<SimpleGrid columns={{ base: 1, sm: 2 }} spacing={4}>
|
||||
{persons.map((contact) => (
|
||||
<Box key={contact.id} bg={bgColor} p={4} borderRadius="md" borderWidth="1px" borderColor={borderColor}>
|
||||
<VStack align="start" spacing={3}>
|
||||
{contact.image_url && (
|
||||
<Avatar src={contact.image_url} name={contact.name} size="lg" />
|
||||
)}
|
||||
<Box>
|
||||
<Heading size="sm">{contact.name}</Heading>
|
||||
{contact.position && (
|
||||
<Badge colorScheme="blue" mt={1}>{contact.position}</Badge>
|
||||
)}
|
||||
</Box>
|
||||
{contact.description && (
|
||||
<Text fontSize="sm" color="gray.600">{contact.description}</Text>
|
||||
)}
|
||||
<VStack align="start" spacing={1}>
|
||||
{contact.email && (
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FiMail} color="blue.500" />
|
||||
<Link href={`mailto:${contact.email}`} color="blue.500" fontSize="sm">{contact.email}</Link>
|
||||
</HStack>
|
||||
)}
|
||||
{contact.phone && (
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FiPhone} color="blue.500" />
|
||||
<Link href={`tel:${contact.phone}`} color="blue.500" fontSize="sm">{contact.phone}</Link>
|
||||
</HStack>
|
||||
)}
|
||||
</VStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
<Tabs colorScheme="blue" isFitted isLazy>
|
||||
{(() => {
|
||||
const categoryEntries = Object.entries(contactsData?.categories || {});
|
||||
const compNames = competitions.map((c) => (c.code && aliasesMap[c.code]) ? aliasesMap[c.code] : c.name).filter(Boolean);
|
||||
const useCategories = categoryEntries.length > 0;
|
||||
const tabs = useCategories ? categoryEntries.map(([n]) => n) : compNames;
|
||||
const hasOthers = uncategorized.length > 0;
|
||||
return (
|
||||
<>
|
||||
<TabList>
|
||||
{tabs.map((n) => (
|
||||
<Tab key={n}>{n}</Tab>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</TabPanel>
|
||||
))}
|
||||
{uncategorized.length > 0 && (
|
||||
<TabPanel pt={4}>
|
||||
<SimpleGrid columns={{ base: 1, sm: 2 }} spacing={4}>
|
||||
{uncategorized.map((contact) => (
|
||||
<Box key={contact.id} bg={bgColor} p={4} borderRadius="md" borderWidth="1px" borderColor={borderColor}>
|
||||
<VStack align="start" spacing={3}>
|
||||
{contact.image_url && (
|
||||
<Avatar src={contact.image_url} name={contact.name} size="lg" />
|
||||
)}
|
||||
<Box>
|
||||
<Heading size="sm">{contact.name}</Heading>
|
||||
{contact.position && (
|
||||
<Badge colorScheme="blue" mt={1}>{contact.position}</Badge>
|
||||
)}
|
||||
</Box>
|
||||
{contact.description && (
|
||||
<Text fontSize="sm" color="gray.600">{contact.description}</Text>
|
||||
)}
|
||||
<VStack align="start" spacing={1}>
|
||||
{contact.email && (
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FiMail} color="blue.500" />
|
||||
<Link href={`mailto:${contact.email}`} color="blue.500" fontSize="sm">{contact.email}</Link>
|
||||
</HStack>
|
||||
)}
|
||||
{contact.phone && (
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FiPhone} color="blue.500" />
|
||||
<Link href={`tel:${contact.phone}`} color="blue.500" fontSize="sm">{contact.phone}</Link>
|
||||
</HStack>
|
||||
)}
|
||||
</VStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</TabPanel>
|
||||
)}
|
||||
</TabPanels>
|
||||
{hasOthers && <Tab>Ostatní</Tab>}
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
{useCategories
|
||||
? categoryEntries.map(([name, persons]) => (
|
||||
<TabPanel key={name} pt={4}>
|
||||
<SimpleGrid columns={{ base: 1, sm: 2 }} spacing={4}>
|
||||
{persons.map((contact) => (
|
||||
<Box key={contact.id} bg={bgColor} p={4} borderRadius="md" borderWidth="1px" borderColor={borderColor}>
|
||||
<VStack align="start" spacing={3}>
|
||||
{contact.image_url && (
|
||||
<Avatar src={contact.image_url} name={contact.name} size="lg" />
|
||||
)}
|
||||
<Box>
|
||||
<Heading size="sm">{contact.name}</Heading>
|
||||
{contact.position && (
|
||||
<Badge colorScheme="blue" mt={1}>{contact.position}</Badge>
|
||||
)}
|
||||
</Box>
|
||||
{contact.description && (
|
||||
<Text fontSize="sm" color="gray.600">{contact.description}</Text>
|
||||
)}
|
||||
<VStack align="start" spacing={1}>
|
||||
{contact.email && (
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FiMail} color="blue.500" />
|
||||
<Link href={`mailto:${contact.email}`} color="blue.500" fontSize="sm">{contact.email}</Link>
|
||||
</HStack>
|
||||
)}
|
||||
{contact.phone && (
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FiPhone} color="blue.500" />
|
||||
<Link href={`tel:${contact.phone}`} color="blue.500" fontSize="sm">{contact.phone}</Link>
|
||||
</HStack>
|
||||
)}
|
||||
</VStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</TabPanel>
|
||||
))
|
||||
: tabs.map((name) => {
|
||||
const persons = (contactsData?.categories || {})[name] || [];
|
||||
return (
|
||||
<TabPanel key={name} pt={4}>
|
||||
{persons.length > 0 ? (
|
||||
<SimpleGrid columns={{ base: 1, sm: 2 }} spacing={4}>
|
||||
{persons.map((contact) => (
|
||||
<Box key={contact.id} bg={bgColor} p={4} borderRadius="md" borderWidth="1px" borderColor={borderColor}>
|
||||
<VStack align="start" spacing={3}>
|
||||
{contact.image_url && (
|
||||
<Avatar src={contact.image_url} name={contact.name} size="lg" />
|
||||
)}
|
||||
<Box>
|
||||
<Heading size="sm">{contact.name}</Heading>
|
||||
{contact.position && (
|
||||
<Badge colorScheme="blue" mt={1}>{contact.position}</Badge>
|
||||
)}
|
||||
</Box>
|
||||
{contact.description && (
|
||||
<Text fontSize="sm" color="gray.600">{contact.description}</Text>
|
||||
)}
|
||||
<VStack align="start" spacing={1}>
|
||||
{contact.email && (
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FiMail} color="blue.500" />
|
||||
<Link href={`mailto:${contact.email}`} color="blue.500" fontSize="sm">{contact.email}</Link>
|
||||
</HStack>
|
||||
)}
|
||||
{contact.phone && (
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FiPhone} color="blue.500" />
|
||||
<Link href={`tel:${contact.phone}`} color="blue.500" fontSize="sm">{contact.phone}</Link>
|
||||
</HStack>
|
||||
)}
|
||||
</VStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
) : (
|
||||
<Text color="gray.500">Pro tuto kategorii zatím nemáme kontaktní osobu.</Text>
|
||||
)}
|
||||
</TabPanel>
|
||||
);
|
||||
})}
|
||||
{hasOthers && (
|
||||
<TabPanel pt={4}>
|
||||
<SimpleGrid columns={{ base: 1, sm: 2 }} spacing={4}>
|
||||
{uncategorized.map((contact) => (
|
||||
<Box key={contact.id} bg={bgColor} p={4} borderRadius="md" borderWidth="1px" borderColor={borderColor}>
|
||||
<VStack align="start" spacing={3}>
|
||||
{contact.image_url && (
|
||||
<Avatar src={contact.image_url} name={contact.name} size="lg" />
|
||||
)}
|
||||
<Box>
|
||||
<Heading size="sm">{contact.name}</Heading>
|
||||
{contact.position && (
|
||||
<Badge colorScheme="blue" mt={1}>{contact.position}</Badge>
|
||||
)}
|
||||
</Box>
|
||||
{contact.description && (
|
||||
<Text fontSize="sm" color="gray.600">{contact.description}</Text>
|
||||
)}
|
||||
<VStack align="start" spacing={1}>
|
||||
{contact.email && (
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FiMail} color="blue.500" />
|
||||
<Link href={`mailto:${contact.email}`} color="blue.500" fontSize="sm">{contact.email}</Link>
|
||||
</HStack>
|
||||
)}
|
||||
{contact.phone && (
|
||||
<HStack spacing={2}>
|
||||
<Icon as={FiPhone} color="blue.500" />
|
||||
<Link href={`tel:${contact.phone}`} color="blue.500" fontSize="sm">{contact.phone}</Link>
|
||||
</HStack>
|
||||
)}
|
||||
</VStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</TabPanel>
|
||||
)}
|
||||
</TabPanels>
|
||||
</>
|
||||
);
|
||||
})()}
|
||||
</Tabs>
|
||||
</Box>
|
||||
)}
|
||||
@@ -422,9 +509,6 @@ const ContactPage: React.FC = () => {
|
||||
</Box>
|
||||
</VStack>
|
||||
</Container>
|
||||
|
||||
{/* Sponsors Section */}
|
||||
<SponsorsSection />
|
||||
</MainLayout>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user