ntp&modercontent_front
This commit is contained in:
@@ -0,0 +1 @@
|
||||
VITE_NTP_SERVER=http://192.168.139.249:3001/api/time
|
||||
Generated
+22
-77
@@ -13,11 +13,12 @@
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"axios": "^1.6.0",
|
||||
"axios": "^1.19.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"dompurify": "^3.4.13",
|
||||
"dotenv": "^17.4.2",
|
||||
"electron-is-dev": "^2.0.0",
|
||||
"framer-motion": "^13.0.0",
|
||||
"framer-motion": "^13.1.0",
|
||||
"pdfjs-dist": "^3.11.174",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
@@ -885,9 +886,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1137,9 +1135,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1157,9 +1152,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1177,9 +1169,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1197,9 +1186,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1217,9 +1203,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1237,9 +1220,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1399,9 +1379,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1416,9 +1393,6 @@
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1433,9 +1407,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1450,9 +1421,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1467,9 +1435,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1484,9 +1449,6 @@
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1501,9 +1463,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1518,9 +1477,6 @@
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1535,9 +1491,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1552,9 +1505,6 @@
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1569,9 +1519,6 @@
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1586,9 +1533,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -1603,9 +1547,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2040,6 +1981,13 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
"version": "17.0.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
|
||||
@@ -3737,6 +3685,15 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.4.13",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
|
||||
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
|
||||
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||
"optionalDependencies": {
|
||||
"@types/trusted-types": "^2.0.7"
|
||||
}
|
||||
},
|
||||
"node_modules/dotenv": {
|
||||
"version": "17.4.2",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
|
||||
@@ -4656,9 +4613,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-13.0.0.tgz",
|
||||
"integrity": "sha512-nQGZXlsiigN48nzvE7AL1GLekql+etcphp8v+PQ0X04oxO20yVmV9rU9XQ25c136RdeIYoaWlKT9oAwvJza3mg==",
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-13.1.0.tgz",
|
||||
"integrity": "sha512-QSZrF0Id3QGuHJ+OL+9PSY9pk86C8ERFalwAGSchzTm65+ZoGH/RM26lmEARLljcHj2lqhv0jZOOks+EI3COOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^13.0.0",
|
||||
@@ -5783,9 +5740,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5807,9 +5761,6 @@
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5831,9 +5782,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -5855,9 +5803,6 @@
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MPL-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
|
||||
@@ -9,11 +9,12 @@
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/react": "^19.2.0",
|
||||
"@types/react-dom": "^19.2.0",
|
||||
"axios": "^1.6.0",
|
||||
"axios": "^1.19.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"dompurify": "^3.4.13",
|
||||
"dotenv": "^17.4.2",
|
||||
"electron-is-dev": "^2.0.0",
|
||||
"framer-motion": "^13.0.0",
|
||||
"framer-motion": "^13.1.0",
|
||||
"pdfjs-dist": "^3.11.174",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
const NTP_URL = import.meta.env.VITE_NTP_SERVER || 'http://192.168.139.249:3001/api/time';
|
||||
interface CurrencyRate {
|
||||
currency: string;
|
||||
rate: number;
|
||||
@@ -30,7 +30,6 @@ const BottomBar: React.FC = () => {
|
||||
|
||||
const [isOnline, setIsOnline] = useState(navigator.onLine);
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [currentTime, setCurrentTime] = useState(new Date()); // ✅ Добавлено
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const getAllItems = () => {
|
||||
@@ -58,14 +57,57 @@ const BottomBar: React.FC = () => {
|
||||
const items = getAllItems();
|
||||
const doubledItems = [...items, ...items, ...items];
|
||||
|
||||
// ✅ Обновление времени каждую секунду
|
||||
const NTP_URL = 'http://localhost:3005/api/time'; // или твой URL
|
||||
|
||||
const useServerTime = (interval: number = 1000) => {
|
||||
const [time, setTime] = useState(() => new Date());
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setCurrentTime(new Date());
|
||||
}, 1000);
|
||||
let timer: NodeJS.Timeout;
|
||||
let diff = 0;
|
||||
|
||||
const fetchTime = async () => {
|
||||
try {
|
||||
const res = await fetch(NTP_URL);
|
||||
if (!res.ok) throw new Error('Server time unavailable');
|
||||
const data = await res.json();
|
||||
const serverTime = new Date(data.time);
|
||||
|
||||
// Вычисляем разницу между серверным и локальным временем
|
||||
diff = serverTime.getTime() - Date.now();
|
||||
|
||||
// Устанавливаем время
|
||||
setTime(new Date(Date.now() + diff));
|
||||
|
||||
// Запускаем локальный таймер
|
||||
if (timer) clearInterval(timer);
|
||||
timer = setInterval(() => {
|
||||
setTime(new Date(Date.now() + diff));
|
||||
}, interval);
|
||||
} catch (err) {
|
||||
console.warn('Server time unavailable, using local time:', err);
|
||||
// Если сервер недоступен — используем локальное время
|
||||
setTime(new Date());
|
||||
if (timer) clearInterval(timer);
|
||||
timer = setInterval(() => {
|
||||
setTime(new Date());
|
||||
}, interval);
|
||||
}
|
||||
};
|
||||
|
||||
// Первый запрос
|
||||
fetchTime();
|
||||
|
||||
// Очистка при размонтировании
|
||||
return () => {
|
||||
if (timer) clearInterval(timer);
|
||||
};
|
||||
}, [interval]);
|
||||
|
||||
return time;
|
||||
};
|
||||
const currentTime = useServerTime(1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const handleOnline = () => setIsOnline(true);
|
||||
@@ -308,4 +350,5 @@ const BottomBar: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default BottomBar;
|
||||
@@ -1,18 +1,79 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { FaHandsHelping, FaCalendarCheck, FaMoneyBillWave, FaHome } from 'react-icons/fa';
|
||||
import { FaHandsHelping, FaCalendarCheck, FaMoneyBillWave, FaHome, FaTimes } from 'react-icons/fa';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
interface TradeUnionProps {
|
||||
onBack: () => void;
|
||||
employee: any;
|
||||
}
|
||||
|
||||
// Функция-обертка для рендеринга иконок
|
||||
const renderIcon = (Icon: any, style?: React.CSSProperties) => {
|
||||
return React.createElement(Icon, { style });
|
||||
};
|
||||
interface Block {
|
||||
id: number;
|
||||
tab: string;
|
||||
content: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
const sections = [
|
||||
{ id: 'events', title: 'Мероприятия', description: 'Анонсы и запись на мероприятия', icon: FaCalendarCheck, color: '#e74c3c' },
|
||||
{ id: 'help', title: 'Материальная помощь', description: 'Размеры и виды материальной помощи', icon: FaMoneyBillWave, color: '#f39c12' },
|
||||
{ id: 'housing', title: 'Жилищные условия', description: 'Информация об очереди на улучшение жилищных условий', icon: FaHome, color: '#3498db' },
|
||||
];
|
||||
|
||||
const TradeUnion: React.FC<TradeUnionProps> = ({ onBack, employee }) => {
|
||||
const [selectedSection, setSelectedSection] = useState<string | null>(null);
|
||||
const [blocksMap, setBlocksMap] = useState<Record<string, Block[]>>({});
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAllSections = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const results: Record<string, Block[]> = {};
|
||||
for (const sec of sections) {
|
||||
const response = await fetch(`http://localhost:3005/moders/content/section/${sec.id}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
results[sec.id] = Array.isArray(data) ? data : [];
|
||||
} else {
|
||||
results[sec.id] = [];
|
||||
}
|
||||
}
|
||||
setBlocksMap(results);
|
||||
} catch (err) {
|
||||
console.error('Ошибка загрузки разделов профсоюза:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchAllSections();
|
||||
}, []);
|
||||
|
||||
const openModal = (sectionId: string) => {
|
||||
setSelectedSection(sectionId);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setSelectedSection(null);
|
||||
};
|
||||
|
||||
const selectedData = selectedSection ? sections.find(s => s.id === selectedSection) : null;
|
||||
const selectedBlocks = selectedSection ? blocksMap[selectedSection] || [] : [];
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleString('ru-RU', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
@@ -63,98 +124,252 @@ const TradeUnion: React.FC<TradeUnionProps> = ({ onBack, employee }) => {
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
}}>
|
||||
{renderIcon(FaHandsHelping, { color: '#e74c3c', fontSize: '24px' })}
|
||||
<FaHandsHelping style={{ color: '#e74c3c', fontSize: '24px' }} />
|
||||
Профсоюз
|
||||
</h2>
|
||||
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
|
||||
gap: '20px',
|
||||
}}>
|
||||
<motion.div
|
||||
whileHover={{ y: -5 }}
|
||||
{loading ? (
|
||||
<div style={{ padding: '40px', textAlign: 'center', color: '#636e72' }}>Загрузка...</div>
|
||||
) : (
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))',
|
||||
gap: '20px',
|
||||
}}>
|
||||
{sections.map((section) => {
|
||||
const Icon = section.icon;
|
||||
const blocks = blocksMap[section.id] || [];
|
||||
const hasContent = blocks.length > 0;
|
||||
return (
|
||||
<motion.div
|
||||
key={section.id}
|
||||
whileHover={{ y: -5 }}
|
||||
onClick={() => openModal(section.id)}
|
||||
style={{
|
||||
background: 'white',
|
||||
borderRadius: '16px',
|
||||
padding: '25px',
|
||||
boxShadow: '0 4px 20px rgba(0,0,0,0.06)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s ease',
|
||||
border: `1px solid ${hasContent ? section.color : '#f1f2f6'}`,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{hasContent && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '12px',
|
||||
right: '12px',
|
||||
background: section.color,
|
||||
color: 'white',
|
||||
fontSize: '10px',
|
||||
padding: '2px 10px',
|
||||
borderRadius: '12px',
|
||||
fontWeight: 600,
|
||||
}}>
|
||||
{blocks.length} блок(ов)
|
||||
</div>
|
||||
)}
|
||||
<div style={{ fontSize: '32px', color: section.color, marginBottom: '15px' }}>
|
||||
<Icon />
|
||||
</div>
|
||||
<h3 style={{ margin: '0 0 10px 0', color: '#2d3436', fontSize: '18px' }}>
|
||||
{section.title}
|
||||
</h3>
|
||||
<p style={{ color: '#636e72', fontSize: '14px', lineHeight: 1.6, margin: 0 }}>
|
||||
{section.description}
|
||||
</p>
|
||||
{!hasContent && (
|
||||
<div style={{
|
||||
marginTop: '12px',
|
||||
fontSize: '12px',
|
||||
color: '#b2bec3',
|
||||
}}>
|
||||
Информация отсутствует
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ===== МОДАЛКА ===== */}
|
||||
{selectedSection && selectedData && (
|
||||
<div
|
||||
style={{
|
||||
background: 'white',
|
||||
borderRadius: '16px',
|
||||
padding: '25px',
|
||||
boxShadow: '0 4px 20px rgba(0,0,0,0.06)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s ease',
|
||||
border: '1px solid #f1f2f6',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
background: 'rgba(0,0,0,0.6)',
|
||||
backdropFilter: 'blur(6px)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 1000,
|
||||
padding: '20px',
|
||||
}}
|
||||
onClick={(e) => {
|
||||
if (e.target === e.currentTarget) closeModal();
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '32px', color: '#e74c3c', marginBottom: '15px' }}>
|
||||
{renderIcon(FaCalendarCheck)}
|
||||
</div>
|
||||
<h3 style={{ margin: '0 0 10px 0', color: '#2d3436', fontSize: '18px' }}>
|
||||
Мероприятия
|
||||
</h3>
|
||||
<p style={{ color: '#636e72', fontSize: '14px', lineHeight: 1.6, margin: 0 }}>
|
||||
Анонсы и запись на мероприятия
|
||||
</p>
|
||||
</motion.div>
|
||||
<div
|
||||
style={{
|
||||
background: 'white',
|
||||
borderRadius: '20px',
|
||||
width: '70%',
|
||||
height: '80%',
|
||||
boxShadow: '0 30px 80px rgba(0,0,0,0.3)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{/* Шапка (заголовок + крестик) — фиксирована */}
|
||||
<div
|
||||
style={{
|
||||
padding: '20px 30px 10px 30px',
|
||||
borderBottom: '1px solid #eee',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
flexShrink: 0,
|
||||
background: 'white',
|
||||
zIndex: 2,
|
||||
}}
|
||||
>
|
||||
<h3 style={{
|
||||
color: selectedData.color,
|
||||
fontSize: '22px',
|
||||
margin: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '12px',
|
||||
}}>
|
||||
<selectedData.icon style={{ fontSize: '24px' }} />
|
||||
{selectedData.title}
|
||||
</h3>
|
||||
<div style={{ flex: 1, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
style={{
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
fontSize: '26px',
|
||||
cursor: 'pointer',
|
||||
color: '#999',
|
||||
padding: '0 8px',
|
||||
transition: 'color 0.2s',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'right',
|
||||
}}
|
||||
onMouseEnter={(e) => e.currentTarget.style.color = '#333'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.color = '#999'}
|
||||
>
|
||||
<FaTimes />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
whileHover={{ y: -5 }}
|
||||
style={{
|
||||
background: 'white',
|
||||
borderRadius: '16px',
|
||||
padding: '25px',
|
||||
boxShadow: '0 4px 20px rgba(0,0,0,0.06)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s ease',
|
||||
border: '1px solid #f1f2f6',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '32px', color: '#f39c12', marginBottom: '15px' }}>
|
||||
{renderIcon(FaMoneyBillWave)}
|
||||
</div>
|
||||
<h3 style={{ margin: '0 0 10px 0', color: '#2d3436', fontSize: '18px' }}>
|
||||
Материальная помощь
|
||||
</h3>
|
||||
<p style={{ color: '#636e72', fontSize: '14px', lineHeight: 1.6, margin: 0 }}>
|
||||
Размеры и виды материальной помощи
|
||||
</p>
|
||||
</motion.div>
|
||||
{/* Тело с прокруткой */}
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
padding: '20px 30px 30px 30px',
|
||||
}}
|
||||
>
|
||||
{selectedBlocks.length === 0 ? (
|
||||
<div style={{
|
||||
padding: '40px',
|
||||
textAlign: 'center',
|
||||
color: '#b2bec3',
|
||||
fontSize: '16px',
|
||||
}}>
|
||||
Информация отсутствует
|
||||
</div>
|
||||
) : (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
}}>
|
||||
{selectedBlocks.map((block) => (
|
||||
<div
|
||||
key={block.id}
|
||||
style={{
|
||||
border: '1px solid #e8e8e8',
|
||||
borderRadius: '12px',
|
||||
padding: '18px 20px',
|
||||
background: '#fafafa',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '15px',
|
||||
lineHeight: 1.7,
|
||||
color: '#2d3436',
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(block.content)
|
||||
}}
|
||||
/>
|
||||
<div style={{
|
||||
marginTop: '14px',
|
||||
paddingTop: '12px',
|
||||
borderTop: '1px solid #eee',
|
||||
fontSize: '12px',
|
||||
color: '#999',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
}}>
|
||||
<span>🕒 {formatDate(block.created_at)}</span>
|
||||
<span style={{ fontSize: '11px', color: '#bbb' }}>ID: {block.id}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
whileHover={{ y: -5 }}
|
||||
style={{
|
||||
background: 'white',
|
||||
borderRadius: '16px',
|
||||
padding: '25px',
|
||||
boxShadow: '0 4px 20px rgba(0,0,0,0.06)',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s ease',
|
||||
border: '1px solid #f1f2f6',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '32px', color: '#3498db', marginBottom: '15px' }}>
|
||||
{renderIcon(FaHome)}
|
||||
{/* Нижняя кнопка закрытия */}
|
||||
<div
|
||||
style={{
|
||||
padding: '12px 30px 20px 30px',
|
||||
borderTop: '1px solid #eee',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0,
|
||||
background: 'white',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={closeModal}
|
||||
style={{
|
||||
padding: '10px 40px',
|
||||
background: '#6c757d',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '10px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
fontWeight: 500,
|
||||
transition: 'background 0.2s',
|
||||
}}
|
||||
onMouseEnter={(e) => e.currentTarget.style.background = '#5a6268'}
|
||||
onMouseLeave={(e) => e.currentTarget.style.background = '#6c757d'}
|
||||
>
|
||||
Закрыть
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h3 style={{ margin: '0 0 10px 0', color: '#2d3436', fontSize: '18px' }}>
|
||||
Жилищные условия
|
||||
</h3>
|
||||
<p style={{ color: '#636e72', fontSize: '14px', lineHeight: 1.6, margin: 0 }}>
|
||||
Информация об очереди на улучшение жилищных условий
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
marginTop: '30px',
|
||||
padding: '20px',
|
||||
background: '#f8f9fa',
|
||||
borderRadius: '12px',
|
||||
border: '1px solid #f1f2f6',
|
||||
textAlign: 'center',
|
||||
color: '#636e72',
|
||||
}}>
|
||||
<p style={{ margin: 0 }}>
|
||||
ℹ️ Раздел в разработке. Функционал будет доступен в ближайшее время.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user