updated mappool
This commit is contained in:
parent
aff1f090c1
commit
074fa4d666
38
package-lock.json
generated
38
package-lock.json
generated
@ -86,6 +86,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
||||
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"regenerator-runtime": "^0.14.0"
|
||||
},
|
||||
@ -123,7 +124,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
|
||||
"integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.1.1",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
@ -1578,6 +1578,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
|
||||
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
@ -2068,7 +2069,6 @@
|
||||
"integrity": "sha512-zeMXFn8zQ+UkjK4ws0RiOC9EWByyW1CcVmLe+2rQocXRsGEDxUCwPEIVgpsGcLHS/P8JkT0oa3839BRABS0oPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
}
|
||||
@ -2086,7 +2086,6 @@
|
||||
"integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@ -2184,7 +2183,6 @@
|
||||
"integrity": "sha512-H+vqmWwT5xoNrXqWs/fesmssOW70gxFlgcMlYcBaWNPIEWDgLa4W9nkSPmhuOgLnXq9QYgkZ31fhDyLhleCsAg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.30.1",
|
||||
"@typescript-eslint/types": "8.30.1",
|
||||
@ -2618,7 +2616,6 @@
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
@ -3138,7 +3135,6 @@
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz",
|
||||
"integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@kurkle/color": "^0.3.0"
|
||||
},
|
||||
@ -3288,6 +3284,7 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@ -3428,7 +3425,6 @@
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/kossnocorp"
|
||||
@ -3885,7 +3881,6 @@
|
||||
"integrity": "sha512-eh/jxIEJyZrvbWRe4XuVclLPDYSYYYgLy5zXGGxD6j8zjSAxFEzI2fL/8xNq6O2yKqVt+eF2YhV+hxjV6UKXwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
@ -4060,7 +4055,6 @@
|
||||
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.8",
|
||||
@ -5409,6 +5403,7 @@
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz",
|
||||
"integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/panva"
|
||||
}
|
||||
@ -5837,6 +5832,7 @@
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
@ -6024,7 +6020,6 @@
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.3.0.tgz",
|
||||
"integrity": "sha512-k0MgP6BsK8cZ73wRjMazl2y2UcXj49ZXLDEgx6BikWuby/CN+nh81qFFI16edgd7xYpe/jj2OZEIwCoqnzz0bQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@next/env": "15.3.0",
|
||||
"@swc/counter": "0.1.3",
|
||||
@ -6317,7 +6312,8 @@
|
||||
"version": "0.9.15",
|
||||
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
|
||||
"integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
@ -6334,6 +6330,7 @@
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
|
||||
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
@ -6462,6 +6459,7 @@
|
||||
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz",
|
||||
"integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^10.13.0 || >=12.0.0"
|
||||
}
|
||||
@ -6748,6 +6746,7 @@
|
||||
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz",
|
||||
"integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"pretty-format": "^3.8.0"
|
||||
},
|
||||
@ -6778,7 +6777,8 @@
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
|
||||
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "6.16.2",
|
||||
@ -6787,7 +6787,6 @@
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@prisma/config": "6.16.2",
|
||||
"@prisma/engines": "6.16.2"
|
||||
@ -6904,7 +6903,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
|
||||
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -6924,7 +6922,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
@ -6994,7 +6991,8 @@
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
|
||||
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.4",
|
||||
@ -7651,8 +7649,7 @@
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz",
|
||||
"integrity": "sha512-1ZIUqtPITFbv/DxRmDr5/agPqJwF69d24m9qmM1939TJehgY539CtzeZRjbLt5G6fSy/7YqqYsfvoTEw9xUI2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
@ -7709,7 +7706,6 @@
|
||||
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@ -7935,7 +7931,6 @@
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@ -8191,7 +8186,8 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC"
|
||||
"license": "ISC",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
|
||||
@ -27,6 +27,8 @@ type CardProps = {
|
||||
}
|
||||
|
||||
export default function Card({
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
align = 'center',
|
||||
maxWidth = 'lg',
|
||||
@ -60,20 +62,28 @@ export default function Card({
|
||||
<div
|
||||
style={style}
|
||||
className={[
|
||||
'flex flex-col rounded-xl border border-gray-200 bg-white shadow-2xs p-3',
|
||||
// ⬇️ kein Außen-Padding; Box misst inkl. Border (verhindert Extra-Scroll)
|
||||
'box-border flex flex-col rounded-xl border border-gray-200 bg-white shadow-2xs overflow-hidden max-h-full',
|
||||
'dark:bg-neutral-800 dark:border-neutral-700 dark:shadow-neutral-700/70',
|
||||
alignClasses,
|
||||
widthClasses[maxWidth],
|
||||
// wenn parent `h-full` gibt, füllt die Card die Höhe
|
||||
'min-h-0'
|
||||
].join(' ')}
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
'flex-1 min-h-0 p-3',
|
||||
bodyScrollable ? 'overflow-auto' : 'overflow-hidden',
|
||||
className ?? '',
|
||||
].join(' ')}
|
||||
>
|
||||
{children}
|
||||
{(title || description) && (
|
||||
<div className="px-4 py-3 border-b border-gray-200/70 dark:border-neutral-700/60">
|
||||
{title && <h3 className="text-base font-semibold">{title}</h3>}
|
||||
{description && (
|
||||
<p className="mt-0.5 text-sm text-gray-500 dark:text-neutral-400">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1 min-h-0 overflow-auto">
|
||||
<div className="p-4 sm:p-6">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -80,6 +80,12 @@ type ChartProps = {
|
||||
radarIcons?: string[] | Record<string, string>
|
||||
radarIconSize?: number // px
|
||||
radarIconOffset?: number // Abstand über dem äußersten Ring (Skaleneinheiten)
|
||||
|
||||
/** Unter jedem Radar-Icon den Label-Text rendern */
|
||||
radarIconLabels?: boolean // default: false
|
||||
radarIconLabelFont?: string // CSS canvas font, z.B. '12px Inter, sans-serif'
|
||||
radarIconLabelColor?: string // z.B. '#fff'
|
||||
radarIconLabelMargin?: number // px Abstand unterhalb des Icons
|
||||
}
|
||||
|
||||
export default function Chart({
|
||||
@ -108,6 +114,12 @@ export default function Chart({
|
||||
radarIcons,
|
||||
radarIconSize = 28,
|
||||
radarIconOffset = 6,
|
||||
|
||||
// ⬇️ neu
|
||||
radarIconLabels = false,
|
||||
radarIconLabelFont = '12px system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Helvetica Neue, Arial, sans-serif',
|
||||
radarIconLabelColor = '#fff',
|
||||
radarIconLabelMargin = 4,
|
||||
}: ChartProps) {
|
||||
const isRadar = type === 'radar'
|
||||
const isAutoHeight = height === 'auto'
|
||||
@ -194,6 +206,27 @@ export default function Chart({
|
||||
ctx.shadowOffsetY = 1
|
||||
ctx.drawImage(img, x, y, size, size)
|
||||
ctx.restore()
|
||||
|
||||
// ⬇️ Label unter dem Icon (optional)
|
||||
if (radarIconLabels) {
|
||||
const text = label
|
||||
const tx = pos.x
|
||||
const ty = y + size + radarIconLabelMargin
|
||||
ctx.save()
|
||||
ctx.font = radarIconLabelFont
|
||||
ctx.fillStyle = radarIconLabelColor
|
||||
ctx.textAlign = 'center'
|
||||
ctx.textBaseline = 'top'
|
||||
|
||||
// leichte Kontur/Shadow für bessere Lesbarkeit
|
||||
ctx.shadowColor = 'rgba(0,0,0,0.45)'
|
||||
ctx.shadowBlur = 3
|
||||
ctx.shadowOffsetX = 0
|
||||
ctx.shadowOffsetY = 1
|
||||
|
||||
ctx.fillText(text, tx, ty)
|
||||
ctx.restore()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -204,8 +237,9 @@ export default function Chart({
|
||||
const options = useMemo(() => {
|
||||
const base: any = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: isAutoHeight,
|
||||
aspectRatio: isAutoHeight ? aspectRatio : undefined,
|
||||
maintainAspectRatio: !isAutoHeight,
|
||||
aspectRatio: !isAutoHeight ? aspectRatio : undefined,
|
||||
layout: { padding: { top: 40, right: 24, bottom: 40, left: 24 } },
|
||||
plugins: {
|
||||
legend: { display: !hideLabels, position: 'top' as const },
|
||||
title: { display: !!title, text: title },
|
||||
@ -263,15 +297,19 @@ export default function Chart({
|
||||
|
||||
// -------- Render (typsicher) --------
|
||||
const wrapperStyle: React.CSSProperties = isAutoHeight
|
||||
? { width: '100%', ...style }
|
||||
: { height: typeof height === 'number' ? height : undefined, width: '100%', ...style }
|
||||
|
||||
? { width: '100%', height: '100%', position: 'relative', ...style }
|
||||
: { width: '100%', height: typeof height === 'number' ? height : undefined, ...style }
|
||||
if (isRadar) {
|
||||
// Nur hier das streng typisierte Radar-Plugin übergeben
|
||||
const radarPlugins = radarIconsPlugin ? [radarIconsPlugin] as Plugin<'radar'>[] : undefined
|
||||
return (
|
||||
<div className={className} style={wrapperStyle}>
|
||||
<Radar data={data} options={options} plugins={radarPlugins} />
|
||||
<Radar
|
||||
data={data}
|
||||
options={options}
|
||||
plugins={radarPlugins}
|
||||
style={{ height: '100%', width: '100%' }} // <— Canvas füllt Parent
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -291,7 +329,11 @@ export default function Chart({
|
||||
|
||||
return (
|
||||
<div className={className} style={wrapperStyle}>
|
||||
<NonRadar data={data} options={options} />
|
||||
<NonRadar
|
||||
data={data}
|
||||
options={options}
|
||||
style={{ height: '100%', width: '100%' }} // <— auch für andere Typen
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -145,6 +145,29 @@ export default function CommunityMatchList({ matchType }: Props) {
|
||||
|
||||
const [now, setNow] = useState(() => Date.now())
|
||||
|
||||
const mySteamId = session?.user?.steamId
|
||||
|
||||
const isOwnMatch = useCallback((m: any) => {
|
||||
if (!mySteamId) return false
|
||||
|
||||
// a) Neues Shape: teamA.players / teamB.players -> p.user.steamId
|
||||
const inTeamA = m?.teamA?.players?.some((p: any) => p?.user?.steamId === mySteamId) ?? false
|
||||
const inTeamB = m?.teamB?.players?.some((p: any) => p?.user?.steamId === mySteamId) ?? false
|
||||
if (inTeamA || inTeamB) return true
|
||||
|
||||
// b) Manchmal flaches players-Array (falls noch vorhanden)
|
||||
const inFlat = m?.players?.some((p: any) =>
|
||||
p?.user?.steamId === mySteamId || p?.steamId === mySteamId
|
||||
) ?? false
|
||||
if (inFlat) return true
|
||||
|
||||
// c) Fallback (nur wenn du es noch möchtest): Team-Mitgliedschaft
|
||||
const byTeamMembership =
|
||||
!!session?.user?.team && (m?.teamA?.id === session.user.team || m?.teamB?.id === session.user.team)
|
||||
|
||||
return byTeamMembership
|
||||
}, [mySteamId, session?.user?.team])
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setNow(Date.now()), 1000)
|
||||
return () => clearInterval(id)
|
||||
@ -313,17 +336,20 @@ export default function CommunityMatchList({ matchType }: Props) {
|
||||
}
|
||||
|
||||
// Gruppieren
|
||||
const grouped = (() => {
|
||||
const sorted = [...matches].sort(
|
||||
const grouped = useMemo(() => {
|
||||
// optional filtern
|
||||
const base = onlyOwn ? matches.filter(isOwnMatch) : matches
|
||||
|
||||
const sorted = [...base].sort(
|
||||
(a, b) => new Date(a.demoDate).getTime() - new Date(b.demoDate).getTime(),
|
||||
);
|
||||
const map = new Map<string, Match[]>();
|
||||
)
|
||||
const map = new Map<string, Match[]>()
|
||||
for (const m of sorted) {
|
||||
const key = dateKeyInTZ(m.demoDate, userTZ);
|
||||
map.set(key, [...(map.get(key) ?? []), m]);
|
||||
const key = dateKeyInTZ(m.demoDate, userTZ)
|
||||
map.set(key, [...(map.get(key) ?? []), m])
|
||||
}
|
||||
return Array.from(map.entries());
|
||||
})();
|
||||
return Array.from(map.entries())
|
||||
}, [matches, onlyOwn, isOwnMatch, userTZ])
|
||||
|
||||
return (
|
||||
<div className="max-w-7xl mx-auto py-8 px-4 space-y-6">
|
||||
@ -365,9 +391,8 @@ export default function CommunityMatchList({ matchType }: Props) {
|
||||
const started = new Date(m.demoDate).getTime() <= Date.now()
|
||||
const unfinished = !m.winnerTeam && m.scoreA == null && m.scoreB == null
|
||||
const isLive = started && unfinished
|
||||
const isOwnTeam = !!session?.user?.team &&
|
||||
(m.teamA.id === session.user.team || m.teamB.id === session.user.team)
|
||||
const dimmed = onlyOwn && !isOwnTeam
|
||||
const isOwn = isOwnMatch(m)
|
||||
const dimmed = onlyOwn ? false : !isOwn
|
||||
|
||||
// 👇 Map-Vote Status berechnen
|
||||
const mv = getMapVoteState(m, now)
|
||||
|
||||
@ -890,29 +890,26 @@ export default function MapVotePanel({ match }: Props) {
|
||||
</main>
|
||||
) : (
|
||||
// Winrate-Tab
|
||||
<div className="w-full max-w-xl justify-self-center">
|
||||
<div className="rounded-lg border border-white/10 bg-neutral-900/40 backdrop-blur-sm p-3" style={{ height: 360 }}>
|
||||
<div className="h-full w-full max-w-xl justify-self-center">
|
||||
<Chart
|
||||
type="radar"
|
||||
labels={activeMapLabels} // z.B. ['Ancient','Anubis', ...]
|
||||
height={"auto"}
|
||||
datasets={[
|
||||
{ label: teamLeft?.name ?? 'Links', data: teamRadarLeft,
|
||||
borderColor: 'rgba(54,162,235,0.9)', backgroundColor: 'rgba(54,162,235,0.20)', borderWidth: 2 },
|
||||
{ label: teamRight?.name ?? 'Rechts', data: teamRadarRight,
|
||||
borderColor: 'rgba(255,99,132,0.9)', backgroundColor: 'rgba(255,99,132,0.20)', borderWidth: 2 },
|
||||
]}
|
||||
// Icons (array passt 1:1 zu labels) ODER als Mapping label->url
|
||||
radarIcons={activeMapKeys.map(k => `/assets/img/mapicons/map_icon_${k}.svg`)}
|
||||
radarIconSize={28}
|
||||
radarIconOffset={24}
|
||||
// Skala + Offset (weil wir werte um +20 schieben)
|
||||
radarMax={120}
|
||||
radarStepSize={20}
|
||||
radarAddRingOffset
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1 min-h-0 grid place-items-center">
|
||||
<div className="w-full max-w-xl h-[55vh] min-h-[420px]"> {/* feste Container-Höhe */}
|
||||
<Chart
|
||||
type="radar"
|
||||
labels={activeMapLabels}
|
||||
height="auto"
|
||||
datasets={[ /* ... */ ]}
|
||||
radarIcons={activeMapKeys.map(k => `/assets/img/mapicons/map_icon_${k}.svg`)}
|
||||
radarIconSize={32}
|
||||
radarIconOffset={20}
|
||||
radarHideTicks={true}
|
||||
// ⬇️ Mapnamen unter den Icons zeigen
|
||||
radarIconLabels={true}
|
||||
radarIconLabelFont="12px Inter, system-ui, sans-serif"
|
||||
radarIconLabelColor="#ffffff"
|
||||
radarIconLabelMargin={4}
|
||||
radarMax={120}
|
||||
radarStepSize={20}
|
||||
radarAddRingOffset={false} // <- aus, wenn du echte % zeigst
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -423,9 +423,21 @@ export function MatchDetails({match, initialNow}: { match: Match; initialNow: nu
|
||||
// Tabellen-Layout
|
||||
const ColGroup = () => (
|
||||
<colgroup>
|
||||
<col style={{width: '24%'}} />
|
||||
<col style={{width: '8%'}} />
|
||||
{Array.from({length: 13}).map((_, i) => <col key={i} style={{width: '5.666%'}} />)}
|
||||
<col style={{ width: '25%' }} /> {/* Spieler */}
|
||||
<col style={{ width: '8.5%' }} /> {/* Rank */}
|
||||
<col style={{ width: '7%' }} /> {/* Aim */}
|
||||
<col style={{ width: '5%' }} /> {/* K */}
|
||||
<col style={{ width: '5%' }} /> {/* A */}
|
||||
<col style={{ width: '5%' }} /> {/* D */}
|
||||
<col style={{ width: '4%' }} /> {/* 1K */}
|
||||
<col style={{ width: '4%' }} /> {/* 2K */}
|
||||
<col style={{ width: '4%' }} /> {/* 3K */}
|
||||
<col style={{ width: '4%' }} /> {/* 4K */}
|
||||
<col style={{ width: '4%' }} /> {/* 5K */}
|
||||
<col style={{ width: '5%' }} /> {/* K/D */}
|
||||
<col style={{ width: '5%' }} /> {/* ADR */}
|
||||
<col style={{ width: '7%' }} /> {/* HS% */}
|
||||
<col style={{ width: '7.5%' }} /> {/* Damage (↑) */}
|
||||
</colgroup>
|
||||
)
|
||||
|
||||
@ -471,7 +483,7 @@ export function MatchDetails({match, initialNow}: { match: Match; initialNow: nu
|
||||
{sorted.map((p) => (
|
||||
<Table.Row key={p.user.steamId}>
|
||||
<Table.Cell
|
||||
className="flex items-center gap-2 py-1"
|
||||
className="flex items-center"
|
||||
hoverable
|
||||
onClick={() => router.push(`/profile/${p.user.steamId}`)}
|
||||
>
|
||||
@ -523,7 +535,7 @@ export function MatchDetails({match, initialNow}: { match: Match; initialNow: nu
|
||||
<Table.Cell>{p.stats?.fiveK ?? '-'}</Table.Cell>
|
||||
<Table.Cell>{kdr(p.stats?.kills, p.stats?.deaths)}</Table.Cell>
|
||||
<Table.Cell>{adr(p.stats?.totalDamage, match.roundCount)}</Table.Cell>
|
||||
<Table.Cell>{((p.stats?.headshotPct ?? 0) * 100).toFixed(0)}%</Table.Cell>
|
||||
<Table.Cell>{((p.stats?.headshotPct ?? 0) * 100).toFixed(0)} %</Table.Cell>
|
||||
<Table.Cell>{p.stats?.totalDamage?.toFixed(0) ?? '-'}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
@ -755,7 +767,9 @@ export function MatchDetails({match, initialNow}: { match: Match; initialNow: nu
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
{match.teamA?.name || sideLabel('A')}
|
||||
{match.matchType === 'community'
|
||||
? (match.teamA?.name ?? sideLabel('A'))
|
||||
: sideLabel('A')}
|
||||
</h2>
|
||||
|
||||
{showEditA && (
|
||||
@ -799,7 +813,9 @@ export function MatchDetails({match, initialNow}: { match: Match; initialNow: nu
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
{match.teamB?.name || sideLabel('B')}
|
||||
{match.matchType === 'community'
|
||||
? (match.teamB?.name ?? sideLabel('B'))
|
||||
: sideLabel('B')}
|
||||
</h2>
|
||||
|
||||
{showEditB && (
|
||||
|
||||
@ -1,17 +1,21 @@
|
||||
'use client'
|
||||
|
||||
import {useEffect, useMemo, useRef, useState} from 'react'
|
||||
import type {RefObject} from 'react'
|
||||
import {useEffect, useRef, useState, type RefObject} from 'react'
|
||||
|
||||
export type SpyItem = { id: string; label: string }
|
||||
|
||||
type Props = {
|
||||
items: SpyItem[]
|
||||
/** Scroll-Container; wenn nicht gesetzt, wird `document` beobachtet */
|
||||
containerRef?: RefObject<HTMLElement | null>
|
||||
className?: string
|
||||
activeClassName?: string
|
||||
inactiveClassName?: string
|
||||
/** Fixer Pixel-Offset (z. B. fixe Headerhöhe). Default 0 */
|
||||
offset?: number
|
||||
/** Hash der URL synchronisieren, wenn Section aktiv wird */
|
||||
updateHash?: boolean
|
||||
/** Dauer des programmatic scrolls (ms) – sperrt solange den Observer */
|
||||
smoothMs?: number
|
||||
}
|
||||
|
||||
export default function ScrollSpyTabs({
|
||||
@ -19,73 +23,93 @@ export default function ScrollSpyTabs({
|
||||
containerRef,
|
||||
className = 'flex flex-col gap-1',
|
||||
activeClassName = 'bg-gray-100 text-gray-900 dark:bg-neutral-700 dark:text-white',
|
||||
inactiveClassName = 'text-gray-500 hover:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700'
|
||||
inactiveClassName = 'text-gray-500 hover:bg-gray-100 dark:text-neutral-400 dark:hover:bg-neutral-700',
|
||||
offset = 0,
|
||||
updateHash = false,
|
||||
smoothMs = 500,
|
||||
}: Props) {
|
||||
const [activeId, setActiveId] = useState<string>(items[0]?.id ?? '')
|
||||
const observerRef = useRef<IntersectionObserver | null>(null)
|
||||
const isProgrammaticRef = useRef(false)
|
||||
const progTimerRef = useRef<number | null>(null)
|
||||
|
||||
// Helper: sichere Query (mit CSS.escape Fallback)
|
||||
const qs = (root: Document | HTMLElement, id: string) => {
|
||||
const esc = (window as any).CSS?.escape?.(id) ?? id.replace(/([ #.;?+*~\\':"!^$[\]()=>|\/@])/g, '\\$1')
|
||||
return root.querySelector<HTMLElement>(`#${esc}`)
|
||||
}
|
||||
|
||||
// Sichtbarkeits-Beobachtung
|
||||
useEffect(() => {
|
||||
const rootEl = containerRef?.current ?? null
|
||||
const sections = items
|
||||
.map(i => (rootEl ?? document).querySelector<HTMLElement>(`#${CSS.escape(i.id)}`))
|
||||
.filter(Boolean) as HTMLElement[]
|
||||
const rootNode: Document | HTMLElement = rootEl ?? document
|
||||
|
||||
const sections = items.map(i => qs(rootNode, i.id)).filter(Boolean) as HTMLElement[]
|
||||
if (sections.length === 0) return
|
||||
|
||||
// etwas Toleranz: Sektion gilt als aktiv, wenn ~40% sichtbar sind
|
||||
observerRef.current?.disconnect()
|
||||
observerRef.current = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const visible = entries
|
||||
.filter(e => e.isIntersecting)
|
||||
.sort((a, b) => b.intersectionRatio - a.intersectionRatio)
|
||||
() => {
|
||||
if (isProgrammaticRef.current) return // während Smooth-Scroll keine Auto-Umschaltung
|
||||
|
||||
if (visible[0]) {
|
||||
const id = (visible[0].target as HTMLElement).id
|
||||
setActiveId(id)
|
||||
} else {
|
||||
// Fallback: oberste Sektion, wenn nichts “offiziell” intersected
|
||||
const first = sections.find(s => {
|
||||
const rect = s.getBoundingClientRect()
|
||||
const top = rect.top - (rootEl?.getBoundingClientRect().top ?? 0)
|
||||
return top >= -10 // nahe am Anfang
|
||||
})
|
||||
if (first) setActiveId(first.id)
|
||||
const rootTop = rootEl ? rootEl.getBoundingClientRect().top : 0
|
||||
const targetLine = rootTop + offset + 1
|
||||
|
||||
let best: { id: string; dist: number } | null = null
|
||||
for (const s of sections) {
|
||||
const top = s.getBoundingClientRect().top
|
||||
const dist = Math.abs(top - targetLine)
|
||||
const isCandidate = top <= targetLine + 1
|
||||
if (isCandidate && (best === null || dist < best.dist)) best = { id: s.id, dist }
|
||||
}
|
||||
const nextActive = best?.id ?? sections[0].id
|
||||
if (nextActive !== activeId) {
|
||||
setActiveId(nextActive)
|
||||
if (updateHash) history.replaceState(null, '', `#${nextActive}`)
|
||||
}
|
||||
},
|
||||
{
|
||||
root: rootEl ?? null,
|
||||
// top padding, damit schon etwas früher aktiv markiert wird:
|
||||
rootMargin: '-20% 0px -40% 0px',
|
||||
threshold: [0.2, 0.4, 0.6, 0.8],
|
||||
rootMargin: `-${Math.max(offset, 0)}px 0px -40% 0px`,
|
||||
threshold: [0, 0.25, 0.5, 0.75, 1],
|
||||
}
|
||||
)
|
||||
|
||||
sections.forEach((s) => observerRef.current?.observe(s))
|
||||
sections.forEach(s => observerRef.current!.observe(s))
|
||||
return () => observerRef.current?.disconnect()
|
||||
}, [items, containerRef])
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [items, containerRef, offset, updateHash])
|
||||
|
||||
const onJump = (id: string) => {
|
||||
const rootEl = containerRef?.current ?? null
|
||||
const el = (rootEl ?? document).querySelector<HTMLElement>(`#${CSS.escape(id)}`)
|
||||
const rootNode: Document | HTMLElement = rootEl ?? document
|
||||
const el = qs(rootNode, id)
|
||||
if (!el) return
|
||||
|
||||
// sofort aktiv setzen (optimistisch)
|
||||
setActiveId(id)
|
||||
if (updateHash) history.replaceState(null, '', `#${id}`)
|
||||
|
||||
// Observer kurz blockieren
|
||||
isProgrammaticRef.current = true
|
||||
if (progTimerRef.current) window.clearTimeout(progTimerRef.current)
|
||||
progTimerRef.current = window.setTimeout(() => {
|
||||
isProgrammaticRef.current = false
|
||||
}, smoothMs)
|
||||
|
||||
if (rootEl) {
|
||||
// innerhalb eines Scroll-Containers:
|
||||
const rootTop = rootEl.getBoundingClientRect().top
|
||||
const targetTop = el.getBoundingClientRect().top
|
||||
const delta = targetTop - rootTop + rootEl.scrollTop - 12 /* kleiner offset */
|
||||
rootEl.scrollTo({ top: delta, behavior: 'smooth' })
|
||||
const delta = targetTop - rootTop + rootEl.scrollTop - offset
|
||||
rootEl.scrollTo({ top: Math.max(delta, 0), behavior: 'smooth' })
|
||||
} else {
|
||||
// Fenster scrollen
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
const y = window.scrollY + el.getBoundingClientRect().top - offset
|
||||
window.scrollTo({ top: Math.max(y, 0), behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className={className} aria-label="Section navigation" role="tablist" aria-orientation="vertical">
|
||||
{items.map((it) => {
|
||||
{items.map(it => {
|
||||
const isActive = activeId === it.id
|
||||
return (
|
||||
<button
|
||||
|
||||
@ -42,9 +42,9 @@ export default async function RootLayout({children, params}: Props) {
|
||||
|
||||
return (
|
||||
<html lang={lang} suppressHydrationWarning>
|
||||
<body className={`antialiased bg-white dark:bg-black min-h-dvh ${geistSans.variable} ${geistMono.variable}`}>
|
||||
{/* ⬇️ volle Viewporthöhe + keine Fensterscroller */}
|
||||
<body className={`antialiased bg-white dark:bg-black h-dvh overflow-hidden ${geistSans.variable} ${geistMono.variable}`}>
|
||||
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
|
||||
{/* ⬇️ EIN globaler i18n-Provider um ALLES */}
|
||||
<NextIntlClientProvider locale={lang} messages={messages}>
|
||||
<Providers>
|
||||
<SSEHandler />
|
||||
@ -53,11 +53,18 @@ export default async function RootLayout({children, params}: Props) {
|
||||
<ReadyOverlayHost />
|
||||
<TelemetrySocket />
|
||||
|
||||
<div className="min-h-dvh grid grid-cols-1 sm:grid-cols-[16rem_1fr]">
|
||||
<Sidebar />
|
||||
<div className="min-w-0 flex flex-col">
|
||||
<main className="flex-1 min-w-0 overflow-hidden">
|
||||
<div className="h-full box-border p-4 sm:p-6">{children}</div>
|
||||
{/* ⬇️ Container selbst ist 100vh hoch */}
|
||||
<div className="h-dvh grid grid-cols-1 sm:grid-cols-[16rem_1fr]">
|
||||
{/* Sidebar ggf. scrollfähig machen */}
|
||||
<div className="min-h-0 overflow-y-auto">
|
||||
<Sidebar />
|
||||
</div>
|
||||
|
||||
{/* Rechte Spalte füllt Höhe; wichtig: min-h-0, damit child scrollen darf */}
|
||||
<div className="min-w-0 flex flex-col h-dvh min-h-0">
|
||||
{/* Nur HIER scrollen */}
|
||||
<main className="flex-1 min-w-0 min-h-0 overflow-auto overscroll-contain">
|
||||
<div className="h-full min-h-0 box-border p-4 sm:p-6 overscroll-contain">{children}</div>
|
||||
</main>
|
||||
<GameBannerSpacer className="hidden sm:block" />
|
||||
</div>
|
||||
@ -72,3 +79,4 @@ export default async function RootLayout({children, params}: Props) {
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@ export default function AppearanceSection() {
|
||||
const tSettings = useTranslations('settings')
|
||||
|
||||
return (
|
||||
<section id="account" className="scroll-mt-16 pb-10">
|
||||
<section id="appearance" className="scroll-mt-16 pb-10">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">{tSettings("sections.appearance.short")}</h2>
|
||||
<form className="border-t border-gray-200 dark:border-neutral-700">
|
||||
<AppearanceSettings />
|
||||
|
||||
@ -8,7 +8,7 @@ export default function UserSection() {
|
||||
const tSettings = useTranslations('settings')
|
||||
|
||||
return (
|
||||
<section id="account" className="scroll-mt-16 pb-10">
|
||||
<section id="user" className="scroll-mt-16 pb-10">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">{tSettings("sections.user.short")}</h2>
|
||||
<form className="border-t border-gray-200 dark:border-neutral-700">
|
||||
<UserSettings />
|
||||
|
||||
@ -24,8 +24,9 @@ export default function SettingsLayoutSettings({ children }: { children: React.R
|
||||
<div className="sticky top-0 pt-2">
|
||||
<ScrollSpyTabs
|
||||
items={items}
|
||||
containerRef={mainRef} // <- rechter Scroll-Container
|
||||
containerRef={mainRef}
|
||||
className="flex flex-col gap-1"
|
||||
updateHash
|
||||
/>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@ -7,11 +7,13 @@ import UserSection from './_sections/UserSection'
|
||||
|
||||
export default function SettingsPage() {
|
||||
return (
|
||||
<Card maxWidth='full'>
|
||||
<UserSection />
|
||||
<PrivacySection />
|
||||
<AccountSection />
|
||||
<AppearanceSection />
|
||||
</Card>
|
||||
<div className="h-full min-h-0">
|
||||
<Card maxWidth="full" height="100%" bodyScrollable>
|
||||
<UserSection />
|
||||
<PrivacySection />
|
||||
<AccountSection />
|
||||
<AppearanceSection />
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -125,12 +125,22 @@ export async function PUT(
|
||||
|
||||
// 1) Match-Felder zusammenbauen
|
||||
const updateData: any = {}
|
||||
if (typeof title !== 'undefined') updateData.title = title
|
||||
if (typeof matchType === 'string') updateData.matchType = matchType
|
||||
if (typeof teamAId !== 'undefined') updateData.teamAId = teamAId
|
||||
if (typeof teamBId !== 'undefined') updateData.teamBId = teamBId
|
||||
if (typeof bestOf !== 'undefined') updateData.bestOf = bestOf // <- BestOf updaten
|
||||
if (typeof title === 'string') updateData.title = title
|
||||
if (typeof matchType === 'string') updateData.matchType = matchType
|
||||
|
||||
// ⬇️ Relationen korrekt updaten
|
||||
if (typeof teamAId !== 'undefined') {
|
||||
updateData.teamA = teamAId
|
||||
? { connect: { id: teamAId } }
|
||||
: { disconnect: true }
|
||||
}
|
||||
if (typeof teamBId !== 'undefined') {
|
||||
updateData.teamB = teamBId
|
||||
? { connect: { id: teamBId } }
|
||||
: { disconnect: true }
|
||||
}
|
||||
|
||||
// Zeiten parsen
|
||||
const parsedMatchDate = parseDateOrNull(matchDate)
|
||||
if (parsedMatchDate !== undefined) updateData.matchDate = parsedMatchDate
|
||||
|
||||
@ -165,12 +175,11 @@ export async function PUT(
|
||||
if (baseDate) {
|
||||
const opensAt = voteOpensAt(baseDate, leadMinutes)
|
||||
if (!m.mapVote) {
|
||||
// Neu anlegen
|
||||
const mapPool = MAP_OPTIONS.filter(o => o.active).map(o => o.key)
|
||||
await tx.mapVote.create({
|
||||
data: {
|
||||
matchId: m.id,
|
||||
bestOf : (m.bestOf as 1|3|5) ?? 3,
|
||||
bestOf : bestOf ?? 3, // ✅
|
||||
mapPool,
|
||||
currentIdx: 0,
|
||||
locked: false,
|
||||
@ -304,7 +313,7 @@ export async function PUT(
|
||||
teamBId: updated.teamBId,
|
||||
matchDate: updated.matchDate,
|
||||
demoDate: updated.demoDate,
|
||||
bestOf: updated.bestOf,
|
||||
bestOf: updated.mapVote?.bestOf ?? 3,
|
||||
mapVote: updated.mapVote,
|
||||
}, { headers: { 'Cache-Control': 'no-store' } })
|
||||
} catch (err) {
|
||||
@ -357,7 +366,7 @@ export async function GET(
|
||||
teamBId: match.teamBId,
|
||||
matchDate: match.matchDate,
|
||||
demoDate: match.demoDate,
|
||||
bestOf: match.bestOf,
|
||||
bestOf: match.mapVote?.bestOf ?? 3,
|
||||
mapVote: {
|
||||
id: match.mapVote?.id ?? null,
|
||||
leadMinutes: match.mapVote?.leadMinutes ?? 60,
|
||||
|
||||
@ -122,17 +122,18 @@ export async function POST (req: NextRequest) {
|
||||
// 4) Match anlegen
|
||||
const newMatch = await tx.match.create({
|
||||
data: {
|
||||
teamAId,
|
||||
teamBId,
|
||||
title : safeTitle,
|
||||
description: safeDesc,
|
||||
map : safeMap,
|
||||
demoDate : plannedAt,
|
||||
bestOf : bestOfInt,
|
||||
|
||||
// Teams per relation verbinden
|
||||
teamA: { connect: { id: teamAId } },
|
||||
teamB: { connect: { id: teamBId } },
|
||||
|
||||
// Optional: falls du am Match die Kader je Seite referenzieren möchtest
|
||||
teamAUsers: aUse.length ? { connect: aUse.map(id => ({ steamId: id })) } : undefined,
|
||||
teamBUsers: bUse.length ? { connect: bUse.map(id => ({ steamId: id })) } : undefined,
|
||||
// Optional: Kader verknüpfen – nur setzen, wenn nicht leer
|
||||
...(aUse.length ? { teamAUsers: { connect: aUse.map(id => ({ steamId: id })) } } : {}),
|
||||
...(bUse.length ? { teamBUsers: { connect: bUse.map(id => ({ steamId: id })) } } : {}),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@ -370,7 +370,7 @@ const config = {
|
||||
"value": "prisma-client-js"
|
||||
},
|
||||
"output": {
|
||||
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"fromEnvVar": null
|
||||
},
|
||||
"config": {
|
||||
@ -384,7 +384,7 @@ const config = {
|
||||
}
|
||||
],
|
||||
"previewFeatures": [],
|
||||
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"isCustomOutput": true
|
||||
},
|
||||
"relativeEnvPaths": {
|
||||
|
||||
@ -371,7 +371,7 @@ const config = {
|
||||
"value": "prisma-client-js"
|
||||
},
|
||||
"output": {
|
||||
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"fromEnvVar": null
|
||||
},
|
||||
"config": {
|
||||
@ -385,7 +385,7 @@ const config = {
|
||||
}
|
||||
],
|
||||
"previewFeatures": [],
|
||||
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"isCustomOutput": true
|
||||
},
|
||||
"relativeEnvPaths": {
|
||||
|
||||
Binary file not shown.
@ -370,7 +370,7 @@ const config = {
|
||||
"value": "prisma-client-js"
|
||||
},
|
||||
"output": {
|
||||
"value": "C:\\Users\\Chris\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"value": "C:\\Users\\Rother\\fork\\ironie-nextjs\\src\\generated\\prisma",
|
||||
"fromEnvVar": null
|
||||
},
|
||||
"config": {
|
||||
@ -384,7 +384,7 @@ const config = {
|
||||
}
|
||||
],
|
||||
"previewFeatures": [],
|
||||
"sourceFilePath": "C:\\Users\\Chris\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"sourceFilePath": "C:\\Users\\Rother\\fork\\ironie-nextjs\\prisma\\schema.prisma",
|
||||
"isCustomOutput": true
|
||||
},
|
||||
"relativeEnvPaths": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user