adaptive personal to mobile / fix calculator

This commit is contained in:
aurinex
2025-07-15 20:20:59 +05:00
parent b946f5ece0
commit f65d2554c1
3 changed files with 678 additions and 381 deletions

View File

@ -30,12 +30,12 @@ function CalculatorPage() {
// Состояния для полей формы
const [country, setCountry] = useState("russia"); // russia или belarus
const [age, setAge] = useState("3"); // Изменено на числовое значение для упрощения расчетов
const [age, setAge] = useState("<3"); // Изменено на числовое значение для упрощения расчетов
const [price, setPrice] = useState("");
const [currency, setCurrency] = useState("USD Доллар США");
const [engineType, setEngineType] = useState("Бензиновый");
const [hybridType, setHybridType] = useState("Не гибрид");
const [engineVolume, setEngineVolume] = useState("");
const [engineVolume, setEngineVolume] = useState("2000");
const [enginePower, setEnginePower] = useState("");
const [enginePowerUnit, setEnginePowerUnit] = useState("л.с.");
const [electricMotorPower, setElectricMotorPower] = useState("");
@ -79,225 +79,403 @@ function CalculatorPage() {
loadCurrencyRates();
}, []);
// const handleCalculate = () => {
// const calculatorResults = document.getElementById("calculator-results");
// if (calculatorResults) {
// calculatorResults.scrollIntoView({ behavior: "smooth" });
// }
// // Получаем курс выбранной валюты
// const currencyCode = mapCurrencyCodeToApiCode(currency);
// const exchangeRate =
// currencyCode === "RUB" ? 1 : getCurrencyRate(currencyRates, currencyCode);
// // Конвертация цены в рубли для расчета
// let priceInRub = parseFloat(price) || 0;
// // Конвертация из валюты пользователя в рубли
// if (currencyCode !== "RUB") {
// priceInRub *= exchangeRate;
// }
// // Минимальная стоимость для расчета (устанавливается таможней)
// const eurRate = getCurrencyRate(currencyRates, "EUR");
// const minCustomValue = 8500 * eurRate; // минимальная стоимость в рублях (8500 евро)
// const calculationValue = Math.max(priceInRub, minCustomValue);
// let customDuty = 0;
// let exciseTax = 0;
// let vat = 0;
// let utilityFee = 0;
// let totalFees = 0;
// // Расчет для физических лиц
// if (calculationType === "Физ. лица") {
// // Коэффициент для возраста
// let ageCoef = 1;
// switch (age) {
// case "3": // менее 3 лет
// ageCoef = 0.54; // 54% от стоимости
// break;
// case "3-5": // от 3 до 5 лет
// ageCoef = 0.48; // 48% от стоимости
// break;
// case "5+": // более 5 лет
// ageCoef = 0.48; // 48% от стоимости (но не менее 2.5 евро/см3)
// break;
// default:
// ageCoef = 0.54;
// }
// // Расчет пошлины для ДВС
// if (engineType === "Бензиновый" || engineType === "Дизельный") {
// const engineVolumeNum = parseFloat(engineVolume) || 0;
// let engineVolumeCoefficient = 1.0;
// if (age === "3-5") {
// if (engineVolumeNum <= 1000) {
// engineVolumeCoefficient = 1.5;
// } else if (engineVolumeNum <= 1500) {
// engineVolumeCoefficient = 1.7;
// } else if (engineVolumeNum <= 1800) {
// engineVolumeCoefficient = 2.5;
// } else if (engineVolumeNum <= 2300) {
// engineVolumeCoefficient = 2.7;
// } else if (engineVolumeNum <= 3000) {
// engineVolumeCoefficient = 3.0;
// } else if (engineVolumeNum > 3000) {
// engineVolumeCoefficient = 3.6;
// }
// }
// const engineVolumeDuty = engineVolumeCoefficient * engineVolumeNum * eurRate;
// // Для авто менее 3 лет
// if (age === "<3") {
// const percentDuty = calculationValue * 0.54;
// customDuty = Math.max(engineVolumeDuty, percentDuty);
// }
// // Для авто 3-5 лет
// else if (age === "3-5") {
// customDuty = engineVolumeDuty;
// }
// // Для авто старше 5 лет
// else if (age === "5-10" || age === ">10") {
// const ageCoefficient = 1.3; // Например, для 5-10 лет
// customDuty = engineVolumeCoefficient * engineVolumeNum * eurRate * ageCoefficient;
// }
// // Акцизы в зависимости от мощности
// const enginePowerNum = parseFloat(enginePower) || 0;
// let powerInHP = enginePowerNum;
// if (enginePowerUnit === "кВт") {
// powerInHP = enginePowerNum * 1.35962;
// }
// let ageCoefficient = 1.0;
// if (age === "3-5") {
// ageCoefficient = 1.1;
// } else if (age === "5-10") {
// ageCoefficient = 1.3;
// } else if (age === ">10") {
// ageCoefficient = 1.5;
// }
// // Акцизные ставки на 2024 год (в рублях за 1 л.с.)
// if (powerInHP <= 90) {
// exciseTax = powerInHP * 0 * ageCoefficient;
// } else if (powerInHP <= 150) {
// exciseTax = powerInHP * 54 * ageCoefficient;
// } else if (powerInHP <= 200) {
// exciseTax = powerInHP * 714 * ageCoefficient;
// } else if (powerInHP <= 300) {
// exciseTax = powerInHP * 1218 * ageCoefficient;
// } else if (powerInHP <= 400) {
// exciseTax = powerInHP * 1260 * ageCoefficient;
// } else if (powerInHP <= 500) {
// exciseTax = powerInHP * 1302 * ageCoefficient;
// } else if (powerInHP > 500) {
// exciseTax = powerInHP * 1374 * ageCoefficient;
// }
// }
// // Для электромобилей
// else if (engineType === "Электрический") {
// customDuty = 0; // Пошлина 0% для электромобилей
// exciseTax = 0; // Акциз 0 для электромобилей
// }
// // Для гибридов
// else if (engineType === "Гибридный") {
// // Для гибридов применяются те же ставки, что и для ДВС
// const engineVolumeNum = parseFloat(engineVolume) || 0;
// if (age === "3") {
// customDuty = calculationValue * 0.54;
// } else if (age === "3-5") {
// customDuty = calculationValue * 0.48;
// } else {
// const dutyPerCm3 = 2.5; // евро за см3
// const dutyInEur = engineVolumeNum * dutyPerCm3;
// customDuty = dutyInEur * eurRate;
// customDuty = Math.max(customDuty, calculationValue * 0.48);
// }
// // Акциз для гибридов рассчитывается так же, как для ДВС
// const enginePowerNum = parseFloat(enginePower) || 0;
// let powerInHP = enginePowerNum;
// if (enginePowerUnit === "кВт") {
// powerInHP = enginePowerNum * 1.35962;
// }
// let ageCoefficient = 1.0;
// if (age === "3-5") {
// ageCoefficient = 1.1;
// } else if (age === "5-10") {
// ageCoefficient = 1.3;
// } else if (age === ">10") {
// ageCoefficient = 1.5;
// }
// if (powerInHP <= 90) {
// exciseTax = powerInHP * 0 * ageCoefficient;
// } else if (powerInHP <= 150) {
// exciseTax = powerInHP * 54 * ageCoefficient;
// } else if (powerInHP <= 200) {
// exciseTax = powerInHP * 714 * ageCoefficient;
// } else if (powerInHP <= 300) {
// exciseTax = powerInHP * 1218 * ageCoefficient;
// } else if (powerInHP <= 400) {
// exciseTax = powerInHP * 1260 * ageCoefficient;
// } else if (powerInHP <= 500) {
// exciseTax = powerInHP * 1302 * ageCoefficient;
// } else if (powerInHP > 500) {
// exciseTax = powerInHP * 1374 * ageCoefficient;
// }
// }
// // НДС 20% от (стоимость + пошлина + акциз)
// vat = (calculationValue + customDuty + exciseTax) * 0.2;
// // Утилизационный сбор
// if (engineType === "Электрический") {
// utilityFee = 3400; // Для электромобилей
// } else {
// utilityFee = enhancedPermeability ? 27000 : 3400; // Для внедорожников выше
// }
// }
// // Расчет для юридических лиц
// else {
// // Здесь должна быть другая логика расчета для юр.лиц
// // В данном примере используем упрощенный расчет, аналогичный физ.лицам
// customDuty = calculationValue * 0.12; // Примерная ставка для юр.лиц
// // Акцизы в зависимости от мощности
// const enginePowerNum = parseFloat(enginePower) || 0;
// let powerInHP = enginePowerNum;
// if (enginePowerUnit === "кВт") {
// powerInHP = enginePowerNum * 1.35962;
// }
// // Акцизные ставки для юр.лиц могут отличаться
// if (powerInHP <= 90) {
// exciseTax = powerInHP * 0;
// } else if (powerInHP <= 150) {
// exciseTax = powerInHP * 55;
// } else {
// exciseTax = powerInHP * 531;
// }
// // НДС 20%
// vat = (calculationValue + customDuty + exciseTax) * 0.2;
// // Утилизационный сбор для юр.лиц
// utilityFee = engineType === "Электрический" ? 3500 : 5000;
// }
// // Итоговая сумма таможенных платежей
// totalFees = customDuty + exciseTax + vat + utilityFee;
// // Сохраняем результаты расчетов
// const results = {
// priceInRub: priceInRub.toFixed(2),
// customDuty: customDuty.toFixed(2),
// exciseTax: exciseTax.toFixed(2),
// vat: vat.toFixed(2),
// utilityFee: utilityFee.toFixed(2),
// totalFees: totalFees.toFixed(2),
// totalWithCar: (priceInRub + totalFees).toFixed(2),
// };
// setCalculationResults(results);
// // После расчетов установить флаг анимации
// setSlideIn(true);
// // Выводим результаты также в консоль для отладки
// console.log({
// original: {
// price,
// currency,
// currencyRate: exchangeRate,
// country,
// age,
// engineType,
// hybridType,
// engineVolume,
// enginePower,
// enginePowerUnit,
// electricMotorPower,
// electricMotorPowerUnit,
// powerRatio,
// calculationType,
// enhancedPermeability,
// },
// calculation: results,
// });
// };
const handleCalculate = () => {
const calculatorResults = document.getElementById("calculator-results");
if (calculatorResults) {
calculatorResults.scrollIntoView({ behavior: "smooth" });
}
// Получаем курс выбранной валюты
const currencyCode = mapCurrencyCodeToApiCode(currency);
const exchangeRate =
currencyCode === "RUB" ? 1 : getCurrencyRate(currencyRates, currencyCode);
const calculatorResults = document.getElementById("calculator-results");
if (calculatorResults) {
calculatorResults.scrollIntoView({ behavior: "smooth" });
}
// Конвертация цены в рубли для расчета
let priceInRub = parseFloat(price) || 0;
// Получаем курс валют
const currencyCode = mapCurrencyCodeToApiCode(currency);
const exchangeRate = currencyCode === "RUB" ? 1 : getCurrencyRate(currencyRates, currencyCode);
const eurRate = getCurrencyRate(currencyRates, "EUR");
// Конвертация из валюты пользователя в рубли
if (currencyCode !== "RUB") {
priceInRub *= exchangeRate;
// Конвертация цены в рубли
let priceInRub = parseFloat(price) || 0;
if (currencyCode !== "RUB") {
priceInRub *= exchangeRate;
}
// Инициализация переменных
let customDuty = 0;
let exciseTax = 0;
let vat = 0;
let utilityFee = 0;
let totalFees = 0;
// Расчет только для физ. лиц
if (calculationType === "Физ. лица") {
const engineVolumeNum = parseFloat(engineVolume) || 0;
const enginePowerNum = parseFloat(enginePower) || 0;
let powerInHP = enginePowerUnit === "кВт" ? enginePowerNum * 1.35962 : enginePowerNum;
// Корректировка стоимости только для авто до 3 лет
let calculationValue = priceInRub;
if (age === "<3") {
const minCustomValue = 8500 * eurRate;
calculationValue = Math.max(priceInRub, minCustomValue);
}
// Минимальная стоимость для расчета (устанавливается таможней)
const eurRate = getCurrencyRate(currencyRates, "EUR");
const minCustomValue = 8500 * eurRate; // минимальная стоимость в рублях (8500 евро)
const calculationValue = Math.max(priceInRub, minCustomValue);
let customDuty = 0;
let exciseTax = 0;
let vat = 0;
let utilityFee = 0;
let totalFees = 0;
// Расчет для физических лиц
if (calculationType === "Физ. лица") {
// Коэффициент для возраста
let ageCoef = 1;
switch (age) {
case "3": // менее 3 лет
ageCoef = 0.54; // 54% от стоимости
break;
case "3-5": // от 3 до 5 лет
ageCoef = 0.48; // 48% от стоимости
break;
case "5+": // более 5 лет
ageCoef = 0.48; // 48% от стоимости (но не менее 2.5 евро/см3)
break;
default:
ageCoef = 0.54;
}
// Расчет пошлины для ДВС
if (engineType === "Бензиновый" || engineType === "Дизельный") {
const engineVolumeNum = parseFloat(engineVolume) || 0;
// Для авто менее 3 лет
if (age === "3") {
customDuty = calculationValue * 0.54; // 54% от стоимости
}
// Для авто 3-5 лет
else if (age === "3-5") {
customDuty = calculationValue * 0.48; // 48% от стоимости
}
// Для авто старше 5 лет
else {
const dutyPerCm3 = 2.5; // евро за см3
const dutyInEur = engineVolumeNum * dutyPerCm3;
customDuty = dutyInEur * eurRate; // переводим в рубли по курсу евро
// Берем максимум между процентом и фиксированной ставкой
customDuty = Math.max(customDuty, calculationValue * 0.48);
}
// Акцизы в зависимости от мощности
const enginePowerNum = parseFloat(enginePower) || 0;
let powerInHP = enginePowerNum;
if (enginePowerUnit === "кВт") {
powerInHP = enginePowerNum * 1.35962;
}
// Акцизные ставки на 2024 год (в рублях за 1 л.с.)
if (powerInHP <= 90) {
exciseTax = powerInHP * 0;
} else if (powerInHP <= 150) {
exciseTax = powerInHP * 53;
} else if (powerInHP <= 200) {
exciseTax = powerInHP * 511;
} else if (powerInHP <= 300) {
exciseTax = powerInHP * 836;
} else if (powerInHP <= 400) {
exciseTax = powerInHP * 1425;
} else {
exciseTax = powerInHP * 1475;
}
}
// Для электромобилей
else if (engineType === "Электрический") {
customDuty = 0; // Пошлина 0% для электромобилей
exciseTax = 0; // Акциз 0 для электромобилей
}
// Для гибридов
else if (engineType === "Гибридный") {
// Для гибридов применяются те же ставки, что и для ДВС
const engineVolumeNum = parseFloat(engineVolume) || 0;
if (age === "3") {
customDuty = calculationValue * 0.54;
} else if (age === "3-5") {
customDuty = calculationValue * 0.48;
} else {
const dutyPerCm3 = 2.5; // евро за см3
const dutyInEur = engineVolumeNum * dutyPerCm3;
customDuty = dutyInEur * eurRate;
customDuty = Math.max(customDuty, calculationValue * 0.48);
}
// Акциз для гибридов рассчитывается так же, как для ДВС
const enginePowerNum = parseFloat(enginePower) || 0;
let powerInHP = enginePowerNum;
if (enginePowerUnit === "кВт") {
powerInHP = enginePowerNum * 1.35962;
}
if (powerInHP <= 90) {
exciseTax = powerInHP * 0;
} else if (powerInHP <= 150) {
exciseTax = powerInHP * 53;
} else if (powerInHP <= 200) {
exciseTax = powerInHP * 511;
} else if (powerInHP <= 300) {
exciseTax = powerInHP * 836;
} else if (powerInHP <= 400) {
exciseTax = powerInHP * 1425;
} else {
exciseTax = powerInHP * 1475;
}
}
// НДС 20% от (стоимость + пошлина + акциз)
vat = (calculationValue + customDuty + exciseTax) * 0.2;
// Утилизационный сбор
if (engineType === "Электрический") {
utilityFee = 3400; // Для электромобилей
} else {
utilityFee = enhancedPermeability ? 27000 : 3400; // Для внедорожников выше
}
}
// Расчет для юридических лиц
// Расчет пошлины
if (engineType === "Электрический") {
customDuty = 0;
}
else {
// Здесь должна быть другая логика расчета для юр.лиц
// В данном примере используем упрощенный расчет, аналогичный физ.лицам
customDuty = calculationValue * 0.12; // Примерная ставка для юр.лиц
// Акцизы в зависимости от мощности
const enginePowerNum = parseFloat(enginePower) || 0;
let powerInHP = enginePowerNum;
if (enginePowerUnit === "кВт") {
powerInHP = enginePowerNum * 1.35962;
// Для ДВС и гибридов
if (age === "<3") {
const percentDuty = priceInRub * 0.54;
const volumeDuty = engineVolumeNum * 2.5 * eurRate;
customDuty = Math.max(percentDuty, volumeDuty);
}
else if (age === "3-5") {
// Ставки для авто 3-5 лет
let rate;
if (engineVolumeNum <= 1000) rate = 1.5;
else if (engineVolumeNum <= 1500) rate = 1.7;
else if (engineVolumeNum <= 1800) rate = 2.5;
else if (engineVolumeNum <= 2300) rate = 2.7;
else if (engineVolumeNum <= 3000) rate = 3.0;
else rate = 3.6;
customDuty = engineVolumeNum * rate * eurRate;
}
else {
// Ставки для авто старше 5 лет
let rate;
if (engineVolumeNum <= 1000) rate = 3.0;
else if (engineVolumeNum <= 1500) rate = 3.2;
else if (engineVolumeNum <= 1800) rate = 3.5;
else if (engineVolumeNum <= 2300) rate = 4.8;
else if (engineVolumeNum <= 3000) rate = 5.0;
else rate = 5.7;
const ageCoef = age === "5-10" ? 1.3 : 1.5;
customDuty = engineVolumeNum * rate * eurRate * ageCoef;
}
// Акцизные ставки для юр.лиц могут отличаться
if (powerInHP <= 90) {
exciseTax = powerInHP * 0;
} else if (powerInHP <= 150) {
exciseTax = powerInHP * 55;
} else {
exciseTax = powerInHP * 531;
}
// НДС 20%
vat = (calculationValue + customDuty + exciseTax) * 0.2;
// Утилизационный сбор для юр.лиц
utilityFee = engineType === "Электрический" ? 3500 : 5000;
}
// Итоговая сумма таможенных платежей
totalFees = customDuty + exciseTax + vat + utilityFee;
// Расчет акциза (только для ДВС и гибридов)
if (engineType !== "Электрический") {
let ageCoefficient = 1.0;
if (age === "3-5") ageCoefficient = 1.1;
else if (age === "5-10") ageCoefficient = 1.3;
else if (age === ">10") ageCoefficient = 1.5;
// Сохраняем результаты расчетов
const results = {
priceInRub: priceInRub.toFixed(2),
customDuty: customDuty.toFixed(2),
exciseTax: exciseTax.toFixed(2),
vat: vat.toFixed(2),
utilityFee: utilityFee.toFixed(2),
totalFees: totalFees.toFixed(2),
totalWithCar: (priceInRub + totalFees).toFixed(2),
};
if (powerInHP <= 90) {
exciseTax = 0;
} else if (powerInHP <= 150) {
exciseTax = powerInHP * 54 * ageCoefficient;
} else if (powerInHP <= 200) {
exciseTax = powerInHP * 714 * ageCoefficient;
} else if (powerInHP <= 300) {
exciseTax = powerInHP * 1218 * ageCoefficient;
} else if (powerInHP <= 400) {
exciseTax = powerInHP * 1260 * ageCoefficient;
} else if (powerInHP <= 500) {
exciseTax = powerInHP * 1302 * ageCoefficient;
} else {
exciseTax = powerInHP * 1374 * ageCoefficient;
}
}
setCalculationResults(results);
// НДС 20%
vat = (calculationValue + customDuty + exciseTax) * 0.2;
// После расчетов установить флаг анимации
setSlideIn(true);
// Утилизационный сбор
utilityFee = engineType === "Электрический" ? 0 :
enhancedPermeability ? 27000 : 3400;
}
// Выводим результаты также в консоль для отладки
console.log({
original: {
price,
currency,
currencyRate: exchangeRate,
country,
age,
engineType,
hybridType,
engineVolume,
enginePower,
enginePowerUnit,
electricMotorPower,
electricMotorPowerUnit,
powerRatio,
calculationType,
enhancedPermeability,
},
calculation: results,
});
// Итоговые расчеты
totalFees = customDuty + exciseTax + vat + utilityFee;
// Округление до целых рублей
const round = (value) => Math.round(value);
// Сохранение результатов
const results = {
priceInRub: round(priceInRub),
customDuty: round(customDuty),
exciseTax: round(exciseTax),
vat: round(vat),
utilityFee: round(utilityFee),
totalFees: round(totalFees),
totalWithCar: round(priceInRub + totalFees),
};
setCalculationResults(results);
setSlideIn(true);
// Логирование для отладки
console.log({
input: {
price, currency, age, engineType,
engineVolume, enginePower, enginePowerUnit,
calculationType, enhancedPermeability
},
calculation: results,
rates: { eurRate, exchangeRate }
});
};
// Функция для получения символа валюты
const getCurrencySymbol = () => {
switch (currency) {
@ -538,9 +716,10 @@ function CalculatorPage() {
displayEmpty
sx={selectStyles}
>
<MenuItem value="3">Менее 3х лет</MenuItem>
<MenuItem value="<3">Менее 3х лет</MenuItem>
<MenuItem value="3-5">3-5 лет</MenuItem>
<MenuItem value="5+">5+ лет</MenuItem>
<MenuItem value="5-10">5-10 лет</MenuItem>
<MenuItem value=">10">10+ лет</MenuItem>
</Select>
</Box>

View File

@ -42,6 +42,7 @@ import PersonIcon from '@mui/icons-material/Person';
import BadgeIcon from '@mui/icons-material/Badge';
import WorkIcon from '@mui/icons-material/Work';
import { useAuth } from '../../context/AuthContext';
import { useResponsive } from "../../theme/useResponsive";
import {
fetchTeam,
createTeamMember,
@ -66,6 +67,7 @@ interface TeamResponse {
const Personal = () => {
const { token } = useAuth();
const { isMobile } = useResponsive();
const [staff, setStaff] = useState<TeamMember[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
@ -246,8 +248,8 @@ const Personal = () => {
component={Paper}
elevation={3}
sx={{
borderRadius: 2,
overflow: 'hidden'
borderRadius: isMobile ? "2vw" : "1vw",
overflow: isMobile ? "scroll" : "hidden"
}}
>
<Table>
@ -278,9 +280,9 @@ const Personal = () => {
src={getImageUrl(member.photo)}
alt={`${member.name} ${member.surname}`}
sx={{
width: 56,
height: 56,
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
width: isMobile ? "15vw" : 56,
height: isMobile ? "15vw" : 56,
boxShadow: '0 0.2vw 0.4vw rgba(0,0,0,0.1)'
}}
>
{getInitials(member.name, member.surname)}
@ -302,11 +304,13 @@ const Personal = () => {
onClick={() => handleOpenEditDialog(member)}
color="primary"
sx={{
bgcolor: 'rgba(25, 118, 210, 0.1)',
mr: 1,
'&:hover': {
bgcolor: 'rgba(25, 118, 210, 0.2)',
}
color: "black",
"&:hover": {
color: "#C27664",
bgcolor: "transparent",
},
transition: "all 0.2s ease",
mr: 1
}}
>
<EditIcon />
@ -317,10 +321,11 @@ const Personal = () => {
onClick={() => handleOpenDeleteDialog(member)}
color="error"
sx={{
bgcolor: 'rgba(211, 47, 47, 0.1)',
'&:hover': {
bgcolor: 'rgba(211, 47, 47, 0.2)',
}
"&:hover": {
color: "#C27664",
bgcolor: "transparent",
},
transition: "all 0.2s ease",
}}
>
<DeleteIcon />
@ -336,7 +341,7 @@ const Personal = () => {
// Рендер сетки карточек
const renderGrid = () => (
<Grid container spacing={3}>
<Grid container spacing={3} style={{ justifyContent: isMobile ? "center" : "auto" }}>
{filteredStaff.map((member) => (
<Grid item xs={12} sm={6} md={4} lg={3} key={member.id}>
<Card
@ -345,11 +350,12 @@ const Personal = () => {
height: '100%',
display: 'flex',
flexDirection: 'column',
borderRadius: 2,
transition: 'transform 0.3s, box-shadow 0.3s',
'&:hover': {
transform: 'translateY(-5px)',
boxShadow: '0 10px 20px rgba(0,0,0,0.1)'
borderRadius: isMobile ? "5vw" : "1vw",
transition: "transform 0.3s, box-shadow 0.3s",
boxShadow: "0 0.5vw 1vw rgba(0,0,0,0.15)",
"&:hover": {
transform: "translateY(-0.2vw)",
boxShadow: "0 0.5vw 1vw rgba(0,0,0,0.1)",
}
}}
>
@ -364,12 +370,19 @@ const Personal = () => {
left: 0,
width: '100%',
height: '100%',
objectFit: 'cover'
objectFit: 'cover',
userSelect: "none",
pointerEvents: "none",
}}
/>
</Box>
<CardContent sx={{ flexGrow: 1, pt: 2 }}>
<Typography gutterBottom variant="h6" component="div" sx={{ fontWeight: 'bold', mb: 0.5 }}>
<CardContent sx={{ flexGrow: 1, pt: isMobile ? "3vw" : "2vw" }}>
<Typography gutterBottom variant="h6" component="div" sx={{
fontWeight: "bold",
mb: "0.5vw",
fontFamily: "Unbounded",
fontSize: isMobile ? "4vw" : "1.2vw"
}}>
{member.name} {member.surname}
</Typography>
@ -380,21 +393,26 @@ const Personal = () => {
sx={{
bgcolor: '#e3f2fd',
color: '#1565c0',
mt: 1
mt: 1,
fontSize: isMobile ? "3vw" : "inherit"
}}
/>
</CardContent>
<Divider />
<CardActions sx={{ justifyContent: 'space-between', p: 1.5 }}>
<CardActions sx={{ justifyContent: 'space-between', p: isMobile ? "3vw" : "1.5vw" }}>
<Button
size="small"
startIcon={<EditIcon />}
onClick={() => handleOpenEditDialog(member)}
sx={{
color: '#1976d2',
'&:hover': { bgcolor: 'rgba(25, 118, 210, 0.1)' }
color: "black",
"&:hover": { color: "#C27664", bgcolor: "transparent" },
transition: "all 0.2s ease",
fontFamily: "Unbounded",
fontWeight: "bold",
fontSize: isMobile ? "4.5vw" : "0.8vw",
}}
>
Изменить
@ -404,8 +422,15 @@ const Personal = () => {
startIcon={<DeleteIcon />}
onClick={() => handleOpenDeleteDialog(member)}
sx={{
color: '#d32f2f',
'&:hover': { bgcolor: 'rgba(211, 47, 47, 0.1)' }
color: "rgb(214, 74, 74)",
"&:hover": {
color: "rgba(150, 0, 0, 1)",
bgcolor: "transparent",
},
transition: "all 0.2s ease",
fontFamily: "Unbounded",
fontWeight: "bold",
fontSize: isMobile ? "4.5vw" : "0.8vw",
}}
>
Удалить
@ -417,33 +442,57 @@ const Personal = () => {
</Grid>
);
// Общие стили для компонентов формы
const textFieldStyles = {
"& .MuiOutlinedInput-root": {
borderRadius: isMobile ? "5vw" : "1vw",
bgcolor: "white",
height: isMobile ? "8vw" : "4vw",
width: "100%",
border: "0.1vw solid #C27664",
fontFamily: "Unbounded",
fontSize: isMobile ? "4vw" : "1.2vw",
color: "#C27664",
mb: isMobile ? "3vw" : "0",
"& fieldset": {
border: "none",
},
},
};
return (
<Box sx={{ p: 3 }}>
<Box sx={{ p: isMobile ? "4vw" : "3vw" }}>
{/* Заголовок и кнопки управления */}
<Paper
elevation={3}
sx={{
p: 3,
mb: 3,
borderRadius: 2,
background: 'linear-gradient(to right, #2D2D2D, #3D3D3D)'
mt: isMobile ? "3vw" : "0",
p: isMobile ? "3vw" : "2vw",
mb: isMobile ? "5vw" : "2vw",
borderRadius: isMobile ? "5vw" : "1vw",
background: "linear-gradient(to right, #2D2D2D, #3D3D3D)"
}}
>
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
justifyContent: isMobile ? "center" : "space-between",
alignItems: 'center',
flexWrap: 'wrap',
gap: 2
gap: isMobile ? "1vw" : "2vw"
}}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<PersonIcon sx={{ fontSize: 40, color: '#C27664' }} />
<Typography variant="h5" sx={{ fontFamily: 'Unbounded', fontWeight: 'bold', color: 'white' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: isMobile ? "1vw" : "2vw" }}>
<PersonIcon sx={{ fontSize: isMobile ? "9vw" : "2vw", color: "#C27664" }} />
<Typography variant="h5" sx={{
fontFamily: "Unbounded",
fontWeight: "bold",
color: "white",
fontSize: isMobile ? "4vw" : "2vw"
}}>
Управление персоналом
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 2 }}>
<Box sx={{ display: 'flex', gap: "1vw" }}>
<Button
variant="contained"
onClick={handleOpenCreateDialog}
@ -453,8 +502,9 @@ const Personal = () => {
color: "white",
textTransform: "none",
fontFamily: "Unbounded",
borderRadius: 2,
px: 3,
borderRadius: isMobile ? "5vw" : "1vw",
margin: isMobile ? "2vw" : "0",
px: isMobile ? "5vw" : "2vw",
"&:hover": { bgcolor: "#945B4D" },
}}
>
@ -468,14 +518,15 @@ const Personal = () => {
<Paper
elevation={2}
sx={{
p: 2,
mb: 3,
p: isMobile ? "3.5vw" : "1.5vw",
mb: isMobile ? "5vw" : "2vw",
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
borderRadius: 2,
borderRadius: isMobile ? "5vw" : "1vw",
flexWrap: 'wrap',
gap: 2
gap: "1vw",
boxShadow: "0 0.5vw 1vw rgba(0,0,0,0.15)",
}}
>
<TextField
@ -486,8 +537,9 @@ const Personal = () => {
size="small"
sx={{
minWidth: 300,
'& .MuiOutlinedInput-root': {
borderRadius: 2
"& .MuiOutlinedInput-root": {
borderRadius: isMobile ? "5vw" : "1vw",
width: isMobile ? "95%" : "auto",
}
}}
InputProps={{
@ -499,8 +551,12 @@ const Personal = () => {
}}
/>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography variant="body2" color="text.secondary" sx={{ mr: 2 }}>
<Box sx={{ display: "flex", alignItems: "center", flexDirection: isMobile ? "column" : "row", width: isMobile ? "100%" : "auto", justifyContent: isMobile ? "center" : "auto" }}>
<Typography
variant="body2"
color="text.secondary"
sx={{ mr: isMobile ? "0" : "1vw", mt: isMobile ? "2vw" : "0", fontFamily: "Unbounded", fontSize: isMobile ? "5vw" : "1vw" }}
>
Всего: {totalStaff} сотрудников
</Typography>
@ -529,7 +585,7 @@ const Personal = () => {
{/* Сообщения об ошибках */}
{error && (
<Alert severity="error" sx={{ mb: 2, borderRadius: 2 }}>
<Alert severity="error" sx={{ mb: isMobile ? "5vw" : "1vw", borderRadius: isMobile ? "5vw" : "1vw", border: isMobile ? "0.1vw solid #C27664" : "none" }}>
{error}
</Alert>
)}
@ -537,10 +593,10 @@ const Personal = () => {
{/* Индикатор загрузки */}
{loading ? (
<Box sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
minHeight: 400
display: "flex",
justifyContent: "center",
alignItems: "center",
minHeight: "10vw"
}}>
<CircularProgress sx={{ color: '#C27664' }} />
</Box>
@ -548,16 +604,17 @@ const Personal = () => {
<Paper
elevation={2}
sx={{
p: 4,
textAlign: 'center',
borderRadius: 2,
bgcolor: '#f9f9f9'
p: "4vw",
textAlign: "center",
borderRadius: "1vw",
bgcolor: "#f9f9f9",
boxShadow: "0 0.5vw 1vw rgba(0,0,0,0.15)",
}}
>
<Typography variant="h6" color="text.secondary">
<Typography variant="h6" color="text.secondary" sx={{ fontFamily: "Unbounded" }}>
Сотрудники не найдены
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
<Typography variant="body2" color="text.secondary" sx={{ fontFamily: "Unbounded" }}>
Попробуйте изменить параметры поиска или добавьте нового сотрудника
</Typography>
</Paper>
@ -573,66 +630,75 @@ const Personal = () => {
maxWidth="md"
fullWidth
PaperProps={{
sx: { borderRadius: 2 }
sx: { borderRadius: isMobile ? "5vw" : "1vw" }
}}
>
<DialogTitle sx={{
fontFamily: 'Unbounded',
bgcolor: '#2D2D2D',
color: 'white',
display: 'flex',
alignItems: 'center',
gap: 1
fontFamily: "Unbounded",
bgcolor: "#2D2D2D",
color: "white",
display: "flex",
alignItems: "center",
gap: "1vw"
}}>
<AddIcon /> Добавить сотрудника
</DialogTitle>
<DialogContent sx={{ mt: 2 }}>
<DialogContent sx={{ mt: isMobile ? "3vw" : "1vw" }}>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Grid
item
xs={12}
md={6}
style={{
display: isMobile ? "block" : "grid",
gridTemplateColumns: isMobile ? "auto" : "repeat(1, 1fr)",
gap: "1vw",
}}
>
<TextField
name="name"
label="Имя"
placeholder="Имя"
value={formData.name}
onChange={handleInputChange}
fullWidth
margin="normal"
variant="outlined"
sx={textFieldStyles}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<PersonIcon color="action" />
<PersonIcon sx={{ color: "#C27664", fontSize: isMobile ? "5vw" : "2vw" }} />
</InputAdornment>
),
}}
/>
<TextField
name="surname"
label="Фамилия"
placeholder="Фамилия"
value={formData.surname}
onChange={handleInputChange}
fullWidth
margin="normal"
variant="outlined"
sx={textFieldStyles}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<BadgeIcon color="action" />
<BadgeIcon sx={{ color: "#C27664", fontSize: isMobile ? "5vw" : "2vw" }} />
</InputAdornment>
),
}}
/>
<TextField
name="role"
label="Должность"
placeholder="Должность"
value={formData.role}
onChange={handleInputChange}
fullWidth
margin="normal"
variant="outlined"
sx={textFieldStyles}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<WorkIcon color="action" />
<WorkIcon sx={{ color: "#C27664", fontSize: isMobile ? "5vw" : "2vw" }} />
</InputAdornment>
),
}}
@ -641,15 +707,16 @@ const Personal = () => {
variant="outlined"
component="label"
sx={{
mt: 2,
borderRadius: 2,
p: 1.5,
borderColor: '#C27664',
color: '#C27664',
'&:hover': {
borderColor: '#945B4D',
bgcolor: 'rgba(194, 118, 100, 0.1)'
}
borderRadius: isMobile ? "5vw" : "1vw",
p: isMobile ? "2vw" : "1vw",
borderColor: "#C27664",
fontFamily: "Unbounded",
color: "#C27664",
fontSize: isMobile ? "3vw" : "1.2vw",
"&:hover": {
borderColor: "#945B4D",
bgcolor: "rgba(194, 118, 100, 0.1)",
},
}}
fullWidth
>
@ -666,13 +733,14 @@ const Personal = () => {
<Paper
elevation={2}
sx={{
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 2,
overflow: 'hidden',
bgcolor: '#f5f5f5'
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "1vw",
overflow: "hidden",
bgcolor: "#2D2D2D",
boxShadow: "0 0.5vw 1vw rgba(0,0,0,0.15)",
}}
>
{photoPreview ? (
@ -681,14 +749,24 @@ const Personal = () => {
src={photoPreview}
alt="Предпросмотр"
sx={{
width: '100%',
height: '100%',
objectFit: 'contain',
p: 2
width: "100%",
height: "100%",
objectFit: "contain",
p: "1vw",
userSelect: "none",
pointerEvents: "none",
}}
/>
) : (
<Typography color="text.secondary" sx={{ p: 4, textAlign: 'center' }}>
<Typography
color="text.secondary"
sx={{
p: "3vw",
textAlign: "center",
color: "white",
fontFamily: "Unbounded",
}}
>
Предпросмотр фото
</Typography>
)}
@ -696,13 +774,16 @@ const Personal = () => {
</Grid>
</Grid>
</DialogContent>
<DialogActions sx={{ p: 2, bgcolor: '#f5f5f5' }}>
<DialogActions sx={{ p: "1vw", bgcolor: "#f5f5f5" }}>
<Button
onClick={handleCloseDialogs}
sx={{
color: '#666',
borderRadius: 2,
px: 3
color: "black",
fontFamily: "Unbounded",
borderRadius: "1vw",
px: "1vw",
"&:hover": { bgcolor: "transparent", color: "#C27664" },
transition: "all 0.2s ease",
}}
>
Отмена
@ -714,8 +795,9 @@ const Personal = () => {
sx={{
bgcolor: "#C27664",
color: "white",
borderRadius: 2,
px: 3,
fontFamily: "Unbounded",
borderRadius: isMobile ? "5vw" : "1vw",
px: isMobile ? "4vw" : "2vw",
"&:hover": { bgcolor: "#945B4D" },
"&.Mui-disabled": {
bgcolor: "rgba(194, 118, 100, 0.5)",
@ -734,66 +816,75 @@ const Personal = () => {
maxWidth="md"
fullWidth
PaperProps={{
sx: { borderRadius: 2 }
sx: { borderRadius: isMobile ? "5vw" : "1vw" }
}}
>
<DialogTitle sx={{
fontFamily: 'Unbounded',
bgcolor: '#2D2D2D',
color: 'white',
display: 'flex',
alignItems: 'center',
gap: 1
fontFamily: "Unbounded",
bgcolor: "#2D2D2D",
color: "white",
display: "flex",
alignItems: "center",
gap: "1vw"
}}>
<EditIcon /> Редактировать сотрудника
</DialogTitle>
<DialogContent sx={{ mt: 2 }}>
<DialogContent sx={{ mt: isMobile ? "3vw" : "1vw" }}>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Grid
item
xs={12}
md={6}
style={{
display: isMobile ? "block" : "grid",
gridTemplateColumns: isMobile ? "auto" : "repeat(1, 1fr)",
gap: "1vw",
}}
>
<TextField
name="name"
label="Имя"
placeholder="Имя"
value={formData.name}
onChange={handleInputChange}
fullWidth
margin="normal"
variant="outlined"
sx={textFieldStyles}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<PersonIcon color="action" />
<PersonIcon sx={{ color: "#C27664", fontSize: isMobile ? "5vw" : "2vw" }} />
</InputAdornment>
),
}}
/>
<TextField
name="surname"
label="Фамилия"
placeholder="Фамилия"
value={formData.surname}
onChange={handleInputChange}
fullWidth
margin="normal"
variant="outlined"
sx={textFieldStyles}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<BadgeIcon color="action" />
<BadgeIcon sx={{ color: "#C27664", fontSize: isMobile ? "5vw" : "2vw" }} />
</InputAdornment>
),
}}
/>
<TextField
name="role"
label="Должность"
placeholder="Должность"
value={formData.role}
onChange={handleInputChange}
fullWidth
margin="normal"
variant="outlined"
sx={textFieldStyles}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<WorkIcon color="action" />
<WorkIcon sx={{ color: "#C27664", fontSize: isMobile ? "5vw" : "2vw" }} />
</InputAdornment>
),
}}
@ -802,15 +893,18 @@ const Personal = () => {
variant="outlined"
component="label"
sx={{
mt: 2,
borderRadius: 2,
p: 1.5,
borderColor: '#C27664',
color: '#C27664',
'&:hover': {
borderColor: '#945B4D',
bgcolor: 'rgba(194, 118, 100, 0.1)'
}
borderRadius: isMobile ? "5vw" : "1vw",
p: isMobile ? "2vw" : "1vw",
borderColor: "#C27664",
fontFamily: "Unbounded",
color: "#C27664",
fontSize: isMobile ? "3vw" : "1.2vw",
"&:hover": {
borderColor: "#945B4D",
bgcolor: "#C27664",
color: "white",
},
transition: "all 0.2s ease",
}}
fullWidth
>
@ -827,13 +921,14 @@ const Personal = () => {
<Paper
elevation={2}
sx={{
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 2,
overflow: 'hidden',
bgcolor: '#f5f5f5'
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "1vw",
overflow: "hidden",
bgcolor: "#2D2D2D",
boxShadow: "0 0.5vw 1vw rgba(0,0,0,0.15)",
}}
>
{photoPreview ? (
@ -842,14 +937,24 @@ const Personal = () => {
src={photoPreview}
alt="Предпросмотр"
sx={{
width: '100%',
height: '100%',
objectFit: 'contain',
p: 2
width: "100%",
height: "100%",
objectFit: "contain",
p: "1vw",
userSelect: "none",
pointerEvents: "none",
}}
/>
) : (
<Typography color="text.secondary" sx={{ p: 4, textAlign: 'center' }}>
<Typography
color="text.secondary"
sx={{
p: "3vw",
textAlign: "center",
color: "white",
fontFamily: "Unbounded",
}}
>
Предпросмотр фото
</Typography>
)}
@ -857,13 +962,16 @@ const Personal = () => {
</Grid>
</Grid>
</DialogContent>
<DialogActions sx={{ p: 2, bgcolor: '#f5f5f5' }}>
<DialogActions sx={{ p: "1vw", bgcolor: "#f5f5f5" }}>
<Button
onClick={handleCloseDialogs}
sx={{
color: '#666',
borderRadius: 2,
px: 3
color: "black",
fontFamily: "Unbounded",
borderRadius: "1vw",
px: "1vw",
"&:hover": { bgcolor: "transparent", color: "#C27664" },
transition: "all 0.2s ease",
}}
>
Отмена
@ -875,8 +983,9 @@ const Personal = () => {
sx={{
bgcolor: "#C27664",
color: "white",
borderRadius: 2,
px: 3,
fontFamily: "Unbounded",
borderRadius: isMobile ? "5vw" : "1vw",
px: isMobile ? "4vw" : "2vw",
"&:hover": { bgcolor: "#945B4D" },
"&.Mui-disabled": {
bgcolor: "rgba(194, 118, 100, 0.5)",
@ -893,57 +1002,61 @@ const Personal = () => {
open={openDeleteDialog}
onClose={handleCloseDialogs}
PaperProps={{
sx: { borderRadius: 2 }
sx: { borderRadius: isMobile ? "5vw" : "1vw", maxWidth: isMobile ? "80vw" : "40vw", overflow: "hidden" }
}}
>
<DialogTitle sx={{
fontFamily: 'Unbounded',
bgcolor: '#2D2D2D',
color: 'white',
display: 'flex',
alignItems: 'center',
gap: 1
fontFamily: "Unbounded",
bgcolor: "#2D2D2D",
color: "white",
display: "flex",
alignItems: "center",
gap: "1vw"
}}>
<DeleteIcon /> Удалить сотрудника
</DialogTitle>
<DialogContent sx={{ mt: 2, minWidth: 400 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<DialogContent sx={{ mt: isMobile ? "3vw" : "1vw" }}>
<Box sx={{ display: "flex", alignItems: "center", mb: isMobile ? "3vw" : "1vw", flexDirection: isMobile ? "column" : "row" }}>
{selectedMember && (
<Avatar
src={getImageUrl(selectedMember.photo)}
alt={`${selectedMember.name} ${selectedMember.surname}`}
sx={{
width: 64,
height: 64,
mr: 2
width: isMobile ? "20vw" : "5vw",
height: isMobile ? "20vw" : "5vw",
mr: isMobile ? "0" : "2vw",
mb: isMobile ? "2vw" : "0",
}}
>
{selectedMember && getInitials(selectedMember.name, selectedMember.surname)}
</Avatar>
)}
<Box>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>
<Typography variant="h6" sx={{ fontWeight: "bold", fontFamily: "Unbounded" }}>
{selectedMember?.name} {selectedMember?.surname}
</Typography>
{selectedMember && (
<Typography variant="body2" color="text.secondary">
<Typography variant="body2" color="text.secondary" sx={{ fontFamily: "Unbounded" }}>
{selectedMember.role}
</Typography>
)}
</Box>
</Box>
<Alert severity="warning" sx={{ mt: 2 }}>
<Alert severity="warning" sx={{ mt: "1vw", fontFamily: "Unbounded", borderRadius: isMobile ? "5vw" : "1vw" }}>
Вы уверены, что хотите удалить этого сотрудника? Это действие нельзя отменить.
</Alert>
</DialogContent>
<DialogActions sx={{ p: 2, bgcolor: '#f5f5f5' }}>
<DialogActions sx={{ p: "1vw", bgcolor: "#f5f5f5" }}>
<Button
onClick={handleCloseDialogs}
sx={{
color: '#666',
borderRadius: 2,
px: 3
color: "black",
borderRadius: "1vw",
px: "1vw",
fontFamily: "Unbounded",
"&:hover": { bgcolor: "transparent", color: "#C27664" },
transition: "all 0.2s ease",
}}
>
Отмена
@ -953,8 +1066,13 @@ const Personal = () => {
variant="contained"
color="error"
sx={{
borderRadius: 2,
px: 3
borderRadius: isMobile ? "5vw" : "1vw",
px: isMobile ? "4vw" : "2vw",
fontFamily: "Unbounded",
bgcolor: "#C27664",
color: "white",
"&:hover": { bgcolor: "#945B4D" },
transition: "all 0.2s ease",
}}
>
Удалить

View File

@ -1877,7 +1877,7 @@ const Vehicle = () => {
>
<DeleteIcon /> Удалить автомобиль
</DialogTitle>
<DialogContent sx={{ mt: isMobile ? "3vw" : "1vw", minWidth: "40vw" }}>
<DialogContent sx={{ mt: isMobile ? "3vw" : "1vw" }}>
<Box sx={{ display: "flex", alignItems: "center", mb: isMobile ? "3vw" : "1vw", flexDirection: isMobile ? "column" : "row" }}>
{selectedCar && (
<Box