fix: issue with translate

This commit is contained in:
2025-05-07 19:28:09 +05:00
parent cd73f557b5
commit 685c2388f6
5 changed files with 708 additions and 708 deletions
+5 -5
View File
@@ -1,5 +1,5 @@
import { motion } from "framer-motion";
import PlusIcon from "./icons/PlusIcon";
import { motion } from 'framer-motion';
import PlusIcon from './icons/PlusIcon';
interface MoreProjectButtonProps {
onClick?: () => void;
@@ -10,12 +10,12 @@ function MoreProjectButton({ onClick }: MoreProjectButtonProps) {
<motion.button
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true, margin: "-100px" }}
viewport={{ once: true, margin: '-100px' }}
transition={{ duration: 1, ease: [0.58, 0.12, 0.27, 0.98], delay: 0.2 }}
className="sm:aspect-[4/3] border border-[#3D425C] rounded-[48px] px-6 py-4 flex sm:flex-col sm:justify-center justify-between items-center gap-2"
className='sm:aspect-[4/3] border border-[#3D425C] rounded-[48px] px-6 py-4 flex sm:flex-col sm:justify-center justify-between items-center gap-2'
onClick={onClick}
>
<p className="font-gilroy font-medium leading-none">Показать еще</p>
<p className='font-medium leading-none font-gilroy'>Show more</p>
<PlusIcon />
</motion.button>
);
+44 -44
View File
@@ -1,53 +1,53 @@
/* eslint-disable react-hooks/rules-of-hooks */
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { motion } from "framer-motion";
import { useEffect, useRef, useState } from "react";
import ArrowLeftIcon from "./icons/ArrowLeftIcon";
import ArrowRightIcon from "./icons/ArrowRightIcon";
import { useSwipeable } from "react-swipeable";
import { motion } from 'framer-motion';
import { useEffect, useRef, useState } from 'react';
import ArrowLeftIcon from './icons/ArrowLeftIcon';
import ArrowRightIcon from './icons/ArrowRightIcon';
import { useSwipeable } from 'react-swipeable';
function VideoSliderMobile() {
const [items] = useState<any[]>([
{
title: "Virtual tour of the residential complex",
desc: "The client can personally assess the angle of sunlight, minor architectural forms, and landscape, moving through the complex with a tap.",
video: "/videos/features/virtual_tour.mp4",
title: 'Virtual tour of the residential complex',
desc: 'The client can personally assess the angle of sunlight, minor architectural forms, and landscape, moving through the complex with a tap.',
video: '/videos/features/virtual_tour.mp4',
},
{
title: "All infrastructure on one screen",
desc: "The ability to assess the infrastructure of the area will show the client important points of interest and the time it takes to reach them.",
video: "/videos/features/nks_infra.mp4",
title: 'All infrastructure on one screen',
desc: 'The ability to assess the infrastructure of the area will show the client important points of interest and the time it takes to reach them.',
video: '/videos/features/nks_infra.mp4',
},
{
title: "Конфигуратор интерьера",
desc: "The client can freely choose furniture and design using the interior configurator. It is possible to select the style for the entire apartment or change individual details.",
video: "/videos/features/uralsky.mp4",
title: 'Interior Configurator',
desc: 'The client can freely choose furniture and design using the interior configurator. It is possible to select the style for the entire apartment or change individual details.',
video: '/videos/features/uralsky.mp4',
},
{
title: "Parametric apartment search",
desc: "The filter will allow marking specific advantages, determine the number of rooms, desired floor, price, and receive a selection of suitable options.",
video: "/videos/features/parametric.mp4",
title: 'Parametric apartment search',
desc: 'The filter will allow marking specific advantages, determine the number of rooms, desired floor, price, and receive a selection of suitable options.',
video: '/videos/features/parametric.mp4',
},
{
title: "Any render in a few seconds",
desc: "When you need any object from any angle for advertising, just take a picture inside the presentation.",
video: "/videos/features/render.mp4",
title: 'Any render in a few seconds',
desc: 'When you need any object from any angle for advertising, just take a picture inside the presentation.',
video: '/videos/features/render.mp4',
},
{
title: "Formation of a wishlist",
desc: "The client can add apartment options to favorites, compare them with each other by the main parameters, and choose their future apartment.",
video: "/videos/features/wish.mp4",
title: 'Formation of a wishlist',
desc: 'The client can add apartment options to favorites, compare them with each other by the main parameters, and choose their future apartment.',
video: '/videos/features/wish.mp4',
},
{
title: "Integration with CRM",
title: 'Integration with CRM',
desc: "The application transfers client information to the developer's CRM system and receives current information about prices and statuses of apartments.",
video: "/videos/features/integra_crm.mp4",
video: '/videos/features/integra_crm.mp4',
},
{
title: "Sending a commercial offer",
desc: "A commercial offer with selected apartments can be sent to the client by mail or printed and handed over personally.",
video: "/videos/features/send.mp4",
title: 'Sending a commercial offer',
desc: 'A commercial offer with selected apartments can be sent to the client by mail or printed and handed over personally.',
video: '/videos/features/send.mp4',
},
// {
// title: "Интерактивная инсоляция",
@@ -65,11 +65,11 @@ function VideoSliderMobile() {
const videoRefs = items.map(() => useRef<HTMLVideoElement>(null));
const handlers = useSwipeable({
onSwiped: (e) => {
if (e.dir === "Left") {
if (e.dir === 'Left') {
handleClickNext();
}
if (e.dir === "Right") {
if (e.dir === 'Right') {
handleClickPrev();
}
},
@@ -106,7 +106,7 @@ function VideoSliderMobile() {
return (
<div
{...handlers}
className="xl:hidden flex flex-col sm:gap-6 gap-4 border-b border-[#3D425C] pb-5"
className='xl:hidden flex flex-col sm:gap-6 gap-4 border-b border-[#3D425C] pb-5'
>
<div
// ref={videosContainerRef}
@@ -121,39 +121,39 @@ function VideoSliderMobile() {
muted
loop
playsInline
preload="metadata"
preload='metadata'
className={`relative aspect-video transition-all duration-500 sm:w-[calc(100%-88px)] ${
index !== activeIndex
? "sm:scale-[70%] scale-[80%] sm:-translate-y-[15%] -translate-y-[10%] sm:-translate-x-[calc(15%+88px-12px)] -translate-x-[calc(10%-8px)]"
: ""
? 'sm:scale-[70%] scale-[80%] sm:-translate-y-[15%] -translate-y-[10%] sm:-translate-x-[calc(15%+88px-12px)] -translate-x-[calc(10%-8px)]'
: ''
}`}
/>
))}
</div>
<div className="flex justify-between sm:gap-[30px] gap-3 sm:h-auto h-[168px]">
<div className="flex flex-col gap-3">
<p className="text-xl font-gilroy font-medium">
<div className='flex justify-between sm:gap-[30px] gap-3 sm:h-auto h-[168px]'>
<div className='flex flex-col gap-3'>
<p className='text-xl font-medium font-gilroy'>
{items[activeIndex].title}
</p>
<p className="text-sm">{items[activeIndex].desc}</p>
<p className='text-sm'>{items[activeIndex].desc}</p>
</div>
<div className="relative flex flex-col items-center gap-4">
<div className="sm:absolute -top-[64px]">
<p className="sm:text-2xl text-xl font-gilroy font-medium">
<div className='relative flex flex-col items-center gap-4'>
<div className='sm:absolute -top-[64px]'>
<p className='text-xl font-medium sm:text-2xl font-gilroy'>
{activeIndex + 1}/{items.length}
</p>
</div>
<div className="flex flex-col gap-2 self-end">
<div className='flex flex-col self-end gap-2'>
<button
onClick={handleClickNext}
className="sm:p-4 p-2 border border-[#3D425C] rounded-full outline-none"
className='sm:p-4 p-2 border border-[#3D425C] rounded-full outline-none'
>
<ArrowRightIcon />
</button>
<button
onClick={handleClickPrev}
className="sm:p-4 p-2 border border-[#3D425C] rounded-full outline-none"
className='sm:p-4 p-2 border border-[#3D425C] rounded-full outline-none'
>
<ArrowLeftIcon />
</button>
@@ -1,292 +1,292 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ChangeEvent, useEffect, useState } from 'react';
import api from '../../utils/api';
import Button from '../Button';
import IProject from '../../types/IProject';
import useModalStore from '../../stores/useModalStore';
import { format } from 'date-fns';
import Close2Icon from '../icons/Close2Icon';
// /* eslint-disable react-hooks/exhaustive-deps */
// /* eslint-disable @typescript-eslint/no-explicit-any */
// import { ChangeEvent, useEffect, useState } from 'react';
// import api from '../../utils/api';
// import Button from '../Button';
// import IProject from '../../types/IProject';
// import useModalStore from '../../stores/useModalStore';
// import { format } from 'date-fns';
// import Close2Icon from '../icons/Close2Icon';
function CreateProjectModal() {
const [project, setProject] = useState<IProject>({
title: '',
company: '',
city: '',
image: '',
releaseDate: format(new Date(), 'yyyy-MM-dd'),
devices: [],
});
// function CreateProjectModal() {
// const [project, setProject] = useState<IProject>({
// title: '',
// company: '',
// city: '',
// image: '',
// releaseDate: format(new Date(), 'yyyy-MM-dd'),
// devices: [],
// });
const [file, setFile] = useState<File>();
const [previewFile, setPreviewFile] = useState<string>();
const [setModal] = useModalStore((state) => [state.setModal]);
// const [file, setFile] = useState<File>();
// const [previewFile, setPreviewFile] = useState<string>();
// const [setModal] = useModalStore((state) => [state.setModal]);
function handleChangeFile(e: ChangeEvent<HTMLInputElement>) {
if (!e.target.files) return;
// function handleChangeFile(e: ChangeEvent<HTMLInputElement>) {
// if (!e.target.files) return;
const targetFile = e.target.files[0];
// const targetFile = e.target.files[0];
setFile(targetFile);
setPreviewFile(URL.createObjectURL(targetFile));
}
// setFile(targetFile);
// setPreviewFile(URL.createObjectURL(targetFile));
// }
async function uploadFile() {
if (!file) return;
// async function uploadFile() {
// if (!file) return;
const formData = new FormData();
formData.append('file', file);
// const formData = new FormData();
// formData.append('file', file);
try {
const { file }: { file: string } = await api
.post('upload', { body: formData })
.json();
// try {
// const { file }: { file: string } = await api
// .post('upload', { body: formData })
// .json();
setProject((prev) => ({
...prev,
image: file,
}));
} catch (error) {
if (error instanceof Error) {
alert(`Error: ${error.message}`);
}
}
}
// setProject((prev) => ({
// ...prev,
// image: file,
// }));
// } catch (error) {
// if (error instanceof Error) {
// alert(`Error: ${error.message}`);
// }
// }
// }
async function createProject() {
try {
await api.post('projects', { json: { ...project } });
// async function createProject() {
// try {
// await api.post('projects', { json: { ...project } });
setModal(null);
} catch (error) {
if (error instanceof Error) {
alert(`Error: ${error.message}`);
}
}
}
// setModal(null);
// } catch (error) {
// if (error instanceof Error) {
// alert(`Error: ${error.message}`);
// }
// }
// }
async function handleSubmit(e: ChangeEvent<HTMLFormElement>) {
e.preventDefault();
// async function handleSubmit(e: ChangeEvent<HTMLFormElement>) {
// e.preventDefault();
await createProject();
// await createProject();
window.location.reload();
}
// window.location.reload();
// }
useEffect(() => {
uploadFile();
}, [file]);
// useEffect(() => {
// uploadFile();
// }, [file]);
return (
<div className='bg-white shadow-lg text-black p-8 rounded-xl flex flex-col gap-4'>
<div className='flex justify-between items-center border-b border-[#ccc] pb-4 gap-4'>
<p className='text-xl'>Создание проекта</p>
<button
onClick={() => setModal(null)}
className='p-2 hover:bg-white hover:bg-opacity-10 transition-colors rounded-full'
>
<Close2Icon />
</button>
</div>
<form
onSubmit={handleSubmit}
className='grid grid-cols-2 gap-4 w-[512px]'
>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Название</label>
<input
autoFocus
required
type='text'
placeholder='Название'
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
value={project.title}
onChange={(e) =>
setProject((prev) => ({ ...prev, title: e.target.value }))
}
/>
</div>
// return (
// <div className='flex flex-col gap-4 p-8 text-black bg-white shadow-lg rounded-xl'>
// <div className='flex justify-between items-center border-b border-[#ccc] pb-4 gap-4'>
// <p className='text-xl'>Создание проекта</p>
// <button
// onClick={() => setModal(null)}
// className='p-2 transition-colors rounded-full hover:bg-white hover:bg-opacity-10'
// >
// <Close2Icon />
// </button>
// </div>
// <form
// onSubmit={handleSubmit}
// className='grid grid-cols-2 gap-4 w-[512px]'
// >
// <div className='flex flex-col gap-1'>
// <label className='text-sm'>Название</label>
// <input
// autoFocus
// required
// type='text'
// placeholder='Название'
// className='px-3 py-2 border rounded-lg outline-none border-neutral-500'
// value={project.title}
// onChange={(e) =>
// setProject((prev) => ({ ...prev, title: e.target.value }))
// }
// />
// </div>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Компания</label>
<input
required
type='text'
placeholder='Компания'
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
value={project.company}
onChange={(e) =>
setProject((prev) => ({ ...prev, company: e.target.value }))
}
/>
</div>
// <div className='flex flex-col gap-1'>
// <label className='text-sm'>Компания</label>
// <input
// required
// type='text'
// placeholder='Компания'
// className='px-3 py-2 border rounded-lg outline-none border-neutral-500'
// value={project.company}
// onChange={(e) =>
// setProject((prev) => ({ ...prev, company: e.target.value }))
// }
// />
// </div>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Город</label>
<input
required
type='text'
placeholder='Город'
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
value={project.city}
onChange={(e) =>
setProject((prev) => ({ ...prev, city: e.target.value }))
}
/>
</div>
// <div className='flex flex-col gap-1'>
// <label className='text-sm'>Город</label>
// <input
// required
// type='text'
// placeholder='Город'
// className='px-3 py-2 border rounded-lg outline-none border-neutral-500'
// value={project.city}
// onChange={(e) =>
// setProject((prev) => ({ ...prev, city: e.target.value }))
// }
// />
// </div>
<label className='relative border border-dashed border-neutral-500 px-3 py-2 hover:bg-opacity-10 hover:bg-black cursor-pointer rounded-lg flex flex-col gap-2'>
<input
required
type='file'
accept='image/*'
className='absolute opacity-0'
onChange={handleChangeFile}
/>
<p className='truncate'>
{file ? file.name : 'Выберите изображение'}
</p>
// <label className='relative flex flex-col gap-2 px-3 py-2 border border-dashed rounded-lg cursor-pointer border-neutral-500 hover:bg-opacity-10 hover:bg-black'>
// <input
// required
// type='file'
// accept='image/*'
// className='absolute opacity-0'
// onChange={handleChangeFile}
// />
// <p className='truncate'>
// {file ? file.name : 'Выберите изображение'}
// </p>
{previewFile && <img src={previewFile} alt='' />}
</label>
// {previewFile && <img src={previewFile} alt='' />}
// </label>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Стадия</label>
<select
required
value={project.stage || ''}
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
onChange={(e) =>
setProject((prev) => ({ ...prev, stage: +e.target.value }))
}
>
<option value='' disabled>
Выберите стадию
</option>
<option value={1}>1</option>
<option value={2}>2</option>
<option value={3}>3</option>
<option value={4}>4</option>
<option value={5}>5</option>
<option value={6}>6</option>
</select>
</div>
// <div className='flex flex-col gap-1'>
// <label className='text-sm'>Стадия</label>
// <select
// required
// value={project.stage || ''}
// className='px-3 py-2 border rounded-lg outline-none border-neutral-500'
// onChange={(e) =>
// setProject((prev) => ({ ...prev, stage: +e.target.value }))
// }
// >
// <option value='' disabled>
// Выберите стадию
// </option>
// <option value={1}>1</option>
// <option value={2}>2</option>
// <option value={3}>3</option>
// <option value={4}>4</option>
// <option value={5}>5</option>
// <option value={6}>6</option>
// </select>
// </div>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Дата релиза</label>
<input
type='date'
required
value={project.releaseDate}
onChange={(e) =>
setProject((prev) => ({
...prev,
releaseDate: e.target.value,
}))
}
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
/>
</div>
// <div className='flex flex-col gap-1'>
// <label className='text-sm'>Дата релиза</label>
// <input
// type='date'
// required
// value={project.releaseDate}
// onChange={(e) =>
// setProject((prev) => ({
// ...prev,
// releaseDate: e.target.value,
// }))
// }
// className='px-3 py-2 border rounded-lg outline-none border-neutral-500'
// />
// </div>
<div className='flex flex-col gap-1'>
<p className='text-sm'>Девайсы</p>
<div className=''>
<label className='flex items-center gap-2'>
<input
type='checkbox'
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, 'stream'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== 'stream'
),
}));
}
}}
/>
<span>Stream</span>
</label>
// <div className='flex flex-col gap-1'>
// <p className='text-sm'>Девайсы</p>
// <div className=''>
// <label className='flex items-center gap-2'>
// <input
// type='checkbox'
// onChange={(e) => {
// if (e.target.checked) {
// setProject((prev) => ({
// ...prev,
// devices: [...prev.devices!, 'stream'],
// }));
// } else {
// setProject((prev) => ({
// ...prev,
// devices: prev.devices!.filter(
// (device) => device !== 'stream'
// ),
// }));
// }
// }}
// />
// <span>Stream</span>
// </label>
<label className='flex items-center gap-2'>
<input
type='checkbox'
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, 'touch'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== 'touch'
),
}));
}
}}
/>
<span>Touch</span>
</label>
// <label className='flex items-center gap-2'>
// <input
// type='checkbox'
// onChange={(e) => {
// if (e.target.checked) {
// setProject((prev) => ({
// ...prev,
// devices: [...prev.devices!, 'touch'],
// }));
// } else {
// setProject((prev) => ({
// ...prev,
// devices: prev.devices!.filter(
// (device) => device !== 'touch'
// ),
// }));
// }
// }}
// />
// <span>Touch</span>
// </label>
<label className='flex items-center gap-2'>
<input
type='checkbox'
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, 'mobile'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== 'mobile'
),
}));
}
}}
/>
<span>Mobile</span>
</label>
// <label className='flex items-center gap-2'>
// <input
// type='checkbox'
// onChange={(e) => {
// if (e.target.checked) {
// setProject((prev) => ({
// ...prev,
// devices: [...prev.devices!, 'mobile'],
// }));
// } else {
// setProject((prev) => ({
// ...prev,
// devices: prev.devices!.filter(
// (device) => device !== 'mobile'
// ),
// }));
// }
// }}
// />
// <span>Mobile</span>
// </label>
<label className='flex items-center gap-2'>
<input
type='checkbox'
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, 'vr'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== 'vr'
),
}));
}
}}
/>
<span>VR</span>
</label>
</div>
</div>
// <label className='flex items-center gap-2'>
// <input
// type='checkbox'
// onChange={(e) => {
// if (e.target.checked) {
// setProject((prev) => ({
// ...prev,
// devices: [...prev.devices!, 'vr'],
// }));
// } else {
// setProject((prev) => ({
// ...prev,
// devices: prev.devices!.filter(
// (device) => device !== 'vr'
// ),
// }));
// }
// }}
// />
// <span>VR</span>
// </label>
// </div>
// </div>
<div className='col-span-full flex justify-end'>
<Button className='text-white outline-none'>Добавить проект</Button>
</div>
</form>
</div>
);
}
// <div className='flex justify-end col-span-full'>
// <Button className='text-white outline-none'>Добавить проект</Button>
// </div>
// </form>
// </div>
// );
// }
export default CreateProjectModal;
// export default CreateProjectModal;
+295 -295
View File
@@ -1,325 +1,325 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ChangeEvent, useEffect, useState } from 'react';
import api from '../../utils/api';
import Button from '../Button';
import IProject from '../../types/IProject';
import useModalStore from '../../stores/useModalStore';
import { format, parseISO } from 'date-fns';
import Close2Icon from '../icons/Close2Icon';
// /* eslint-disable react-hooks/exhaustive-deps */
// /* eslint-disable @typescript-eslint/no-explicit-any */
// import { ChangeEvent, useEffect, useState } from 'react';
// import api from '../../utils/api';
// import Button from '../Button';
// import IProject from '../../types/IProject';
// import useModalStore from '../../stores/useModalStore';
// import { format, parseISO } from 'date-fns';
// import Close2Icon from '../icons/Close2Icon';
interface EditProjectModalProps {
projectId: string;
}
// interface EditProjectModalProps {
// projectId: string;
// }
function EditProjectModal({ projectId }: EditProjectModalProps) {
const [project, setProject] = useState<IProject>({
title: '',
company: '',
city: '',
image: '',
releaseDate: '2023-01-01',
devices: [],
});
// function EditProjectModal({ projectId }: EditProjectModalProps) {
// const [project, setProject] = useState<IProject>({
// title: '',
// company: '',
// city: '',
// image: '',
// releaseDate: '2023-01-01',
// devices: [],
// });
const [file, setFile] = useState<File>();
const [previewFile, setPreviewFile] = useState<string>();
const [setModal] = useModalStore((state) => [state.setModal]);
// const [file, setFile] = useState<File>();
// const [previewFile, setPreviewFile] = useState<string>();
// const [setModal] = useModalStore((state) => [state.setModal]);
function handleChangeFile(e: ChangeEvent<HTMLInputElement>) {
if (!e.target.files) return;
// function handleChangeFile(e: ChangeEvent<HTMLInputElement>) {
// if (!e.target.files) return;
const targetFile = e.target.files[0];
// const targetFile = e.target.files[0];
setFile(targetFile);
setPreviewFile(URL.createObjectURL(targetFile));
}
// setFile(targetFile);
// setPreviewFile(URL.createObjectURL(targetFile));
// }
async function uploadFile() {
if (!file) return;
// async function uploadFile() {
// if (!file) return;
const formData = new FormData();
formData.append('file', file);
// const formData = new FormData();
// formData.append('file', file);
try {
const { file }: { file: string } = await api
.post('upload', { body: formData })
.json();
// try {
// const { file }: { file: string } = await api
// .post('upload', { body: formData })
// .json();
setProject((prev) => ({
...prev,
image: file,
}));
} catch (error) {
if (error instanceof Error) {
alert(`Error: ${error.message}`);
}
}
}
// setProject((prev) => ({
// ...prev,
// image: file,
// }));
// } catch (error) {
// if (error instanceof Error) {
// alert(`Error: ${error.message}`);
// }
// }
// }
async function updateProject() {
try {
await api.put(`projects/${projectId}`, { json: { ...project } });
} catch (error) {
if (error instanceof Error) {
alert(`Error: ${error.message}`);
}
}
}
// async function updateProject() {
// try {
// await api.put(`projects/${projectId}`, { json: { ...project } });
// } catch (error) {
// if (error instanceof Error) {
// alert(`Error: ${error.message}`);
// }
// }
// }
async function handleSubmit(e: ChangeEvent<HTMLFormElement>) {
e.preventDefault();
// async function handleSubmit(e: ChangeEvent<HTMLFormElement>) {
// e.preventDefault();
await updateProject();
setModal(null);
window.location.reload();
}
// await updateProject();
// setModal(null);
// window.location.reload();
// }
async function getProject() {
try {
const project: IProject = await api.get(`projects/${projectId}`).json();
project.releaseDate = format(parseISO(project.releaseDate), 'yyyy-MM-dd');
// async function getProject() {
// try {
// const project: IProject = await api.get(`projects/${projectId}`).json();
// project.releaseDate = format(parseISO(project.releaseDate), 'yyyy-MM-dd');
setProject(project);
} catch (error) {
if (error instanceof Error) {
alert(`Error: ${error.message}`);
}
}
}
// setProject(project);
// } catch (error) {
// if (error instanceof Error) {
// alert(`Error: ${error.message}`);
// }
// }
// }
useEffect(() => {
uploadFile();
}, [file]);
// useEffect(() => {
// uploadFile();
// }, [file]);
useEffect(() => {
getProject();
}, []);
// useEffect(() => {
// getProject();
// }, []);
return (
<div className='bg-white shadow-lg text-black p-8 rounded-xl flex flex-col gap-4'>
<div className='flex justify-between items-center border-b border-[#ccc] pb-4 gap-4'>
<p className='text-xl'>Редактирование проекта</p>
<button
onClick={() => setModal(null)}
className='p-2 hover:bg-white hover:bg-opacity-10 transition-colors rounded-full'
>
<Close2Icon />
</button>
</div>
<form
onSubmit={handleSubmit}
className='grid grid-cols-2 gap-4 w-[512px]'
>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Название</label>
<input
autoFocus
required
type='text'
placeholder='Название'
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
value={project.title}
onChange={(e) =>
setProject((prev) => ({ ...prev, title: e.target.value }))
}
/>
</div>
// return (
// <div className='flex flex-col gap-4 p-8 text-black bg-white shadow-lg rounded-xl'>
// <div className='flex justify-between items-center border-b border-[#ccc] pb-4 gap-4'>
// <p className='text-xl'>Редактирование проекта</p>
// <button
// onClick={() => setModal(null)}
// className='p-2 transition-colors rounded-full hover:bg-white hover:bg-opacity-10'
// >
// <Close2Icon />
// </button>
// </div>
// <form
// onSubmit={handleSubmit}
// className='grid grid-cols-2 gap-4 w-[512px]'
// >
// <div className='flex flex-col gap-1'>
// <label className='text-sm'>Название</label>
// <input
// autoFocus
// required
// type='text'
// placeholder='Название'
// className='px-3 py-2 border rounded-lg outline-none border-neutral-500'
// value={project.title}
// onChange={(e) =>
// setProject((prev) => ({ ...prev, title: e.target.value }))
// }
// />
// </div>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Компания</label>
<input
required
type='text'
placeholder='Компания'
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
value={project.company}
onChange={(e) =>
setProject((prev) => ({ ...prev, company: e.target.value }))
}
/>
</div>
// <div className='flex flex-col gap-1'>
// <label className='text-sm'>Компания</label>
// <input
// required
// type='text'
// placeholder='Компания'
// className='px-3 py-2 border rounded-lg outline-none border-neutral-500'
// value={project.company}
// onChange={(e) =>
// setProject((prev) => ({ ...prev, company: e.target.value }))
// }
// />
// </div>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Город</label>
<input
required
type='text'
placeholder='Город'
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
value={project.city}
onChange={(e) =>
setProject((prev) => ({ ...prev, city: e.target.value }))
}
/>
</div>
// <div className='flex flex-col gap-1'>
// <label className='text-sm'>Город</label>
// <input
// required
// type='text'
// placeholder='Город'
// className='px-3 py-2 border rounded-lg outline-none border-neutral-500'
// value={project.city}
// onChange={(e) =>
// setProject((prev) => ({ ...prev, city: e.target.value }))
// }
// />
// </div>
<label className='relative border border-dashed border-neutral-500 px-3 py-2 hover:bg-opacity-10 hover:bg-black cursor-pointer rounded-lg flex flex-col gap-2'>
<input
type='file'
accept='image/*'
className='absolute opacity-0'
onChange={handleChangeFile}
/>
<p className='truncate'>
{file ? file.name : 'Выберите изображение'}
</p>
// <label className='relative flex flex-col gap-2 px-3 py-2 border border-dashed rounded-lg cursor-pointer border-neutral-500 hover:bg-opacity-10 hover:bg-black'>
// <input
// type='file'
// accept='image/*'
// className='absolute opacity-0'
// onChange={handleChangeFile}
// />
// <p className='truncate'>
// {file ? file.name : 'Выберите изображение'}
// </p>
{previewFile ? (
<img src={previewFile} alt='' />
) : (
project.image && (
<img
src={`${import.meta.env.VITE_API_URL}/upload/${project.image}`}
alt=''
/>
)
)}
</label>
// {previewFile ? (
// <img src={previewFile} alt='' />
// ) : (
// project.image && (
// <img
// src={`${import.meta.env.VITE_API_URL}/upload/${project.image}`}
// alt=''
// />
// )
// )}
// </label>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Стадия</label>
<select
required
value={project.stage || ''}
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
onChange={(e) =>
setProject((prev) => ({ ...prev, stage: +e.target.value }))
}
>
<option value='' disabled>
Выберите стадию
</option>
<option value={1}>1</option>
<option value={2}>2</option>
<option value={3}>3</option>
<option value={4}>4</option>
<option value={5}>5</option>
<option value={6}>6</option>
</select>
</div>
// <div className='flex flex-col gap-1'>
// <label className='text-sm'>Стадия</label>
// <select
// required
// value={project.stage || ''}
// className='px-3 py-2 border rounded-lg outline-none border-neutral-500'
// onChange={(e) =>
// setProject((prev) => ({ ...prev, stage: +e.target.value }))
// }
// >
// <option value='' disabled>
// Выберите стадию
// </option>
// <option value={1}>1</option>
// <option value={2}>2</option>
// <option value={3}>3</option>
// <option value={4}>4</option>
// <option value={5}>5</option>
// <option value={6}>6</option>
// </select>
// </div>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Дата релиза</label>
<input
type='date'
required
value={project.releaseDate}
onChange={(e) =>
setProject((prev) => ({
...prev,
releaseDate: e.target.value,
}))
}
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
/>
</div>
// <div className='flex flex-col gap-1'>
// <label className='text-sm'>Дата релиза</label>
// <input
// type='date'
// required
// value={project.releaseDate}
// onChange={(e) =>
// setProject((prev) => ({
// ...prev,
// releaseDate: e.target.value,
// }))
// }
// className='px-3 py-2 border rounded-lg outline-none border-neutral-500'
// />
// </div>
<div className='flex flex-col gap-1'>
<p className='text-sm'>Девайсы</p>
<div className=''>
<label className='flex items-center gap-2'>
<input
type='checkbox'
checked={project.devices!.includes('stream')}
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, 'stream'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== 'stream'
),
}));
}
}}
/>
<span>Stream</span>
</label>
// <div className='flex flex-col gap-1'>
// <p className='text-sm'>Девайсы</p>
// <div className=''>
// <label className='flex items-center gap-2'>
// <input
// type='checkbox'
// checked={project.devices!.includes('stream')}
// onChange={(e) => {
// if (e.target.checked) {
// setProject((prev) => ({
// ...prev,
// devices: [...prev.devices!, 'stream'],
// }));
// } else {
// setProject((prev) => ({
// ...prev,
// devices: prev.devices!.filter(
// (device) => device !== 'stream'
// ),
// }));
// }
// }}
// />
// <span>Stream</span>
// </label>
<label className='flex items-center gap-2'>
<input
type='checkbox'
checked={project.devices!.includes('touch')}
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, 'touch'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== 'touch'
),
}));
}
}}
/>
<span>Touch</span>
</label>
// <label className='flex items-center gap-2'>
// <input
// type='checkbox'
// checked={project.devices!.includes('touch')}
// onChange={(e) => {
// if (e.target.checked) {
// setProject((prev) => ({
// ...prev,
// devices: [...prev.devices!, 'touch'],
// }));
// } else {
// setProject((prev) => ({
// ...prev,
// devices: prev.devices!.filter(
// (device) => device !== 'touch'
// ),
// }));
// }
// }}
// />
// <span>Touch</span>
// </label>
<label className='flex items-center gap-2'>
<input
type='checkbox'
checked={project.devices!.includes('mobile')}
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, 'mobile'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== 'mobile'
),
}));
}
}}
/>
<span>Mobile</span>
</label>
// <label className='flex items-center gap-2'>
// <input
// type='checkbox'
// checked={project.devices!.includes('mobile')}
// onChange={(e) => {
// if (e.target.checked) {
// setProject((prev) => ({
// ...prev,
// devices: [...prev.devices!, 'mobile'],
// }));
// } else {
// setProject((prev) => ({
// ...prev,
// devices: prev.devices!.filter(
// (device) => device !== 'mobile'
// ),
// }));
// }
// }}
// />
// <span>Mobile</span>
// </label>
<label className='flex items-center gap-2'>
<input
type='checkbox'
checked={project.devices!.includes('vr')}
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, 'vr'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== 'vr'
),
}));
}
}}
/>
<span>VR</span>
</label>
</div>
</div>
// <label className='flex items-center gap-2'>
// <input
// type='checkbox'
// checked={project.devices!.includes('vr')}
// onChange={(e) => {
// if (e.target.checked) {
// setProject((prev) => ({
// ...prev,
// devices: [...prev.devices!, 'vr'],
// }));
// } else {
// setProject((prev) => ({
// ...prev,
// devices: prev.devices!.filter(
// (device) => device !== 'vr'
// ),
// }));
// }
// }}
// />
// <span>VR</span>
// </label>
// </div>
// </div>
<div className='col-span-full flex justify-end'>
<Button className='text-white outline-none'>
Сохранить изменения
</Button>
</div>
</form>
</div>
);
}
// <div className='flex justify-end col-span-full'>
// <Button className='text-white outline-none'>
// Сохранить изменения
// </Button>
// </div>
// </form>
// </div>
// );
// }
export default EditProjectModal;
// export default EditProjectModal;
+100 -100
View File
@@ -1,108 +1,108 @@
import { useEffect, useState } from "react";
import api from "../utils/api";
import IProject from "../types/IProject";
import ProjectCard from "../components/ProjectCard";
import Button from "../components/Button";
import useModalStore from "../stores/useModalStore";
import CreateProjectModal from "../components/modals/CreateProjectModal";
import ModalContainer from "../components/ModalContainer";
import EditProjectModal from "../components/modals/EditProjectModal";
import DeleteProjectModal from "../components/modals/DeleteProjectModal";
// import { useEffect, useState } from "react";
// import api from "../utils/api";
// import IProject from "../types/IProject";
// import ProjectCard from "../components/ProjectCard";
// import Button from "../components/Button";
// import useModalStore from "../stores/useModalStore";
// import CreateProjectModal from "../components/modals/CreateProjectModal";
// import ModalContainer from "../components/ModalContainer";
// import EditProjectModal from "../components/modals/EditProjectModal";
// import DeleteProjectModal from "../components/modals/DeleteProjectModal";
function ProjectsPage() {
const [projects, setProjects] = useState<IProject[]>([]);
const [setModal] = useModalStore((state) => [state.setModal]);
// function ProjectsPage() {
// const [projects, setProjects] = useState<IProject[]>([]);
// const [setModal] = useModalStore((state) => [state.setModal]);
async function getProjects() {
try {
const projects: IProject[] = await api.get("projects").json();
// async function getProjects() {
// try {
// const projects: IProject[] = await api.get("projects").json();
setProjects(projects);
} catch (error) {
if (error instanceof Error) {
alert(`Error: ${error.message}`);
}
}
}
// setProjects(projects);
// } catch (error) {
// if (error instanceof Error) {
// alert(`Error: ${error.message}`);
// }
// }
// }
function handleClickCreateProject() {
setModal(<CreateProjectModal />);
}
// function handleClickCreateProject() {
// setModal(<CreateProjectModal />);
// }
useEffect(() => {
getProjects();
}, []);
// useEffect(() => {
// getProjects();
// }, []);
return (
<div className="min-h-screen">
<div className="fixed top-0 left-0 z-10 p-4 bg-[#14161F] border-b border-white border-opacity-10 w-full shadow-2xl">
<Button onClick={handleClickCreateProject}>Добавить проект</Button>
</div>
<div className="conatiner mx-auto 2xl:px-10 xl:px-8 sm:px-6 px-4 2xl:max-w-screen-2xl mt-20">
<div className="relative py-8 flex flex-col gap-8">
<div className="grid grid-cols-3 gap-4">
{projects.map((project, index) => (
<div key={index} className="relative">
<ProjectCard {...project} />
<div className="absolute top-0 right-0 p-4 flex gap-2">
<button
onClick={() =>
setModal(<EditProjectModal projectId={project.id!} />)
}
className="group relative p-2 bg-black bg-opacity-60 hover:bg-opacity-70 transition-opacity rounded-full"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"
/>
</svg>
<span className="pointer-events-none group-hover:opacity-100 opacity-0 transition-opacity absolute -bottom-[90%] left-[50%] -translate-x-[50%] bg-neutral-900 px-2 py-1 text-sm rounded-lg">
Редактировать
</span>
</button>
<button
onClick={() =>
setModal(<DeleteProjectModal projectId={project.id!} />)
}
className="group relative p-2 bg-black bg-opacity-60 hover:bg-opacity-70 transition-opacity rounded-full"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
<span className="pointer-events-none group-hover:opacity-100 opacity-0 transition-opacity absolute -bottom-[90%] left-[50%] -translate-x-[50%] bg-neutral-900 px-2 py-1 text-sm rounded-lg">
Удалить
</span>
</button>
</div>
</div>
))}
</div>
</div>
</div>
// return (
// <div className="min-h-screen">
// <div className="fixed top-0 left-0 z-10 p-4 bg-[#14161F] border-b border-white border-opacity-10 w-full shadow-2xl">
// <Button onClick={handleClickCreateProject}>Добавить проект</Button>
// </div>
// <div className="px-4 mx-auto mt-20 conatiner 2xl:px-10 xl:px-8 sm:px-6 2xl:max-w-screen-2xl">
// <div className="relative flex flex-col gap-8 py-8">
// <div className="grid grid-cols-3 gap-4">
// {projects.map((project, index) => (
// <div key={index} className="relative">
// <ProjectCard {...project} />
// <div className="absolute top-0 right-0 flex gap-2 p-4">
// <button
// onClick={() =>
// setModal(<EditProjectModal projectId={project.id!} />)
// }
// className="relative p-2 transition-opacity bg-black rounded-full group bg-opacity-60 hover:bg-opacity-70"
// >
// <svg
// xmlns="http://www.w3.org/2000/svg"
// fill="none"
// viewBox="0 0 24 24"
// strokeWidth={1.5}
// stroke="currentColor"
// className="w-6 h-6"
// >
// <path
// strokeLinecap="round"
// strokeLinejoin="round"
// d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"
// />
// </svg>
// <span className="pointer-events-none group-hover:opacity-100 opacity-0 transition-opacity absolute -bottom-[90%] left-[50%] -translate-x-[50%] bg-neutral-900 px-2 py-1 text-sm rounded-lg">
// Редактировать
// </span>
// </button>
// <button
// onClick={() =>
// setModal(<DeleteProjectModal projectId={project.id!} />)
// }
// className="relative p-2 transition-opacity bg-black rounded-full group bg-opacity-60 hover:bg-opacity-70"
// >
// <svg
// xmlns="http://www.w3.org/2000/svg"
// fill="none"
// viewBox="0 0 24 24"
// strokeWidth={1.5}
// stroke="currentColor"
// className="w-6 h-6"
// >
// <path
// strokeLinecap="round"
// strokeLinejoin="round"
// d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
// />
// </svg>
// <span className="pointer-events-none group-hover:opacity-100 opacity-0 transition-opacity absolute -bottom-[90%] left-[50%] -translate-x-[50%] bg-neutral-900 px-2 py-1 text-sm rounded-lg">
// Удалить
// </span>
// </button>
// </div>
// </div>
// ))}
// </div>
// </div>
// </div>
<ModalContainer />
</div>
);
}
// <ModalContainer />
// </div>
// );
// }
export default ProjectsPage;
// export default ProjectsPage;