Skip to content

Commit de09f5d

Browse files
Francisca105nalves599
authored andcommitted
Add new components for venue stands and SVG elements
1 parent 8229954 commit de09f5d

File tree

9 files changed

+674
-0
lines changed

9 files changed

+674
-0
lines changed
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
"use client";
2+
3+
import React, { useState, useEffect, useMemo } from "react";
4+
import { useSearchParams, usePathname, useRouter } from "next/navigation";
5+
import EventDayButton from "@/components/EventDayButton";
6+
import GridList from "@/components/GridList";
7+
import ZoomSvg from "@/components/svg/ZoomableSvg";
8+
import CompanyStand from "./stands/CompanyStand";
9+
import Entrances from "./stands/Entrances";
10+
import CoworkingZone from "./stands/CoworkingZone";
11+
import SessionsStands from "./stands/SessionsZone";
12+
import FoodZone from "./stands/FoodZone";
13+
14+
interface VenueStandsProps {
15+
companies: Company[];
16+
}
17+
18+
const VenueStands: React.FC<VenueStandsProps> = ({ companies }) => {
19+
const searchParams = useSearchParams();
20+
const pathname = usePathname();
21+
const router = useRouter();
22+
23+
// Extract the 'day' parameter from the URL search parameters
24+
const dayParam = searchParams.get("day");
25+
const [showingDay, setShowingDay] = useState<string | null>(null);
26+
27+
const standPositions: Record<number, { x: number; y: number }> = {
28+
1: { x: 118, y: 188 },
29+
2: { x: 118, y: 218 },
30+
3: { x: 180, y: 142 },
31+
4: { x: 209, y: 142 },
32+
5: { x: 180, y: 171 },
33+
6: { x: 209, y: 171 },
34+
7: { x: 180, y: 218 },
35+
8: { x: 209, y: 218 },
36+
9: { x: 276, y: 142 },
37+
10: { x: 305, y: 142 },
38+
11: { x: 276, y: 189 },
39+
12: { x: 305, y: 189 },
40+
13: { x: 276, y: 219 },
41+
14: { x: 305, y: 219 },
42+
15: { x: 367, y: 142 },
43+
16: { x: 396, y: 142 },
44+
17: { x: 367, y: 171 },
45+
18: { x: 396, y: 171 },
46+
19: { x: 367, y: 218 },
47+
20: { x: 396, y: 218 },
48+
21: { x: 453, y: 137 },
49+
22: { x: 453, y: 165 },
50+
23: { x: 453, y: 193 },
51+
24: { x: 453, y: 222 },
52+
};
53+
54+
const getAllUniqueDates = (companies: Company[]): string[] => {
55+
return Array.from(
56+
new Set(
57+
companies
58+
.flatMap((company) => company.stands || [])
59+
.map((stand) => stand.date)
60+
)
61+
).sort();
62+
};
63+
64+
const sortedDays = getAllUniqueDates(companies);
65+
console.log(sortedDays);
66+
67+
const companiesForSelectedDay = useMemo(() => {
68+
if (!showingDay) return [];
69+
return companies.filter((company) =>
70+
company.stands?.some((stand) => stand.date === showingDay)
71+
);
72+
}, [companies, showingDay]);
73+
74+
const getCompanyAtPosition = (standId: string): Company | null => {
75+
if (!showingDay) return null;
76+
77+
return (
78+
companiesForSelectedDay.find((company) =>
79+
company.stands?.some(
80+
(stand) => stand.date === showingDay && stand.standId === standId
81+
)
82+
) || null
83+
);
84+
};
85+
86+
const standsForSelectedDay = useMemo(() => {
87+
if (!showingDay) return [];
88+
return companies.flatMap(
89+
(company) =>
90+
company.stands?.filter((stand) => stand.date === showingDay) || []
91+
);
92+
}, [companies, showingDay]);
93+
94+
useEffect(() => {
95+
const today = new Date().toISOString().split("T")[0];
96+
const defaultDay = sortedDays.includes(today) ? today : sortedDays[0];
97+
98+
if (!showingDay) {
99+
setShowingDay(dayParam || defaultDay);
100+
}
101+
}, [dayParam, sortedDays, showingDay]);
102+
103+
const updateSearchParam = (newDay: string) => {
104+
const params = new URLSearchParams(searchParams.toString());
105+
params.set("day", newDay);
106+
router.push(`${pathname}?${params.toString()}`, { scroll: false });
107+
};
108+
109+
useEffect(() => {
110+
if (dayParam && sortedDays.includes(dayParam)) {
111+
setShowingDay(dayParam);
112+
}
113+
}, [dayParam, sortedDays]);
114+
115+
return (
116+
<div className="w-full space-y-8">
117+
<GridList>
118+
{sortedDays.map((day) => (
119+
<EventDayButton
120+
key={`event-day-${day}`}
121+
date={day}
122+
onClick={() => {
123+
setShowingDay(day);
124+
updateSearchParam(day);
125+
}}
126+
selected={showingDay === day}
127+
/>
128+
))}
129+
</GridList>
130+
131+
<ZoomSvg minZoom={0.6}>
132+
<svg viewBox="0 0 512 380" className="w-full h-auto max-w-4xl mx-auto">
133+
<Entrances />
134+
<SessionsStands />
135+
<FoodZone />
136+
<CoworkingZone />
137+
138+
{/* Company Stands */}
139+
<g id="stands">
140+
{standsForSelectedDay.map((stand) => (
141+
<CompanyStand
142+
key={`stand-${stand.standId}`}
143+
stand={stand}
144+
company={getCompanyAtPosition(stand.standId)}
145+
standPositions={standPositions}
146+
/>
147+
))}
148+
</g>
149+
</svg>
150+
</ZoomSvg>
151+
</div>
152+
);
153+
};
154+
155+
export default VenueStands;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { CompanyService } from "@/services/CompanyService";
2+
import VenueStands from "./VenueStands";
3+
4+
export default async function VenuePage() {
5+
const all_companies = await CompanyService.getCompanies();
6+
7+
function companiesNotFetched() {
8+
return (
9+
<div className="container m-auto h-full p-4">
10+
<div className="text-center">No companies found!</div>
11+
</div>
12+
);
13+
}
14+
15+
if (!all_companies || all_companies.length === 0) {
16+
return companiesNotFetched();
17+
}
18+
19+
const companies = (
20+
await Promise.all(
21+
all_companies.map(async (company) => {
22+
const detailedCompany = await CompanyService.getCompany(company.id);
23+
return detailedCompany;
24+
})
25+
)
26+
).filter(Boolean);
27+
28+
if (!companies || companies.length === 0) {
29+
return companiesNotFetched();
30+
}
31+
32+
const uniqueCompanies = Array.from(
33+
new Map(companies.map((company) => [company?.id, company])).values()
34+
).filter((company): company is Company => company !== null); // Remove nulls
35+
36+
return (
37+
<div className="container m-auto h-full">
38+
<div className="flex flex-col items-start gap-y-2 p-4 text-start text-sm">
39+
<h1 className="text-2xl font-bold">Venue</h1>
40+
</div>
41+
<VenueStands companies={uniqueCompanies} />
42+
</div>
43+
);
44+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
"use client";
2+
3+
import React from "react";
4+
import Link from "next/link";
5+
6+
interface StandProps {
7+
stand: Stand;
8+
company: Company | null;
9+
standPositions: Record<number, { x: number; y: number }>;
10+
isSelected?: boolean;
11+
className?: string;
12+
}
13+
14+
const CompanyStand: React.FC<StandProps> = ({
15+
stand,
16+
company,
17+
standPositions,
18+
isSelected = false,
19+
className = "",
20+
}) => {
21+
const standNumber = parseInt(stand.standId);
22+
const position = standPositions[standNumber];
23+
24+
if (!position) return null;
25+
26+
return (
27+
<a href={company ? `/companies/${company.id}` : "#"} className={className}>
28+
<g
29+
className={`
30+
transition-all duration-200 ease-in-out
31+
${company ? "cursor-pointer hover:opacity-80" : "cursor-default"}
32+
${isSelected ? "opacity-70" : "opacity-100"}
33+
`}
34+
>
35+
{/* Base stand rectangle */}
36+
<rect
37+
x={position.x}
38+
y={position.y}
39+
width="26"
40+
height="26"
41+
fill={isSelected ? "#E5E7EB" : "white"}
42+
stroke={isSelected ? "#9CA3AF" : "black"}
43+
strokeWidth={isSelected ? "2" : "1"}
44+
className="transition-all duration-200"
45+
/>
46+
47+
{/* Company logo or stand number */}
48+
{company?.img ? (
49+
<image
50+
href={company.img}
51+
x={position.x + 3}
52+
y={position.y + 3}
53+
width="20"
54+
height="20"
55+
preserveAspectRatio="xMidYMid meet"
56+
className={`
57+
transition-all duration-200
58+
${isSelected ? "opacity-80" : "opacity-100"}
59+
`}
60+
/>
61+
) : company?.name ? (
62+
<text
63+
x={position.x + 3}
64+
y={position.y + 15}
65+
fontSize="6"
66+
className={`
67+
${company ? "font-medium" : "font-normal"}
68+
${isSelected ? "fill-gray-600" : "fill-black"}
69+
`}
70+
>
71+
{company.name}
72+
</text>
73+
) : (
74+
<text
75+
x={position.x + (standNumber < 10 ? 9 : 6)}
76+
y={position.y + 18}
77+
fontSize="13"
78+
className={`
79+
${company ? "font-medium" : "font-normal"}
80+
${isSelected ? "fill-gray-600" : "fill-black"}
81+
`}
82+
>
83+
{standNumber}
84+
</text>
85+
)}
86+
87+
{/* Optional highlight effect for selected state */}
88+
{isSelected && (
89+
<rect
90+
x={position.x - 1}
91+
y={position.y - 1}
92+
width="28"
93+
height="28"
94+
fill="none"
95+
stroke="#4F46E5"
96+
strokeWidth="2"
97+
className="pointer-events-none"
98+
/>
99+
)}
100+
</g>
101+
</a>
102+
);
103+
};
104+
105+
export default CompanyStand;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const CoworkingZone = () => {
2+
return (
3+
<g id="zones">
4+
{/* Startup Zone */}
5+
<g id="startup">
6+
<rect
7+
x="86.3"
8+
y="270.3"
9+
width="82.1"
10+
height="92.2"
11+
fill="none"
12+
stroke="black"
13+
strokeWidth="1.5"
14+
strokeDasharray="3,5"
15+
/>
16+
<text x="104" y="310" fontSize="13">
17+
Startup
18+
</text>
19+
<text x="112" y="325" fontSize="13">
20+
zone
21+
</text>
22+
</g>
23+
24+
{/* Gaming Zone */}
25+
<g id="gaming">
26+
<rect
27+
x="168.4"
28+
y="270.3"
29+
width="101.7"
30+
height="92.2"
31+
fill="none"
32+
stroke="black"
33+
strokeWidth="1.5"
34+
strokeDasharray="3,5"
35+
/>
36+
<text x="193" y="310" fontSize="13">
37+
Gaming
38+
</text>
39+
<text x="202" y="325" fontSize="13">
40+
zone
41+
</text>
42+
</g>
43+
44+
{/* Lounge Zone */}
45+
<g id="lounge">
46+
<rect
47+
x="270.1"
48+
y="270.3"
49+
width="93.6"
50+
height="92.2"
51+
fill="none"
52+
stroke="black"
53+
strokeWidth="1.5"
54+
strokeDasharray="3,5"
55+
/>
56+
<text x="293" y="319" fontSize="13">
57+
Lounge
58+
</text>
59+
</g>
60+
</g>
61+
);
62+
};
63+
64+
export default CoworkingZone;

0 commit comments

Comments
 (0)