fix: name project and company from api

This commit is contained in:
2025-05-07 19:01:15 +05:00
parent 1ff3fb351d
commit cd73f557b5
7 changed files with 1156 additions and 501 deletions
+34 -35
View File
@@ -1,18 +1,17 @@
import { motion } from "framer-motion";
import ProgressPie from "./ProgressPie";
import TouchScreenIcon from "./icons/TouchScreenIcon";
import VRIcon from "./icons/VRIcon";
import MobileIcon from "./icons/MobileIcon";
import IProject from "../types/IProject";
import { format } from "date-fns";
import { motion } from 'framer-motion';
import ProgressPie from './ProgressPie';
import TouchScreenIcon from './icons/TouchScreenIcon';
import VRIcon from './icons/VRIcon';
import MobileIcon from './icons/MobileIcon';
import IProject from '../types/IProject';
import { format } from 'date-fns';
function ProjectCard({
name,
company,
city,
englishTitle,
englishCity,
image,
stage = 6,
releaseDate = format(new Date(), "yyyy-MM-dd"),
releaseDate = format(new Date(), 'yyyy-MM-dd'),
devices = [],
}: IProject) {
const stagePercentage = Math.round((100 / 6) * stage);
@@ -21,36 +20,36 @@ function ProjectCard({
<motion.div
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="group relative aspect-[4/3] p-4 flex items-end overflow-hidden"
className='group relative aspect-[4/3] p-4 flex items-end overflow-hidden'
>
<div
className="group-hover:scale-110 transition-transform duration-500 absolute top-0 left-0 w-full h-full bg-cover bg-center bg-no-repeat"
className='absolute top-0 left-0 w-full h-full transition-transform duration-500 bg-center bg-no-repeat bg-cover group-hover:scale-110'
style={{
backgroundImage: `url(${import.meta.env.VITE_API_URL}/upload/${image})`,
backgroundImage: `url(${import.meta.env.VITE_CLOUD_URL}/${image})`,
}}
></div>
<div className="absolute top-0 left-0 w-full h-full bg-gradient-card"></div>
<div className="relative flex flex-col gap-4">
<div className='absolute top-0 left-0 w-full h-full bg-gradient-card'></div>
<div className='relative flex flex-col gap-4'>
<div>
<p className="2xl:text-2xl text-xl font-gilroy font-medium">{name}</p>
<p className="2xl:text-sm text-xs">
{company !== "-" && `${company},`} {city}
<p className='text-xl font-medium 2xl:text-2xl font-gilroy'>
{englishTitle}
</p>
<p className='text-xs 2xl:text-sm'>{englishCity}</p>
</div>
<div className="flex gap-2">
<div className='flex gap-2'>
{stage < 6 ? (
<div className="bg-[#14161F] px-3 py-2 rounded-full w-fit flex items-center gap-1">
<p className="font-gilroy font-medium leading-none">
<div className='bg-[#14161F] px-3 py-2 rounded-full w-fit flex items-center gap-1'>
<p className='font-medium leading-none font-gilroy'>
{stagePercentage}%
</p>
<ProgressPie value={stagePercentage} />
</div>
) : (
<div className="bg-gradient py-2.5 px-4 rounded-full">
<p className="font-gilroy font-medium leading-none">
<div className='bg-gradient py-2.5 px-4 rounded-full'>
<p className='font-medium leading-none font-gilroy'>
{new Date(releaseDate).getFullYear()}
</p>
</div>
@@ -58,26 +57,26 @@ function ProjectCard({
{devices.length > 0 && (
<>
{devices.includes("stream") && (
<div className="bg-[#14161F] px-3 py-2 rounded-full w-fit flex items-center gap-2">
<div className="w-2 h-2 bg-gradient rounded-full"></div>
<p className="font-gilroy font-semibold leading-none text-gradient">
{devices.includes('stream') && (
<div className='bg-[#14161F] px-3 py-2 rounded-full w-fit flex items-center gap-2'>
<div className='w-2 h-2 rounded-full bg-gradient'></div>
<p className='font-semibold leading-none font-gilroy text-gradient'>
Stream
</p>
</div>
)}
{devices.includes("touch") && (
<div className="bg-[#14161F] p-2 rounded-full w-fit flex items-center gap-2">
{devices.includes('touch') && (
<div className='bg-[#14161F] p-2 rounded-full w-fit flex items-center gap-2'>
<TouchScreenIcon />
</div>
)}
{devices.includes("mobile") && (
<div className="bg-[#14161F] p-2 rounded-full w-fit flex items-center gap-2">
{devices.includes('mobile') && (
<div className='bg-[#14161F] p-2 rounded-full w-fit flex items-center gap-2'>
<MobileIcon />
</div>
)}
{devices.includes("vr") && (
<div className="bg-[#14161F] p-2 rounded-full w-fit flex items-center gap-2">
{devices.includes('vr') && (
<div className='bg-[#14161F] p-2 rounded-full w-fit flex items-center gap-2'>
<VRIcon />
</div>
)}
@@ -1,20 +1,20 @@
/* 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";
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>({
name: "",
company: "",
city: "",
image: "",
releaseDate: format(new Date(), "yyyy-MM-dd"),
title: '',
company: '',
city: '',
image: '',
releaseDate: format(new Date(), 'yyyy-MM-dd'),
devices: [],
});
@@ -35,11 +35,11 @@ function CreateProjectModal() {
if (!file) return;
const formData = new FormData();
formData.append("file", file);
formData.append('file', file);
try {
const { file }: { file: string } = await api
.post("upload", { body: formData })
.post('upload', { body: formData })
.json();
setProject((prev) => ({
@@ -55,7 +55,7 @@ function CreateProjectModal() {
async function createProject() {
try {
await api.post("projects", { json: { ...project } });
await api.post('projects', { json: { ...project } });
setModal(null);
} catch (error) {
@@ -78,42 +78,42 @@ function CreateProjectModal() {
}, [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>
<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"
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]"
className='grid grid-cols-2 gap-4 w-[512px]'
>
<div className="flex flex-col gap-1">
<label className="text-sm">Название</label>
<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.name}
type='text'
placeholder='Название'
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
value={project.title}
onChange={(e) =>
setProject((prev) => ({ ...prev, name: e.target.value }))
setProject((prev) => ({ ...prev, title: e.target.value }))
}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm">Компания</label>
<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"
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 }))
@@ -121,13 +121,13 @@ function CreateProjectModal() {
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm">Город</label>
<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"
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 }))
@@ -135,32 +135,32 @@ function CreateProjectModal() {
/>
</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">
<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"
type='file'
accept='image/*'
className='absolute opacity-0'
onChange={handleChangeFile}
/>
<p className="truncate">
{file ? file.name : "Выберите изображение"}
<p className='truncate'>
{file ? file.name : 'Выберите изображение'}
</p>
{previewFile && <img src={previewFile} alt="" />}
{previewFile && <img src={previewFile} alt='' />}
</label>
<div className="flex flex-col gap-1">
<label className="text-sm">Стадия</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"
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 value='' disabled>
Выберите стадию
</option>
<option value={1}>1</option>
@@ -172,10 +172,10 @@ function CreateProjectModal() {
</select>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm">Дата релиза</label>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Дата релиза</label>
<input
type="date"
type='date'
required
value={project.releaseDate}
onChange={(e) =>
@@ -184,27 +184,27 @@ function CreateProjectModal() {
releaseDate: e.target.value,
}))
}
className="border border-neutral-500 px-3 py-2 rounded-lg outline-none"
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
/>
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Девайсы</p>
<div className="">
<label className="flex items-center gap-2">
<div className='flex flex-col gap-1'>
<p className='text-sm'>Девайсы</p>
<div className=''>
<label className='flex items-center gap-2'>
<input
type="checkbox"
type='checkbox'
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, "stream"],
devices: [...prev.devices!, 'stream'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== "stream"
(device) => device !== 'stream'
),
}));
}
@@ -213,20 +213,20 @@ function CreateProjectModal() {
<span>Stream</span>
</label>
<label className="flex items-center gap-2">
<label className='flex items-center gap-2'>
<input
type="checkbox"
type='checkbox'
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, "touch"],
devices: [...prev.devices!, 'touch'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== "touch"
(device) => device !== 'touch'
),
}));
}
@@ -235,20 +235,20 @@ function CreateProjectModal() {
<span>Touch</span>
</label>
<label className="flex items-center gap-2">
<label className='flex items-center gap-2'>
<input
type="checkbox"
type='checkbox'
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, "mobile"],
devices: [...prev.devices!, 'mobile'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== "mobile"
(device) => device !== 'mobile'
),
}));
}
@@ -257,20 +257,20 @@ function CreateProjectModal() {
<span>Mobile</span>
</label>
<label className="flex items-center gap-2">
<label className='flex items-center gap-2'>
<input
type="checkbox"
type='checkbox'
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, "vr"],
devices: [...prev.devices!, 'vr'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== "vr"
(device) => device !== 'vr'
),
}));
}
@@ -281,8 +281,8 @@ function CreateProjectModal() {
</div>
</div>
<div className="col-span-full flex justify-end">
<Button className="text-white outline-none">Добавить проект</Button>
<div className='col-span-full flex justify-end'>
<Button className='text-white outline-none'>Добавить проект</Button>
</div>
</form>
</div>
@@ -1,12 +1,12 @@
/* 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";
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;
@@ -14,11 +14,11 @@ interface EditProjectModalProps {
function EditProjectModal({ projectId }: EditProjectModalProps) {
const [project, setProject] = useState<IProject>({
name: "",
company: "",
city: "",
image: "",
releaseDate: "2023-01-01",
title: '',
company: '',
city: '',
image: '',
releaseDate: '2023-01-01',
devices: [],
});
@@ -39,11 +39,11 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
if (!file) return;
const formData = new FormData();
formData.append("file", file);
formData.append('file', file);
try {
const { file }: { file: string } = await api
.post("upload", { body: formData })
.post('upload', { body: formData })
.json();
setProject((prev) => ({
@@ -78,7 +78,7 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
async function getProject() {
try {
const project: IProject = await api.get(`projects/${projectId}`).json();
project.releaseDate = format(parseISO(project.releaseDate), "yyyy-MM-dd");
project.releaseDate = format(parseISO(project.releaseDate), 'yyyy-MM-dd');
setProject(project);
} catch (error) {
@@ -97,42 +97,42 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
}, []);
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>
<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"
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]"
className='grid grid-cols-2 gap-4 w-[512px]'
>
<div className="flex flex-col gap-1">
<label className="text-sm">Название</label>
<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.name}
type='text'
placeholder='Название'
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
value={project.title}
onChange={(e) =>
setProject((prev) => ({ ...prev, name: e.target.value }))
setProject((prev) => ({ ...prev, title: e.target.value }))
}
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm">Компания</label>
<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"
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 }))
@@ -140,13 +140,13 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
/>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm">Город</label>
<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"
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 }))
@@ -154,38 +154,40 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
/>
</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">
<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"
type='file'
accept='image/*'
className='absolute opacity-0'
onChange={handleChangeFile}
/>
<p className="truncate">{file ? file.name : "Выберите изображение"}</p>
<p className='truncate'>
{file ? file.name : 'Выберите изображение'}
</p>
{previewFile ? (
<img src={previewFile} alt="" />
<img src={previewFile} alt='' />
) : (
project.image && (
<img
src={`${import.meta.env.VITE_API_URL}/upload/${project.image}`}
alt=""
alt=''
/>
)
)}
</label>
<div className="flex flex-col gap-1">
<label className="text-sm">Стадия</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"
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 value='' disabled>
Выберите стадию
</option>
<option value={1}>1</option>
@@ -197,10 +199,10 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
</select>
</div>
<div className="flex flex-col gap-1">
<label className="text-sm">Дата релиза</label>
<div className='flex flex-col gap-1'>
<label className='text-sm'>Дата релиза</label>
<input
type="date"
type='date'
required
value={project.releaseDate}
onChange={(e) =>
@@ -209,28 +211,28 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
releaseDate: e.target.value,
}))
}
className="border border-neutral-500 px-3 py-2 rounded-lg outline-none"
className='border border-neutral-500 px-3 py-2 rounded-lg outline-none'
/>
</div>
<div className="flex flex-col gap-1">
<p className="text-sm">Девайсы</p>
<div className="">
<label className="flex items-center gap-2">
<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")}
type='checkbox'
checked={project.devices!.includes('stream')}
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, "stream"],
devices: [...prev.devices!, 'stream'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== "stream"
(device) => device !== 'stream'
),
}));
}
@@ -239,21 +241,21 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
<span>Stream</span>
</label>
<label className="flex items-center gap-2">
<label className='flex items-center gap-2'>
<input
type="checkbox"
checked={project.devices!.includes("touch")}
type='checkbox'
checked={project.devices!.includes('touch')}
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, "touch"],
devices: [...prev.devices!, 'touch'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== "touch"
(device) => device !== 'touch'
),
}));
}
@@ -262,21 +264,21 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
<span>Touch</span>
</label>
<label className="flex items-center gap-2">
<label className='flex items-center gap-2'>
<input
type="checkbox"
checked={project.devices!.includes("mobile")}
type='checkbox'
checked={project.devices!.includes('mobile')}
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, "mobile"],
devices: [...prev.devices!, 'mobile'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== "mobile"
(device) => device !== 'mobile'
),
}));
}
@@ -285,21 +287,21 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
<span>Mobile</span>
</label>
<label className="flex items-center gap-2">
<label className='flex items-center gap-2'>
<input
type="checkbox"
checked={project.devices!.includes("vr")}
type='checkbox'
checked={project.devices!.includes('vr')}
onChange={(e) => {
if (e.target.checked) {
setProject((prev) => ({
...prev,
devices: [...prev.devices!, "vr"],
devices: [...prev.devices!, 'vr'],
}));
} else {
setProject((prev) => ({
...prev,
devices: prev.devices!.filter(
(device) => device !== "vr"
(device) => device !== 'vr'
),
}));
}
@@ -310,8 +312,8 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
</div>
</div>
<div className="col-span-full flex justify-end">
<Button className="text-white outline-none">
<div className='col-span-full flex justify-end'>
<Button className='text-white outline-none'>
Сохранить изменения
</Button>
</div>