Refactor AvailableDemos component to utilize getStreamingProjects for data retrieval, enhancing localization support. Remove unused getProjects query and related constants, and adjust project rendering logic for improved responsiveness and swipe handling.
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
@@ -0,0 +1,58 @@
|
|||||||
|
import type { IProject } from "@/types";
|
||||||
|
|
||||||
|
const BASE: Omit<
|
||||||
|
IProject,
|
||||||
|
| "id"
|
||||||
|
| "title"
|
||||||
|
| "englishTitle"
|
||||||
|
| "city"
|
||||||
|
| "englishCity"
|
||||||
|
| "buildFilename"
|
||||||
|
| "image"
|
||||||
|
> = {
|
||||||
|
description: "",
|
||||||
|
stage: 0,
|
||||||
|
releaseDate: "2026-01-01T00:00:00.000Z",
|
||||||
|
tags: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const RU_PROJECTS: IProject[] = [
|
||||||
|
{
|
||||||
|
...BASE,
|
||||||
|
id: "revolution-towers",
|
||||||
|
title: "МФК «Revolution towers»",
|
||||||
|
englishTitle: "Revolution Towers",
|
||||||
|
city: "Россия, Екатеринбург",
|
||||||
|
englishCity: "Russia, Yekaterinburg",
|
||||||
|
buildFilename: "nksJukovaDev",
|
||||||
|
image: "/img/projects/nks.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...BASE,
|
||||||
|
id: "life-residence",
|
||||||
|
title: "ЖК «Life Резиденция»",
|
||||||
|
englishTitle: "Life Residence",
|
||||||
|
city: "Россия, Тюмень",
|
||||||
|
englishCity: "Russia, Tyumen",
|
||||||
|
buildFilename: "lifeResidence",
|
||||||
|
image: "/img/projects/liferes.jpg",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const EN_PROJECTS: IProject[] = [
|
||||||
|
{
|
||||||
|
...BASE,
|
||||||
|
id: "upside-towers",
|
||||||
|
title: "Upside Towers",
|
||||||
|
englishTitle: "Upside Towers",
|
||||||
|
city: "Russia, Moscow",
|
||||||
|
englishCity: "Russia, Moscow",
|
||||||
|
buildFilename: "upsideTowersDevEn",
|
||||||
|
image: "/img/projects/upside.jpg",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Demo projects for the streaming section, selected by UI language. */
|
||||||
|
export function getStreamingProjects(i18nLanguage: string): IProject[] {
|
||||||
|
return i18nLanguage.startsWith("ru") ? RU_PROJECTS : EN_PROJECTS;
|
||||||
|
}
|
||||||
@@ -1,30 +1,26 @@
|
|||||||
import { useRef, useState, type MouseEvent as ReactMouseEvent } from "react";
|
import { useMemo, useRef, useState, type MouseEvent as ReactMouseEvent } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import BR from "@/components/Layout/LineBreak";
|
import BR from "@/components/Layout/LineBreak";
|
||||||
import {
|
import { getStreamingProjects } from "@/data/streamingProjects";
|
||||||
REMOTE_DEMO_TAG,
|
|
||||||
useGetProjectsQuery,
|
|
||||||
} from "@/queries/getProjects";
|
|
||||||
import { StreamingProject } from "./StreamingProject";
|
import { StreamingProject } from "./StreamingProject";
|
||||||
import { useSwipeable } from "react-swipeable";
|
import { useSwipeable } from "react-swipeable";
|
||||||
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
import { useMediaQueries } from "@/hooks/useMediaQueries";
|
||||||
|
|
||||||
export default function AvailableDemos() {
|
export default function AvailableDemos() {
|
||||||
const { t } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const { isMd } = useMediaQueries();
|
const { isMd } = useMediaQueries();
|
||||||
const { data: streamingProjects } = useGetProjectsQuery(REMOTE_DEMO_TAG);
|
const projects = useMemo(
|
||||||
|
() => getStreamingProjects(i18n.language),
|
||||||
|
[i18n.language]
|
||||||
|
);
|
||||||
const [current, setCurrent] = useState(0);
|
const [current, setCurrent] = useState(0);
|
||||||
const projects = streamingProjects ?? [];
|
|
||||||
|
|
||||||
const slideCount = Math.min(projects.length + 1, 4);
|
const slideCount = projects.length + 1;
|
||||||
const handlers = useSwipeable({
|
const handlers = useSwipeable({
|
||||||
onSwipedLeft: () =>
|
onSwipedLeft: () =>
|
||||||
setCurrent((prev) => (prev + 1) % Math.max(slideCount, 1)),
|
setCurrent((prev) => (prev + 1) % Math.max(slideCount, 1)),
|
||||||
onSwipedRight: () =>
|
onSwipedRight: () =>
|
||||||
setCurrent(
|
setCurrent((prev) => (prev + projects.length) % Math.max(slideCount, 1)),
|
||||||
(prev) =>
|
|
||||||
(prev + Math.min(projects.length, 4)) % Math.max(slideCount, 1)
|
|
||||||
),
|
|
||||||
trackMouse: true,
|
trackMouse: true,
|
||||||
preventScrollOnSwipe: true,
|
preventScrollOnSwipe: true,
|
||||||
touchEventOptions: { passive: false },
|
touchEventOptions: { passive: false },
|
||||||
@@ -65,7 +61,7 @@ export default function AvailableDemos() {
|
|||||||
className="grid lg:hidden md:hidden grid-cols-4 gap-3 px-5 [scrollbar-width:none] relative max-md:aspect-[340/344] [transform-style:preserve-3d] items-stretch mb-[5.556vw]"
|
className="grid lg:hidden md:hidden grid-cols-4 gap-3 px-5 [scrollbar-width:none] relative max-md:aspect-[340/344] [transform-style:preserve-3d] items-stretch mb-[5.556vw]"
|
||||||
{...handlers}
|
{...handlers}
|
||||||
>
|
>
|
||||||
{projects.slice(0, 3).map((project, index, { length }) => (
|
{projects.map((project, index, { length }) => (
|
||||||
<StreamingProject
|
<StreamingProject
|
||||||
key={project.id}
|
key={project.id}
|
||||||
{...project}
|
{...project}
|
||||||
@@ -112,12 +108,12 @@ export default function AvailableDemos() {
|
|||||||
<div
|
<div
|
||||||
ref={sliderRef}
|
ref={sliderRef}
|
||||||
onMouseDown={isMd ? onSliderMouseDown : undefined}
|
onMouseDown={isMd ? onSliderMouseDown : undefined}
|
||||||
className="max-md:hidden flex md:-mx-[2.604vw] md:w-[calc(100%+5.208vw)] md:px-[2.604vw] lg:gap-[0.833vw] md:gap-[1.563vw] lg:h-[27.5vw] md:h-[51.563vw] md:overflow-x-scroll hide-scrollbars max-md:cursor-grab active:cursor-grabbing select-none touch-pan-x"
|
className="max-md:hidden flex md:-mx-[2.604vw] md:w-[calc(100%+5.208vw)] md:px-[2.604vw] lg:gap-[0.833vw] md:gap-[1.563vw] lg:h-[27.5vw] md:h-[51.563vw] md:overflow-x-scroll hide-scrollbars lg:overflow-x-visible max-md:cursor-grab active:cursor-grabbing lg:cursor-default select-none touch-pan-x"
|
||||||
>
|
>
|
||||||
{projects.slice(0, 3).map((project, index, { length }) => (
|
{projects.map((project, index, { length }) => (
|
||||||
<div
|
<div
|
||||||
key={project.id}
|
key={project.id}
|
||||||
className={`w-full ${index === 0 ? "flex-auto" : "flex-1"}`}
|
className="w-full min-w-0 flex-1 basis-0 shrink"
|
||||||
>
|
>
|
||||||
<StreamingProject
|
<StreamingProject
|
||||||
{...project}
|
{...project}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import { api, hasApiConfigured } from "@/lib/api";
|
|
||||||
import type { IProject } from "@/types";
|
|
||||||
import { queryKeys } from "@/queries/keys";
|
|
||||||
import { queryOptions, useQuery } from "@tanstack/react-query";
|
|
||||||
|
|
||||||
/** Тег фильтра на стороне API (как в CMS). */
|
|
||||||
export const REMOTE_DEMO_TAG = "Удаленная демонстрация";
|
|
||||||
|
|
||||||
function releaseTimestamp(p: IProject): number {
|
|
||||||
const t = Date.parse(p.releaseDate);
|
|
||||||
return Number.isNaN(t) ? 0 : t;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useGetProjectsQuery(tags?: string | string[]) {
|
|
||||||
const tagList =
|
|
||||||
tags && tags.length > 0
|
|
||||||
? Array.isArray(tags)
|
|
||||||
? tags
|
|
||||||
: [tags]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return useQuery(
|
|
||||||
queryOptions({
|
|
||||||
queryKey: queryKeys.projectsWithTags(tagList),
|
|
||||||
queryFn: () => {
|
|
||||||
if (tagList.length === 0) {
|
|
||||||
return api.get("projects").json<IProject[]>();
|
|
||||||
}
|
|
||||||
const qs = tagList
|
|
||||||
.map((tag) => `tags=${encodeURIComponent(tag)}`)
|
|
||||||
.join("&");
|
|
||||||
return api.get(`projects?${qs}`).json<IProject[]>();
|
|
||||||
},
|
|
||||||
enabled: hasApiConfigured,
|
|
||||||
select:
|
|
||||||
tags === REMOTE_DEMO_TAG ||
|
|
||||||
(Array.isArray(tags) &&
|
|
||||||
tags.length === 1 &&
|
|
||||||
tags[0] === REMOTE_DEMO_TAG)
|
|
||||||
? (data) =>
|
|
||||||
[...data]
|
|
||||||
.sort((a, b) => releaseTimestamp(b) - releaseTimestamp(a))
|
|
||||||
.slice(0, 3)
|
|
||||||
: undefined,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
export const queryKeys = {
|
export const queryKeys = {
|
||||||
projects: ["projects"] as const,
|
|
||||||
projectsWithTags: (tags: string[]) => ["projects", ...tags] as const,
|
|
||||||
countryCode: ["countryCode"] as const,
|
countryCode: ["countryCode"] as const,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user