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}
-
+
+
+
-
+
+
+
);
}
@@ -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 */}
+
- {/*
-
-
-
-
ФЕСТИВАЛЬ
-
+ {/* Content overlay */}
+
+
+ {/* Top row with ФЕСТИВАЛЬ and date box */}
+
+
+
+
+
+
+
-
ТЕХНИЧЕСКИХ
-
-
ВИДОВ СПОРТА */}
-

-
-

-
-
-
- {/*
*/}
+
+ {/* */}
+
-
+
-

-

-

-

-

-

-

-

- {/*

*/}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/*
+
+
*/}
-

+
+
+
{/* 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
+
+
+ Войдите для доступа к экспорту данных
+
+
+
+
+
+
+ );
+ }
+
+ // Main CSV exporter interface
+ return (
+
+ {/* Header */}
+
+
+
+
+ Экспорт данных в CSV
+
+
+
+
+
+
+ {/* Content */}
+
+
+
+
+ Настройки экспорта
+
+
+ {error && (
+
+ ⚠️
+ {error}
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Информация:
+
+ - • Экспорт включает все поля в зависимости от выбранного типа
+ - • Файл сохраняется в формате CSV с поддержкой UTF-8
+ - • Имя файла содержит коллекцию, тип и дату экспорта
+
+
+
+
+
+
+ );
+};
+
+export default CSVExporter;
\ No newline at end of file