2026-08-15 18:42:06 +03:00

263 lines
11 KiB
TypeScript

'use client';
import React from 'react';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Box,
Container,
Divider,
Grid2,
Link,
List,
ListItem,
Paper,
Typography,
Theme,
} from '@mui/material';
import { styled } from '@mui/material/styles';
import { useTheme } from '@mui/material/styles';
import { ArrowBack, ExpandMore } from '@mui/icons-material';
import { IconUrl } from '@/src/core/api';
import { IMod } from '@/src/types/Imod';
import { Irace } from '@/src/types/Irace';
import '@/src/css/Unit.css';
import { IRaceUnits, IUnitShort } from '@/src/types/IUnitShort';
import { IBuildingShort, IRaceBuildings } from '@/src/types/IBuildingShort';
import { StyledLink } from '@/src/commons/StyledLink';
import { BackButton } from '@/src/commons/BackButton';
const SectionTitle = styled(Typography)(({ theme }) => ({
fontWeight: 700,
mb: 2,
fontSize: '1.25rem',
}));
const UnitCard = styled(Paper)(({ theme }) => ({
background: theme.palette.mode === 'dark'
? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)'
: 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)',
border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`,
borderRadius: '16px',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
boxShadow: theme.palette.mode === 'dark' ? '0 20px 40px rgba(255, 255, 255, 0.1)' : '0 20px 40px rgba(0, 0, 0, 0.2)',
borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)',
},
}));
const BuildingCard = styled(UnitCard)(({ theme }) => ({
padding: theme.spacing(1),
}));
const UnitLink = styled(Link)(({ theme }) => ({
color: theme.palette.text.primary,
textDecoration: 'none',
display: 'block',
'&:hover': { color: theme.palette.mode === 'dark' ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)' }
}));
const LoadingText = styled(Typography)(({ theme }) => ({
color: theme.palette.text.secondary,
}));
function Unit(unit: IUnitShort, modId: number, raceId: string, theme: Theme) { const isDark = theme.palette.mode === 'dark';
return (
<UnitLink href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}>
<ListItem sx={{
color: theme.palette.text.primary,
padding: '1.5rem 2rem',
marginBottom: '0.5rem',
background: isDark
? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)'
: 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)',
borderRadius: '16px',
border: `1px solid ${isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`,
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
boxShadow: isDark ? '0 20px 40px rgba(255, 255, 255, 0.1)' : '0 20px 40px rgba(0, 0, 0, 0.2)',
borderColor: isDark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)',
}
}}>
{unit.icon && <img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')} />}
{unit.name}
{unit.canDetect && <span>&nbsp;<img style={{ verticalAlign: "top" }}
src="/images/DETECT_YES.webp" /></span>}
</ListItem>
</UnitLink>
)
}
function UnitSmall(unit: IUnitShort, modId: number, raceId: string) {
var unitName = ""
if (unit.name.length > 21) {
unitName = unit.name.substring(0, 19) + "...";
} else {
unitName = unit.name;
}
return (<StyledLink href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id} >
{unit.icon && <img className="unitIconSmall" src={IconUrl + unit.icon.replaceAll('\\', '/')} />}
&nbsp;<span style={{ fontSize: 14 }}>{unitName}</span>
{unit.canDetect && <span>&nbsp;<img
src="/images/DETECT_YES.webp" /></span>}<br /></StyledLink>)
}
function Building(building: IBuildingShort, modId: number, raceId: string, theme: Theme) {
const isDark = theme.palette.mode === 'dark';
return (<Grid2 size={{ xs: 12, md: 3 }}>
<Link href={"/mod/" + modId + "/race/" + raceId + "/building/" + building.id} sx={{
textDecoration: 'none',
display: 'block',
'&:hover': { color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)' }
}}>
<BuildingCard elevation={0}>
<ListItem sx={{}}>
{building.icon && <img className="unitIcon" src={IconUrl + building.icon.replaceAll('\\', '/')} />}
&nbsp;<StyledLink>{building.name}</StyledLink>
{building.canDetect && <span>&nbsp;<img style={{ verticalAlign: "top" }}
src="/images/DETECT_YES.webp" /></span>}
</ListItem>
{building.units.map(unit => UnitSmall(unit, modId, raceId))}
</BuildingCard>
</Link>
</Grid2>)
}
interface UnitsProps {
raceId: string;
modId: number;
units: IRaceUnits | null;
buildings: IRaceBuildings | null;
}
function Units({ raceId, modId, units, buildings }: UnitsProps) {
const theme = useTheme();
const isDark = theme.palette.mode === 'dark';
if (units) {
const accordionSx = isDark ? {
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
border: '1px solid rgba(255, 255, 255, 0.1)',
borderRadius: '12px',
'&:before': { display: 'none' },
'&.Mui-expanded': { margin: '0 0 8px 0' },
} : {
background: 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)',
border: '1px solid rgba(0, 0, 0, 0.1)',
borderRadius: '12px',
'&:before': { display: 'none' },
'&.Mui-expanded': { margin: '0 0 8px 0' },
};
return (buildings != null ?
<Box>
<Grid2 container spacing={2}>
{buildings.buildings.map(building => Building(building, modId, raceId, theme))}
</Grid2>
{buildings.buildingsAdvanced.length > 0 && <Box sx={{ mt: 3 }}>
<Divider sx={{ my: 2, borderColor: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)' }} />
<SectionTitle>Advanced buildings</SectionTitle>
<Grid2 container spacing={2}>
{buildings.buildingsAdvanced.map(building => Building(building, modId, raceId, theme))}
</Grid2><br /></Box>}
<Box sx={{ mt: 3 }}>
<Accordion sx={accordionSx}>
<AccordionSummary
expandIcon={<ExpandMore sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)' }} />}
aria-controls="units-accordion"
id="units-accordion"
sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', '& .MuiAccordionSummary-content': { margin: '12px 0' }, '& .MuiAccordionSummary-content.Mui-expanded': { margin: '12px 0' } }}
>
<SectionTitle sx={{ mb: 0, display: 'flex', alignItems: 'center' }}>All units</SectionTitle>
</AccordionSummary>
<AccordionDetails>
<Grid2 container spacing={2}>
<Grid2 size={{ xs: 12, md: 4 }}>
<Typography variant="h6" sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', fontWeight: 600, mb: 2 }}>Infantry</Typography>
{units.infantry.map(unit => Unit(unit, modId, raceId, theme))}
</Grid2>
<Grid2 size={{ xs: 12, md: 4 }}>
<Typography variant="h6" sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', fontWeight: 600, mb: 2 }}>Tech</Typography>
<List>
{units.tech.map(unit => Unit(unit, modId, raceId, theme))}
</List>
</Grid2>
<Grid2 size={{ xs: 12, md: 4 }}>
<Typography variant="h6" sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', fontWeight: 600, mb: 2 }}>Support</Typography>
<List>
{units.support.map(unit => Unit(unit, modId, raceId, theme))}
</List>
</Grid2>
</Grid2>
</AccordionDetails>
</Accordion>
</Box>
</Box> : <LoadingText>Loading</LoadingText>
)
} else {
return <LoadingText>Loading...</LoadingText>;
}
}
interface RacePageClientProps {
initialMod: IMod | null;
initialRace: Irace | null;
raceUnits: IRaceUnits | null;
raceBuildings: IRaceBuildings | null;
}
export default function RacePageClient({ initialMod, initialRace, raceUnits, raceBuildings }: RacePageClientProps) {
const theme = useTheme();
if (initialMod != null && initialRace != null) {
const backRef = "/mod/" + initialMod.id;
const isDark = theme.palette.mode === 'dark';
return (
<Container maxWidth="lg">
<BackButton
variant="outlined"
startIcon={<ArrowBack />}
href={backRef}
>
Back to mod
</BackButton>
<Box sx={{ mb: 4 }}>
<Typography variant="h3" component="h1" sx={{
fontWeight: 800,
mb: 1,
background: isDark
? 'linear-gradient(135deg, #dee2e6 0%, #e0e0e0 100%)'
: 'none',
WebkitBackgroundClip: isDark ? 'text' : 'initial',
WebkitTextFillColor: isDark ? '#dee2e6' : 'initial',
color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)',
}}>
{initialRace.name}
</Typography>
<Typography variant="h6" sx={{
color: theme.palette.text.secondary,
fontWeight: 500,
}}>
{initialMod.name} ({initialMod.version})
</Typography>
</Box>
<Units raceId={initialRace.id} modId={initialMod.id} units={raceUnits} buildings={raceBuildings} />
</Container>
);
} else {
return (
<Container maxWidth="lg">
<LoadingText variant="h5" sx={{ py: 8, textAlign: 'center' }}>
Race not found
</LoadingText>
</Container>
);
}
}