From 2560e47b1118991f082ef6e6c586e17d8c523231 Mon Sep 17 00:00:00 2001 From: Zhakhangir Anuarbek Date: Wed, 27 Aug 2025 22:04:46 +0500 Subject: [PATCH] feat: replace img tags with Image component for better performance and optimization --- src/components/Activities.tsx | 37 ++-- src/components/Cover.tsx | 154 ++++++++-------- src/components/Events.tsx | 79 ++++----- src/components/Info.tsx | 56 +++--- src/components/Kamaz.tsx | 46 ++--- src/components/Partners.tsx | 140 +++++++++------ src/components/Scheme.tsx | 16 +- src/pages/csv.tsx | 320 ++++++++++++++++++++++++++++++++++ 8 files changed, 617 insertions(+), 231 deletions(-) create mode 100644 src/pages/csv.tsx diff --git a/src/components/Activities.tsx b/src/components/Activities.tsx index a375574..9672d7f 100644 --- a/src/components/Activities.tsx +++ b/src/components/Activities.tsx @@ -3,6 +3,7 @@ import { fluxgore } from "@/utils/fonts"; import { useRef, useState } from "react"; import { Swiper, SwiperSlide } from "swiper/react"; import type { Swiper as SwiperType } from "swiper"; +import Image from "next/image"; import "swiper/css"; @@ -35,7 +36,13 @@ function SwiperButton({ `} aria-label={direction === "next" ? "Next slide" : "Previous slide"} > - + ); } @@ -46,17 +53,24 @@ function Slide({ title, imageSrc }: { title: string; imageSrc: string }) {

{title}

- Slide Image +
+ {title} +
- Background Tear +
+ Background Tear +
); } @@ -152,7 +166,6 @@ function Activities() { imageSrc="/images/activities/race_taxi.png" /> - diff --git a/src/components/Cover.tsx b/src/components/Cover.tsx index c592556..f7fe863 100644 --- a/src/components/Cover.tsx +++ b/src/components/Cover.tsx @@ -1,6 +1,7 @@ /* eslint-disable @next/next/no-img-element */ import { fluxgore } from "@/utils/fonts"; import Button from "./Button"; +import Image from "next/image"; interface CoverHeadingProps { children: React.ReactNode; @@ -94,63 +95,68 @@ function DateBox() { function Cover() { return ( -
-
- {/* Top row with ФЕСТИВАЛЬ and date box */} -
- -
+
+ {/* Background Image */} + Tech fest background - {/*
- -
-
- ФЕСТИВАЛЬ -
+ {/* Content overlay */} +
+
+ {/* Top row with ФЕСТИВАЛЬ and date box */} +
+ +
+ Фестиваль технических видов спорта 2025 + + Фестиваль технических видов спорта 2025 +
- ТЕХНИЧЕСКИХ - - ВИДОВ СПОРТА */} - Фестиваль технических видов спорта 2025 - - Фестиваль технических видов спорта 2025 -
- -
- {/* */} +
+ {/* */} +
-
+
- Dep Logo - Mos Logo - Raf Logo - Ctvs Logo - Xenum Logo - GNVOil Logo - Q8oils Logo - Q8oils Logo - {/* SMP Logo */} +
+ Dep Logo +
+
+ Mos Logo +
+
+ Raf Logo +
+
+ Ctvs Logo +
+
+ Xenum Logo +
+
+ GNVOil Logo +
+
+ Q8oils Logo +
+
+ Partner Logo +
+ {/*
+ SMP Logo +
*/}
- Festival Map +
+ Festival Map +
{/* Date Selection */}
diff --git a/src/pages/csv.tsx b/src/pages/csv.tsx new file mode 100644 index 0000000..08ae3ef --- /dev/null +++ b/src/pages/csv.tsx @@ -0,0 +1,320 @@ +import React, { useState, useEffect } from 'react'; +import PocketBase from "pocketbase"; +import { fluxgore, gothampro } from "@/utils/fonts"; + +const pb = new PocketBase("https://base.mossport.info"); + +interface CSVExporterProps { + className?: string; +} + +const CSVExporter: React.FC = ({ className = '' }) => { + const [isLoading, setIsLoading] = useState(false); + const [selectedCollection, setSelectedCollection] = useState('forms'); + const [selectedType, setSelectedType] = useState(''); + const [error, setError] = useState(null); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [credentials, setCredentials] = useState({ email: "", password: "" }); + const [authError, setAuthError] = useState(""); + const [loading, setLoading] = useState(true); + + const exportTypes = [ + { value: '', label: 'Все типы' }, + { value: 'exhibition', label: 'Выставка' }, + { value: 'fight', label: 'Дрифт-битва' }, + ]; + + const collections = [ + { value: 'forms', label: 'Формы' }, + // Add other collections if needed + ]; + + useEffect(() => { + // Check if user is already authenticated + if (pb.authStore.isValid) { + setIsAuthenticated(true); + } + setLoading(false); + }, []); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setAuthError(""); + + try { + await pb.admins.authWithPassword(credentials.email, credentials.password); + setIsAuthenticated(true); + } catch (error) { + console.error("Auth error:", error); + setAuthError("Неверный email или пароль"); + } + }; + + const handleLogout = () => { + pb.authStore.clear(); + setIsAuthenticated(false); + setCredentials({ email: "", password: "" }); + }; + + const handleExport = async () => { + try { + setIsLoading(true); + setError(null); + + // Build query parameters + const params = new URLSearchParams({ + collection: selectedCollection, + }); + + if (selectedType) { + params.append('type', selectedType); + } + + // Add auth token if available + if (pb.authStore.token) { + params.append('token', pb.authStore.token); + } + + // Make the request + const response = await fetch(`/api/csv?${params.toString()}`); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || 'Ошибка при экспорте данных'); + } + + // Get the filename from response headers + const contentDisposition = response.headers.get('Content-Disposition'); + let filename = 'export.csv'; + + if (contentDisposition) { + const filenameMatch = contentDisposition.match(/filename="(.+)"/); + if (filenameMatch) { + filename = filenameMatch[1]; + } + } + + // Create blob and download + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + + } catch (err) { + setError(err instanceof Error ? err.message : 'Произошла ошибка'); + } finally { + setIsLoading(false); + } + }; + + // Loading state + if (loading) { + return ( +
+
+
+
+ Загрузка... +
+
+
+ ); + } + + // Authentication form + if (!isAuthenticated) { + return ( +
+
+
+
+

+ Экспорт данных CSV +

+

+ Войдите для доступа к экспорту данных +

+
+
+
+
+ + + setCredentials((prev) => ({ + ...prev, + email: e.target.value, + })) + } + /> +
+
+ + + setCredentials((prev) => ({ + ...prev, + password: e.target.value, + })) + } + /> +
+
+ + {authError && ( +
+ {authError} +
+ )} + + +
+
+
+
+ ); + } + + // Main CSV exporter interface + return ( +
+ {/* Header */} +
+
+
+

+ Экспорт данных в CSV +

+ +
+
+
+ + {/* Content */} +
+
+
+

+ Настройки экспорта +

+ + {error && ( +
+ ⚠️ + {error} +
+ )} + +
+
+ + +
+ +
+ + +
+ + +
+ +
+

Информация:

+
    +
  • • Экспорт включает все поля в зависимости от выбранного типа
  • +
  • • Файл сохраняется в формате CSV с поддержкой UTF-8
  • +
  • • Имя файла содержит коллекцию, тип и дату экспорта
  • +
+
+
+
+
+
+ ); +}; + +export default CSVExporter; \ No newline at end of file