147 lines
4.1 KiB
TypeScript
147 lines
4.1 KiB
TypeScript
import React, { useEffect } from "react";
|
||
import {
|
||
FormControl,
|
||
Select,
|
||
MenuItem,
|
||
SelectChangeEvent,
|
||
Typography,
|
||
Box,
|
||
keyframes,
|
||
} from "@mui/material";
|
||
import { observer } from "mobx-react-lite";
|
||
import { authStore, cityStore, selectedCityStore, snapshotStore, type City } from "@shared";
|
||
import { MapPin } from "lucide-react";
|
||
|
||
const borderSpin = keyframes`
|
||
0% {
|
||
background-position: 0% 50%;
|
||
}
|
||
100% {
|
||
background-position: 200% 50%;
|
||
}
|
||
`;
|
||
|
||
export const CitySelector: React.FC = observer(() => {
|
||
const { selectedCity, setSelectedCity, isLocked } = selectedCityStore;
|
||
const canLoadAllCities = authStore.isAdmin && authStore.canRead("cities");
|
||
|
||
useEffect(() => {
|
||
if (canLoadAllCities) {
|
||
cityStore.getCities("ru");
|
||
return;
|
||
}
|
||
authStore.fetchMeCities().catch(() => undefined);
|
||
}, [canLoadAllCities]);
|
||
|
||
const baseCities: City[] = canLoadAllCities
|
||
? cityStore.cities["ru"].data
|
||
: authStore.meCities["ru"].map((uc) => ({
|
||
id: uc.city_id,
|
||
name: uc.name,
|
||
country: "",
|
||
country_code: "",
|
||
arms: "",
|
||
}));
|
||
|
||
const currentCities: City[] = selectedCity?.id
|
||
? (() => {
|
||
const exists = baseCities.some((city) => city.id === selectedCity.id);
|
||
if (exists) {
|
||
return baseCities;
|
||
}
|
||
return [selectedCity, ...baseCities];
|
||
})()
|
||
: baseCities;
|
||
|
||
const noCitySelected = !selectedCity?.id;
|
||
|
||
const handleCityChange = (event: SelectChangeEvent<string>) => {
|
||
const cityId = event.target.value;
|
||
if (cityId === "") {
|
||
snapshotStore.clearStoreCache();
|
||
setSelectedCity(null);
|
||
return;
|
||
}
|
||
|
||
const city = currentCities.find((c) => c.id === Number(cityId));
|
||
if (city) {
|
||
snapshotStore.clearStoreCache();
|
||
setSelectedCity(city);
|
||
}
|
||
};
|
||
|
||
const selectElement = (
|
||
<Select
|
||
value={selectedCity?.id?.toString() || ""}
|
||
onChange={handleCityChange}
|
||
displayEmpty
|
||
disabled={isLocked}
|
||
sx={{
|
||
height: "40px",
|
||
color: "white",
|
||
borderRadius: "4px",
|
||
...(noCitySelected && !isLocked
|
||
? {
|
||
backgroundColor: "#48989f",
|
||
"& .MuiOutlinedInput-notchedOutline": { border: "none" },
|
||
}
|
||
: {
|
||
"& .MuiOutlinedInput-notchedOutline": {
|
||
borderColor: isLocked
|
||
? "rgba(255, 255, 255, 0.1)"
|
||
: "rgba(255, 255, 255, 0.3)",
|
||
},
|
||
}),
|
||
"&.Mui-disabled": {
|
||
color: "rgba(255, 255, 255, 0.5)",
|
||
WebkitTextFillColor: "rgba(255, 255, 255, 0.5)",
|
||
},
|
||
"&:hover .MuiOutlinedInput-notchedOutline": {
|
||
borderColor: isLocked
|
||
? "rgba(255, 255, 255, 0.1)"
|
||
: "rgba(255, 255, 255, 0.5)",
|
||
},
|
||
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
|
||
borderColor: "white",
|
||
},
|
||
"& .MuiSvgIcon-root": {
|
||
color: isLocked ? "rgba(255, 255, 255, 0.3)" : "white",
|
||
},
|
||
}}
|
||
>
|
||
<MenuItem value="">
|
||
<Typography variant="body2">Выберите город</Typography>
|
||
</MenuItem>
|
||
{currentCities.map((city) => (
|
||
<MenuItem key={city.id} value={city.id?.toString()}>
|
||
<Typography variant="body2">{city.name}</Typography>
|
||
</MenuItem>
|
||
))}
|
||
</Select>
|
||
);
|
||
|
||
return (
|
||
<Box className="flex items-center gap-2">
|
||
<MapPin size={16} className={isLocked ? "text-gray-400" : "text-white"} />
|
||
<FormControl size="medium" sx={{ minWidth: 120 }}>
|
||
{noCitySelected && !isLocked ? (
|
||
<Box
|
||
sx={{
|
||
position: "relative",
|
||
borderRadius: "4px",
|
||
padding: "2px",
|
||
background: "linear-gradient(90deg, rgba(255,255,255,0.1), rgba(255,255,255,0.7), rgba(255,255,255,0.1), rgba(255,255,255,0.7), rgba(255,255,255,0.1))",
|
||
backgroundSize: "200% 100%",
|
||
animation: `${borderSpin} 2.5s linear infinite`,
|
||
}}
|
||
>
|
||
{selectElement}
|
||
</Box>
|
||
) : (
|
||
selectElement
|
||
)}
|
||
</FormControl>
|
||
</Box>
|
||
);
|
||
});
|