Bulk commit: Stand ende 22.01.
This commit is contained in:
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env.local
|
||||||
11
.env.production
Normal file
11
.env.production
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
VITE_BACKEND_URI="PRODUCTION_BACKEND_URI"
|
||||||
|
VITE_RPC_URI="PRODUCTION_RPC_URI"
|
||||||
|
# VITE_BACKEND_URI="https://vt-api.kocoder.xyz"
|
||||||
|
# VITE_RPC_URI="https://vt-rpc.kocoder.xyz"
|
||||||
|
|
||||||
|
OIDC_USE_MOCK=false
|
||||||
|
OIDC_ISSUER_URI=https://keycloak.kocoder.xyz/realms/che
|
||||||
|
OIDC_CLIENT_ID=eventory
|
||||||
|
|
||||||
|
VITE_ENVIRONMENT="PRODUCTION_ENVIRONMENT"
|
||||||
|
|
||||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
FROM node:24.11.1-trixie-slim AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN npm i --legacy-peer-deps
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:24.11.1-trixie AS runner
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=builder /app/.output .
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD [ "node", "./server/index.mjs" ]
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
"prefix": ""
|
"prefix": ""
|
||||||
},
|
},
|
||||||
|
"iconLibrary": "lucide",
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "@/components",
|
"components": "@/components",
|
||||||
"utils": "@/lib/utils",
|
"utils": "@/lib/utils",
|
||||||
@@ -17,5 +18,7 @@
|
|||||||
"lib": "@/lib",
|
"lib": "@/lib",
|
||||||
"hooks": "@/hooks"
|
"hooks": "@/hooks"
|
||||||
},
|
},
|
||||||
"iconLibrary": "lucide"
|
"registries": {
|
||||||
}
|
"@diceui": "https://diceui.com/r/{name}.json"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
226
k3s.yaml
Normal file
226
k3s.yaml
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: vt
|
||||||
|
labels:
|
||||||
|
name: vt
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: vt-fe
|
||||||
|
namespace: vt
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: vt-fe
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: vt-fe
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: vt-fe
|
||||||
|
image: git.kocoder.xyz/kocoded/vt-fe:2025120501
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
env:
|
||||||
|
- name: BACKEND_URI
|
||||||
|
value: "https://vt-api.kocoder.xyz"
|
||||||
|
- name: RPC_URI
|
||||||
|
value: "https://vt-rpc.kocoder.xyz"
|
||||||
|
- name: OIDC_USE_MOCK
|
||||||
|
value: "false"
|
||||||
|
- name: OIDC_ISSUER_URI
|
||||||
|
value: https://keycloak.kocoder.xyz/realms/che
|
||||||
|
- name: OIDC_CLIENT_ID
|
||||||
|
value: eventory
|
||||||
|
- name: VITE_ENVIRONMENT
|
||||||
|
value: "development"
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: vt-be
|
||||||
|
namespace: vt
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: vt-be
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: vt-be
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: vt-be
|
||||||
|
image: git.kocoder.xyz/kocoded/vt-be:2025120302
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
env:
|
||||||
|
- name: CLIENT_ID
|
||||||
|
value: "golang-vt-backend"
|
||||||
|
- name: CLIENT_SECRET
|
||||||
|
value: "awumIoacqNmwKTxRilQSM9cDmA7xA0j0"
|
||||||
|
- name: BACKEND_URI
|
||||||
|
value: "https://vt-api.kocoder.xyz"
|
||||||
|
- name: FRONTEND_URI
|
||||||
|
value: "https://vt.kocoder.xyz"
|
||||||
|
- name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
|
||||||
|
value: "https://otel.kocoder.xyz/v1/traces"
|
||||||
|
- name: OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
|
||||||
|
value: "https://otel.kocoder.xyz/v1/metrics"
|
||||||
|
- name: OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
|
||||||
|
value: "https://otel.kocoder.xyz/v1/logs"
|
||||||
|
- name: DB_DSN
|
||||||
|
value: "host=10.1.0.2 user=vt password=20a1c7809cd065bc5afe7c36fde26abf625316c8a83cc841b435c9acf3619b1f dbname=vt port=5432 sslmode=prefer TimeZone=Europe/Vienna"
|
||||||
|
- name: VALKEY_HOST
|
||||||
|
value: "10.1.0.2"
|
||||||
|
- name: VALKEY_PORT
|
||||||
|
value: "6379"
|
||||||
|
- name: VALKEY_USER
|
||||||
|
value: "default"
|
||||||
|
- name: VALKEY_PASS
|
||||||
|
value: "Konsti2007!"
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
name: "api"
|
||||||
|
- containerPort: 3002
|
||||||
|
name: "rpc"
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: vt-fe
|
||||||
|
namespace: vt
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: vt-fe
|
||||||
|
ports:
|
||||||
|
- port: 3000
|
||||||
|
targetPort: 3000
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: vt-be
|
||||||
|
namespace: vt
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: vt-be
|
||||||
|
ports:
|
||||||
|
- port: 3000
|
||||||
|
targetPort: 3000
|
||||||
|
- port: 3002
|
||||||
|
targetPort: 3002
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: vt-kocoder-xyz-prod
|
||||||
|
namespace: vt
|
||||||
|
spec:
|
||||||
|
secretName: vt-tls
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt-prod
|
||||||
|
kind: ClusterIssuer
|
||||||
|
commonName: "vt.kocoder.xyz"
|
||||||
|
dnsNames:
|
||||||
|
- "vt.kocoder.xyz"
|
||||||
|
- "vt-api.kocoder.xyz"
|
||||||
|
- "vt-rpc.kocoder.xyz"
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: vt-fe
|
||||||
|
namespace: vt
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- kind: Rule
|
||||||
|
match: Host(`vt.kocoder.xyz`)
|
||||||
|
observability:
|
||||||
|
accessLogs: true
|
||||||
|
metrics: true
|
||||||
|
tracing: true
|
||||||
|
priority: 10
|
||||||
|
services:
|
||||||
|
- kind: Service
|
||||||
|
name: vt-fe
|
||||||
|
namespace: vt
|
||||||
|
passHostHeader: true
|
||||||
|
port: 3000
|
||||||
|
responseForwarding:
|
||||||
|
flushInterval: 1ms
|
||||||
|
scheme: http
|
||||||
|
sticky:
|
||||||
|
cookie:
|
||||||
|
httpOnly: true
|
||||||
|
name: cookie
|
||||||
|
secure: true
|
||||||
|
strategy: wrr
|
||||||
|
weight: 10
|
||||||
|
- kind: Rule
|
||||||
|
match: Host(`vt-api.kocoder.xyz`)
|
||||||
|
observability:
|
||||||
|
accessLogs: true
|
||||||
|
metrics: true
|
||||||
|
tracing: true
|
||||||
|
priority: 10
|
||||||
|
services:
|
||||||
|
- kind: Service
|
||||||
|
name: vt-be
|
||||||
|
namespace: vt
|
||||||
|
passHostHeader: true
|
||||||
|
port: 3000
|
||||||
|
responseForwarding:
|
||||||
|
flushInterval: 1ms
|
||||||
|
scheme: http
|
||||||
|
sticky:
|
||||||
|
cookie:
|
||||||
|
httpOnly: true
|
||||||
|
name: cookie
|
||||||
|
secure: true
|
||||||
|
strategy: wrr
|
||||||
|
weight: 10
|
||||||
|
- kind: Rule
|
||||||
|
match: Host(`vt-rpc.kocoder.xyz`)
|
||||||
|
observability:
|
||||||
|
accessLogs: true
|
||||||
|
metrics: true
|
||||||
|
tracing: true
|
||||||
|
priority: 10
|
||||||
|
services:
|
||||||
|
- kind: Service
|
||||||
|
name: vt-be
|
||||||
|
namespace: vt
|
||||||
|
passHostHeader: true
|
||||||
|
port: 3002
|
||||||
|
responseForwarding:
|
||||||
|
flushInterval: 1ms
|
||||||
|
scheme: http
|
||||||
|
sticky:
|
||||||
|
cookie:
|
||||||
|
httpOnly: true
|
||||||
|
name: cookie
|
||||||
|
secure: true
|
||||||
|
strategy: wrr
|
||||||
|
weight: 10
|
||||||
|
tls:
|
||||||
|
secretName: vt-tls
|
||||||
11786
package-lock.json
generated
11786
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
161
package.json
161
package.json
@@ -4,21 +4,27 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev --port 3001 --host",
|
"dev": "vite dev --port 3001 --host",
|
||||||
"start": "node .output/server/index.mjs",
|
"start": "node dist/server/server.js",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"serve": "vite preview",
|
"serve": "NODE_ENV=production node ./server.js",
|
||||||
|
"dev:server": "node ./server.js",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"format": "prettier",
|
"format": "prettier",
|
||||||
"check": "prettier --write . && eslint --fix"
|
"check": "prettier --write . && eslint --fix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@bitnoi.se/react-scheduler": "^0.3.1",
|
||||||
|
"@connectrpc/connect": "^2.1.0",
|
||||||
|
"@connectrpc/connect-query": "^2.2.0",
|
||||||
|
"@connectrpc/connect-web": "^2.1.0",
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/modifiers": "^9.0.0",
|
"@dnd-kit/modifiers": "^9.0.0",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@dragdroptouch/drag-drop-touch": "^2.0.3",
|
"@dragdroptouch/drag-drop-touch": "^2.0.3",
|
||||||
"@faker-js/faker": "^9.6.0",
|
"@faker-js/faker": "^10.1.0",
|
||||||
"@radix-ui/react-avatar": "^1.1.10",
|
"@radix-ui/react-avatar": "^1.1.11",
|
||||||
"@radix-ui/react-checkbox": "^1.3.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-collapsible": "^1.1.12",
|
"@radix-ui/react-collapsible": "^1.1.12",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
@@ -34,81 +40,100 @@
|
|||||||
"@radix-ui/react-toggle": "^1.1.10",
|
"@radix-ui/react-toggle": "^1.1.10",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.11",
|
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@t3-oss/env-core": "^0.12.0",
|
"@t3-oss/env-core": "^0.13.8",
|
||||||
"@tailwindcss/vite": "^4.0.6",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@tanstack/match-sorter-utils": "^8.19.4",
|
"@tanstack/match-sorter-utils": "^8.19.4",
|
||||||
"@tanstack/react-devtools": "^0.2.2",
|
"@tanstack/nitro-v2-vite-plugin": "^1.139.0",
|
||||||
"@tanstack/react-form": "^1.0.0",
|
"@tanstack/react-devtools": "^0.8",
|
||||||
"@tanstack/react-query": "^5.66.5",
|
"@tanstack/react-form": "^1.23.8",
|
||||||
"@tanstack/react-query-devtools": "^5.84.2",
|
"@tanstack/react-query": "^5.90.7",
|
||||||
"@tanstack/react-router": "^1.130.2",
|
"@tanstack/react-query-devtools": "^5.90.2",
|
||||||
"@tanstack/react-router-devtools": "^1.131.5",
|
"@tanstack/react-router": "^1.134.13",
|
||||||
"@tanstack/react-router-ssr-query": "^1.131.7",
|
"@tanstack/react-router-devtools": "^1.134.13",
|
||||||
"@tanstack/react-start": "^1.131.7",
|
"@tanstack/react-router-ssr-query": "^1.134.13",
|
||||||
"@tanstack/react-store": "^0.7.0",
|
"@tanstack/react-start": "^1.134.14",
|
||||||
|
"@tanstack/react-store": "^0.8.0",
|
||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@tanstack/react-virtual": "^3.13.12",
|
"@tanstack/react-virtual": "^3.13.12",
|
||||||
"@tanstack/router-plugin": "^1.121.2",
|
"@tanstack/router-plugin": "^1.134.14",
|
||||||
"@tanstack/store": "^0.7.0",
|
"@tanstack/store": "^0.8.0",
|
||||||
"@tanstack/zod-adapter": "^1.133.36",
|
"@tanstack/zod-adapter": "^1.134.13",
|
||||||
"@tiptap/extension-blockquote": "^3.4.2",
|
"@tiptap/extension-blockquote": "^3.10.3",
|
||||||
"@tiptap/extension-bold": "^3.4.2",
|
"@tiptap/extension-bold": "^3.10.3",
|
||||||
"@tiptap/extension-bullet-list": "^3.4.2",
|
"@tiptap/extension-bullet-list": "^3.10.3",
|
||||||
"@tiptap/extension-code": "^3.4.2",
|
"@tiptap/extension-code": "^3.10.3",
|
||||||
"@tiptap/extension-code-block": "^3.4.2",
|
"@tiptap/extension-code-block": "^3.10.3",
|
||||||
"@tiptap/extension-details": "^3.4.2",
|
"@tiptap/extension-details": "^3.10.3",
|
||||||
"@tiptap/extension-emoji": "^3.4.2",
|
"@tiptap/extension-emoji": "^3.10.3",
|
||||||
"@tiptap/extension-hard-break": "^3.4.2",
|
"@tiptap/extension-hard-break": "^3.10.3",
|
||||||
"@tiptap/extension-heading": "^3.4.2",
|
"@tiptap/extension-heading": "^3.10.3",
|
||||||
"@tiptap/extension-highlight": "^3.4.2",
|
"@tiptap/extension-highlight": "^3.10.3",
|
||||||
"@tiptap/extension-horizontal-rule": "^3.4.2",
|
"@tiptap/extension-horizontal-rule": "^3.10.3",
|
||||||
"@tiptap/extension-italic": "^3.4.2",
|
"@tiptap/extension-italic": "^3.10.3",
|
||||||
"@tiptap/extension-link": "^3.4.2",
|
"@tiptap/extension-link": "^3.10.3",
|
||||||
"@tiptap/extension-list-item": "^3.4.2",
|
"@tiptap/extension-list-item": "^3.10.3",
|
||||||
"@tiptap/extension-mathematics": "^3.4.2",
|
"@tiptap/extension-mathematics": "^3.10.3",
|
||||||
"@tiptap/extension-mention": "^3.4.2",
|
"@tiptap/extension-mention": "^3.10.3",
|
||||||
"@tiptap/extension-ordered-list": "^3.4.2",
|
"@tiptap/extension-ordered-list": "^3.10.3",
|
||||||
"@tiptap/extension-paragraph": "^3.4.2",
|
"@tiptap/extension-paragraph": "^3.10.3",
|
||||||
"@tiptap/extension-strike": "^3.4.2",
|
"@tiptap/extension-strike": "^3.10.3",
|
||||||
"@tiptap/extension-subscript": "^3.4.2",
|
"@tiptap/extension-subscript": "^3.10.3",
|
||||||
"@tiptap/extension-superscript": "^3.4.2",
|
"@tiptap/extension-superscript": "^3.10.3",
|
||||||
"@tiptap/extension-table": "^3.4.2",
|
"@tiptap/extension-table": "^3.10.3",
|
||||||
"@tiptap/extension-task-item": "^3.4.2",
|
"@tiptap/extension-task-item": "^3.10.3",
|
||||||
"@tiptap/extension-task-list": "^3.4.2",
|
"@tiptap/extension-task-list": "^3.10.3",
|
||||||
"@tiptap/extension-text-style": "^3.4.2",
|
"@tiptap/extension-text-style": "^3.10.3",
|
||||||
"@tiptap/extension-underline": "^3.4.2",
|
"@tiptap/extension-underline": "^3.10.3",
|
||||||
"@tiptap/extensions": "^3.4.2",
|
"@tiptap/extensions": "^3.10.3",
|
||||||
"@tiptap/pm": "^3.4.2",
|
"@tiptap/pm": "^3.10.3",
|
||||||
"@tiptap/react": "^3.4.2",
|
"@tiptap/react": "^3.10.3",
|
||||||
"@tiptap/starter-kit": "^3.4.2",
|
"@tiptap/starter-kit": "^3.10.3",
|
||||||
|
"@types/express": "^5.0.6",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@unpic/pixels": "^1.3.0",
|
||||||
|
"@unpic/placeholder": "^0.1.2",
|
||||||
|
"@vercel/og": "^0.8.5",
|
||||||
|
"blurhash": "^2.0.5",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
|
"compression": "^1.8.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"lucide-react": "^0.476.0",
|
"express": "^5.2.1",
|
||||||
"react": "^19.0.0",
|
"get-port": "^7.1.0",
|
||||||
|
"lucide-react": "^0.553.0",
|
||||||
|
"maplibre-gl": "^5.15.0",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
|
"nitro": "^3.0.1-alpha.1",
|
||||||
|
"node-fetch": "^3.3.2",
|
||||||
|
"oidc-spa": "^9.1.3",
|
||||||
|
"react": "^19.2.0",
|
||||||
"react-day-picker": "^9.11.1",
|
"react-day-picker": "^9.11.1",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"react-resizable-panels": "^4.4.1",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^3.0.2",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tailwindcss": "^4.0.6",
|
"tailwindcss": "^4.1.17",
|
||||||
"tw-animate-css": "^1.3.6",
|
"tw-animate-css": "^1.4.0",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"zod": "^3.24.2"
|
"webcrypto-liner": "^0.1.38",
|
||||||
|
"zod": "^4.1.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tanstack/devtools-event-client": "^0.2.1",
|
"@bufbuild/protobuf": "^2.10.1",
|
||||||
"@tanstack/eslint-config": "^0.3.0",
|
"@tanstack/devtools-event-client": "^0.3.4",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@tanstack/eslint-config": "^0.3.2",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/dom": "^10.4.1",
|
||||||
"@types/react": "^19.0.8",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@types/react-dom": "^19.0.3",
|
"@types/react": "^19.2.2",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@types/react-dom": "^19.2.2",
|
||||||
"jsdom": "^26.0.0",
|
"@vitejs/plugin-react": "^5.1.0",
|
||||||
"prettier": "^3.5.3",
|
"baseline-browser-mapping": "^2.9.14",
|
||||||
"typescript": "^5.7.2",
|
"jsdom": "^27.1.0",
|
||||||
"vite": "^6.3.5",
|
"prettier": "^3.6.2",
|
||||||
"vitest": "^3.0.5",
|
"typescript": "^5.9.3",
|
||||||
"web-vitals": "^4.2.4"
|
"vite": "^7",
|
||||||
|
"vitest": "^4.0.8",
|
||||||
|
"web-vitals": "^5.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
import { registerGlobalMiddleware } from '@tanstack/react-start'
|
|
||||||
|
|
||||||
registerGlobalMiddleware({
|
|
||||||
middleware: [],
|
|
||||||
})
|
|
||||||
27
src/components/404.tsx
Normal file
27
src/components/404.tsx
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Link } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
function NotFound() {
|
||||||
|
return (
|
||||||
|
<main className="mx-auto flex w-full max-w-7xl flex-auto flex-col justify-center px-6 py-24 sm:py-64 lg:px-8">
|
||||||
|
<p className="text-base/8 font-semibold text-indigo-600 dark:text-indigo-400">
|
||||||
|
404
|
||||||
|
</p>
|
||||||
|
<h1 className="mt-4 text-pretty text-5xl font-semibold tracking-tight text-gray-900 sm:text-6xl dark:text-white">
|
||||||
|
Page not found
|
||||||
|
</h1>
|
||||||
|
<p className="mt-6 text-pretty text-lg font-medium text-gray-500 sm:text-xl/8 dark:text-gray-400">
|
||||||
|
Sorry, we couldn’t find the page you’re looking for.
|
||||||
|
</p>
|
||||||
|
<div className="mt-10">
|
||||||
|
<Link
|
||||||
|
to="/dashboard"
|
||||||
|
className="text-sm/7 font-semibold text-indigo-600 dark:text-indigo-400"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">←</span> Back to home
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NotFound
|
||||||
@@ -24,11 +24,6 @@ import {
|
|||||||
import { NavProjects } from '@/features/Projects/components/list'
|
import { NavProjects } from '@/features/Projects/components/list'
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
user: {
|
|
||||||
name: 'shadcn',
|
|
||||||
email: 'm@example.com',
|
|
||||||
avatar: 'https://avatars.githubusercontent.com/u/124599?v=4',
|
|
||||||
},
|
|
||||||
navMain: [
|
navMain: [
|
||||||
{
|
{
|
||||||
title: 'Dashboard',
|
title: 'Dashboard',
|
||||||
@@ -89,7 +84,7 @@ const data = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Kalender',
|
title: 'Kalender',
|
||||||
url: '/kalendar',
|
url: '/calendar',
|
||||||
icon: Settings2,
|
icon: Settings2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -155,7 +150,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>
|
<SidebarFooter>
|
||||||
<NavUser user={data.user} />
|
<NavUser />
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Link } from '@tanstack/react-router'
|
import { Link, useRouterState } from '@tanstack/react-router'
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
@@ -7,10 +7,23 @@ import {
|
|||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from './ui/breadcrumb'
|
} from './ui/breadcrumb'
|
||||||
import { useCurrentMandant } from '@/features/Mandant/queries'
|
import { Fragment } from 'react/jsx-runtime'
|
||||||
|
import { useQuery } from '@connectrpc/connect-query'
|
||||||
|
import { getCurrentTenant } from '@/gen/mandant/v1/mandant-MandantService_connectquery'
|
||||||
|
|
||||||
function Breadcrumbs() {
|
function Breadcrumbs() {
|
||||||
const { data: currentMandant } = useCurrentMandant()
|
const { data: currentMandant } = useQuery(getCurrentTenant, {}, {})
|
||||||
|
|
||||||
|
const matches = useRouterState({ select: (s) => s.matches })
|
||||||
|
|
||||||
|
const breadcrumbs = matches
|
||||||
|
.filter((match) => match.context.breadcrumb)
|
||||||
|
.map(({ pathname, context }) => {
|
||||||
|
return {
|
||||||
|
title: context.breadcrumb,
|
||||||
|
path: pathname,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
@@ -23,9 +36,18 @@ function Breadcrumbs() {
|
|||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
<BreadcrumbSeparator className="hidden md:block" />
|
<BreadcrumbSeparator className="hidden md:block" />
|
||||||
<BreadcrumbItem>
|
{breadcrumbs.map(({ title, path }, index) => (
|
||||||
<BreadcrumbPage>Dashboard</BreadcrumbPage>
|
<Fragment key={path}>
|
||||||
</BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage>
|
||||||
|
<Link to={path}>{title}</Link>
|
||||||
|
</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
{index < breadcrumbs.length - 1 && ( // Conditional separator
|
||||||
|
<BreadcrumbSeparator className="hidden md:block" />
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
</BreadcrumbList>
|
</BreadcrumbList>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
"use client"
|
'use client'
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from 'react'
|
||||||
import { ChevronDownIcon } from "lucide-react"
|
import { ChevronDownIcon } from 'lucide-react'
|
||||||
import { type DateRange } from "react-day-picker"
|
import type { DateRange } from 'react-day-picker'
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from '@/components/ui/button'
|
||||||
import { Calendar } from "@/components/ui/calendar"
|
import { Calendar } from '@/components/ui/calendar'
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from '@/components/ui/label'
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover"
|
} from '@/components/ui/popover'
|
||||||
|
|
||||||
export default function Calendar23() {
|
export default function Calendar23() {
|
||||||
const [range, setRange] = React.useState<DateRange | undefined>(undefined)
|
const [range, setRange] = React.useState<DateRange | undefined>(undefined)
|
||||||
@@ -30,7 +30,7 @@ export default function Calendar23() {
|
|||||||
>
|
>
|
||||||
{range?.from && range?.to
|
{range?.from && range?.to
|
||||||
? `${range.from.toLocaleDateString()} - ${range.to.toLocaleDateString()}`
|
? `${range.from.toLocaleDateString()} - ${range.to.toLocaleDateString()}`
|
||||||
: "Select date"}
|
: 'Select date'}
|
||||||
<ChevronDownIcon />
|
<ChevronDownIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
|
|||||||
64
src/components/calendar-24.tsx
Normal file
64
src/components/calendar-24.tsx
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
import { ChevronDownIcon } from 'lucide-react'
|
||||||
|
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Calendar } from '@/components/ui/calendar'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@/components/ui/popover'
|
||||||
|
|
||||||
|
export default function Calendar24({ defaultDate }: { defaultDate: Date }) {
|
||||||
|
const [open, setOpen] = React.useState(false)
|
||||||
|
const [date, setDate] = React.useState<Date | undefined>(defaultDate)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<Label htmlFor="date-picker" className="px-1">
|
||||||
|
Date
|
||||||
|
</Label>
|
||||||
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
id="date-picker"
|
||||||
|
className="w-32 justify-between font-normal"
|
||||||
|
>
|
||||||
|
{date ? date.toLocaleDateString() : 'Select date'}
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto overflow-hidden p-0" align="start">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={date}
|
||||||
|
captionLayout="dropdown"
|
||||||
|
onSelect={(date) => {
|
||||||
|
setDate(date)
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<Label htmlFor="time-picker" className="px-1">
|
||||||
|
Time
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
type="time"
|
||||||
|
id="time-picker"
|
||||||
|
step="1"
|
||||||
|
defaultValue="10:30:00"
|
||||||
|
className="bg-background appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
52
src/components/calendar.tsx
Normal file
52
src/components/calendar.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { addDays, format, startOfWeek } from 'date-fns'
|
||||||
|
|
||||||
|
const daysInWeek = 7
|
||||||
|
const weeksInView = 5
|
||||||
|
const startOfCalendar = startOfWeek(new Date(), { weekStartsOn: 1 })
|
||||||
|
|
||||||
|
function CalendarEntry() {
|
||||||
|
const date = new Date()
|
||||||
|
const dayOfTheMonth = date.getDate() - 1
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`absolute top-10 bg-emerald-500 w-14`}
|
||||||
|
style={{
|
||||||
|
left: (((dayOfTheMonth % daysInWeek) * 1) / daysInWeek) * 100 + `%`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
a
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Calendar() {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`bg-muted grid grid-cols-${daysInWeek} gap-1 border-4 border-muted h-full w-full select-none relative`}
|
||||||
|
>
|
||||||
|
{new Array(weeksInView * daysInWeek).fill(0).map((v, iw) => {
|
||||||
|
const date = addDays(startOfCalendar, iw)
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
date.getMonth() === startOfCalendar.getMonth()
|
||||||
|
? 'bg-background'
|
||||||
|
: 'bg-background/40'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{format(date, 'dd')}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<div className="absolute top-0 left-0 w-full h-full">
|
||||||
|
<CalendarEntry />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Blank() {
|
||||||
|
return <div className="grid-cols-7"></div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Calendar
|
||||||
@@ -68,10 +68,11 @@ export function DataTable<TData, TValue>({
|
|||||||
|
|
||||||
const flatData = useMemo(() => {
|
const flatData = useMemo(() => {
|
||||||
const res = data?.pages.flatMap((page) => page.data) ?? []
|
const res = data?.pages.flatMap((page) => page.data) ?? []
|
||||||
|
// console.log(res)
|
||||||
return res
|
return res
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const totalDBRowCount = data?.pages[0]?.meta?.totalProjectsCount ?? 0
|
const totalDBRowCount = data?.pages[0]?.meta?.totalCount ?? 0
|
||||||
const totalFetched = flatData.length
|
const totalFetched = flatData.length
|
||||||
|
|
||||||
// called on scroll and possibly on mount to fetch more data as the user scrolls and reaches bottom of table
|
// called on scroll and possibly on mount to fetch more data as the user scrolls and reaches bottom of table
|
||||||
@@ -80,17 +81,20 @@ export function DataTable<TData, TValue>({
|
|||||||
if (containerRefElement) {
|
if (containerRefElement) {
|
||||||
const { scrollHeight, scrollTop, clientHeight } = containerRefElement
|
const { scrollHeight, scrollTop, clientHeight } = containerRefElement
|
||||||
// once the user has scrolled within 500px of the bottom of the table, fetch more data if we can
|
// once the user has scrolled within 500px of the bottom of the table, fetch more data if we can
|
||||||
console.log(
|
// console.log(
|
||||||
scrollHeight,
|
// scrollHeight,
|
||||||
scrollTop,
|
// scrollTop,
|
||||||
clientHeight,
|
// clientHeight,
|
||||||
isFetching,
|
// isFetching,
|
||||||
totalFetched,
|
// totalFetched,
|
||||||
totalDBRowCount,
|
// totalDBRowCount,
|
||||||
)
|
// scrollHeight - scrollTop - clientHeight < 300,
|
||||||
|
// !isFetching,
|
||||||
|
// totalFetched < totalDBRowCount,
|
||||||
|
// )
|
||||||
|
|
||||||
if (
|
if (
|
||||||
scrollHeight - scrollTop - clientHeight < 100 &&
|
scrollHeight - scrollTop - clientHeight < 300 &&
|
||||||
!isFetching &&
|
!isFetching &&
|
||||||
totalFetched < totalDBRowCount
|
totalFetched < totalDBRowCount
|
||||||
) {
|
) {
|
||||||
@@ -124,14 +128,14 @@ export function DataTable<TData, TValue>({
|
|||||||
return (
|
return (
|
||||||
<div className="h-full max-h-full" id="">
|
<div className="h-full max-h-full" id="">
|
||||||
<div className="flex items-center py-4">
|
<div className="flex items-center py-4">
|
||||||
<Input
|
{/* <Input
|
||||||
placeholder="Filter names..."
|
placeholder="Filter names..."
|
||||||
value={table.getColumn('name')?.getFilterValue() as string}
|
value={table.getColumn('name')?.getFilterValue() as string}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
table.getColumn('name')?.setFilterValue(event.target.value)
|
table.getColumn('name')?.setFilterValue(event.target.value)
|
||||||
}
|
}
|
||||||
className="max-w-sm"
|
className="max-w-sm"
|
||||||
/>
|
/> */}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="outline" className="ml-auto">
|
<Button variant="outline" className="ml-auto">
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import { createKeycloakUtils, isKeycloak } from 'oidc-spa/keycloak'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Card, CardContent } from '@/components/ui/card'
|
import { Card, CardContent } from '@/components/ui/card'
|
||||||
import { Field, FieldDescription, FieldGroup } from '@/components/ui/field'
|
import { Field, FieldDescription, FieldGroup } from '@/components/ui/field'
|
||||||
import { env } from '@/env'
|
import { useOidc } from '@/lib/oidc'
|
||||||
|
import { useNavigate } from '@tanstack/react-router'
|
||||||
|
import NotFound from './404'
|
||||||
|
|
||||||
function Logo() {
|
function Logo() {
|
||||||
return (
|
return (
|
||||||
@@ -16,6 +19,7 @@ function Logo() {
|
|||||||
stroke-width="10"
|
stroke-width="10"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
|
className='h-full w-full'
|
||||||
>
|
>
|
||||||
<line x1="100" y1="80" x2="420" y2="80" />
|
<line x1="100" y1="80" x2="420" y2="80" />
|
||||||
<polyline points="100,80 160,40 220,80 280,40 340,80 400,40 420,80" />
|
<polyline points="100,80 160,40 220,80 280,40 340,80 400,40 420,80" />
|
||||||
@@ -43,6 +47,18 @@ export function LoginForm({
|
|||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<'div'>) {
|
}: React.ComponentProps<'div'>) {
|
||||||
|
const { isUserLoggedIn, isOidcReady } = useOidc()
|
||||||
|
|
||||||
|
if (!isOidcReady || isUserLoggedIn) {
|
||||||
|
return <NotFound />
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Login className={className} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function Login({ className, ...props }: React.ComponentProps<'div'>) {
|
||||||
|
const { login } = useOidc({ assert: 'user not logged in' })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
<div className={cn('flex flex-col gap-6', className)} {...props}>
|
||||||
<Card className="overflow-hidden p-0">
|
<Card className="overflow-hidden p-0">
|
||||||
@@ -57,29 +73,26 @@ export function LoginForm({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Field className="">
|
<Field className="">
|
||||||
<Button variant="outline" type="button" asChild>
|
<Button
|
||||||
<a href={`${env.VITE_BACKEND_URI}/api/auth`}>
|
variant="outline"
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
type="button"
|
||||||
<path
|
onClick={() => login({ redirectUrl: '/dashboard' })}
|
||||||
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
|
>
|
||||||
fill="currentColor"
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
/>
|
<path
|
||||||
</svg>
|
d="M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701"
|
||||||
<span className="">Login with Che</span>
|
fill="currentColor"
|
||||||
</a>
|
/>
|
||||||
|
</svg>
|
||||||
|
<span className="">Login with Che</span>
|
||||||
</Button>
|
</Button>
|
||||||
</Field>
|
</Field>
|
||||||
<FieldDescription className="text-center">
|
|
||||||
Don't have an account? <a href="#">Sign up</a>
|
|
||||||
</FieldDescription>
|
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
</form>
|
</form>
|
||||||
<div className="bg-muted relative hidden md:block">
|
<div className="bg-muted relative hidden md:block">
|
||||||
<img
|
<div className="absolute relative top-1/2 left-1/2 h-32 w-32 max-h-32 max-w-32 -translate-x-1/2 -translate-y-1/2 opacity-10">
|
||||||
src="/placeholder.svg"
|
<Logo />
|
||||||
alt="Image"
|
</div>
|
||||||
className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -90,3 +103,4 @@ export function LoginForm({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1106
src/components/ui/kanban.tsx
Normal file
1106
src/components/ui/kanban.tsx
Normal file
File diff suppressed because it is too large
Load Diff
1305
src/components/ui/map.tsx
Normal file
1305
src/components/ui/map.tsx
Normal file
File diff suppressed because it is too large
Load Diff
54
src/components/ui/resizable.tsx
Normal file
54
src/components/ui/resizable.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { GripVerticalIcon } from "lucide-react"
|
||||||
|
import * as ResizablePrimitive from "react-resizable-panels"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function ResizablePanelGroup({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ResizablePrimitive.Group>) {
|
||||||
|
return (
|
||||||
|
<ResizablePrimitive.Group
|
||||||
|
data-slot="resizable-panel-group"
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResizablePanel({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
|
||||||
|
return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResizableHandle({
|
||||||
|
withHandle,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ResizablePrimitive.Separator> & {
|
||||||
|
withHandle?: boolean
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ResizablePrimitive.Separator
|
||||||
|
data-slot="resizable-handle"
|
||||||
|
className={cn(
|
||||||
|
"bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{withHandle && (
|
||||||
|
<div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
|
||||||
|
<GripVerticalIcon className="size-2.5" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ResizablePrimitive.Separator>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
|
||||||
10
src/env.ts
10
src/env.ts
@@ -1,4 +1,5 @@
|
|||||||
import { createEnv } from '@t3-oss/env-core'
|
import { createEnv } from '@t3-oss/env-core'
|
||||||
|
import { vite } from '@t3-oss/env-core/presets-zod'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
|
||||||
export const env = createEnv({
|
export const env = createEnv({
|
||||||
@@ -14,14 +15,17 @@ export const env = createEnv({
|
|||||||
|
|
||||||
client: {
|
client: {
|
||||||
VITE_APP_TITLE: z.string().min(1).optional(),
|
VITE_APP_TITLE: z.string().min(1).optional(),
|
||||||
VITE_BACKEND_URI: z.string(),
|
VITE_RPC_URI: z.string().optional(),
|
||||||
|
VITE_BACKEND_URI: z.string().optional(),
|
||||||
|
VITE_ENVIRONMENT: z.string().optional(),
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* What object holds the environment variables at runtime. This is usually
|
* What object holds the environment variables at runtime. This is usually
|
||||||
* `process.env` or `import.meta.env`.
|
* `process.env` or `import.meta.env`.
|
||||||
*/
|
*/
|
||||||
runtimeEnv: import.meta.env,
|
runtimeEnv: process.env || import.meta.env,
|
||||||
|
// extends: [vite()],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default, this library will feed the environment variables directly to
|
* By default, this library will feed the environment variables directly to
|
||||||
@@ -38,3 +42,5 @@ export const env = createEnv({
|
|||||||
*/
|
*/
|
||||||
emptyStringAsUndefined: true,
|
emptyStringAsUndefined: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
console.log(env)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { BadgeCheck, Bell, ChevronsUpDown, LogOut } from 'lucide-react'
|
import { BadgeCheck, Bell, ChevronsUpDown, LogOut } from 'lucide-react'
|
||||||
|
|
||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import { useProfile } from '../queries'
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -20,23 +17,14 @@ import {
|
|||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
useSidebar,
|
useSidebar,
|
||||||
} from '@/components/ui/sidebar'
|
} from '@/components/ui/sidebar'
|
||||||
import { env } from '@/env'
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { useOidc } from '@/lib/oidc'
|
||||||
|
|
||||||
export function NavUser({
|
export function NavUser() {
|
||||||
user,
|
const { logout, decodedIdToken } = useOidc()
|
||||||
}: {
|
|
||||||
user: {
|
|
||||||
name: string
|
|
||||||
email: string
|
|
||||||
avatar: string
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
const { isMobile } = useSidebar()
|
const { isMobile } = useSidebar()
|
||||||
const { data } = useProfile()
|
|
||||||
|
|
||||||
if (!data) {
|
if (!logout) return
|
||||||
return <div>Loading...</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
@@ -48,12 +36,17 @@ export function NavUser({
|
|||||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||||
>
|
>
|
||||||
<Avatar className="h-8 w-8 rounded-lg">
|
<Avatar className="h-8 w-8 rounded-lg">
|
||||||
<AvatarImage src={data.picture} alt={data.name} />
|
<AvatarImage
|
||||||
|
src={decodedIdToken.picture}
|
||||||
|
alt={decodedIdToken.name}
|
||||||
|
/>
|
||||||
<AvatarFallback className="rounded-lg">KH</AvatarFallback>
|
<AvatarFallback className="rounded-lg">KH</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span className="truncate font-medium">{data.name}</span>
|
<span className="truncate font-medium">
|
||||||
<span className="truncate text-xs">{data.Email}</span>
|
{decodedIdToken.name}
|
||||||
|
</span>
|
||||||
|
<span className="truncate text-xs">{decodedIdToken.email}</span>
|
||||||
</div>
|
</div>
|
||||||
<ChevronsUpDown className="ml-auto size-4" />
|
<ChevronsUpDown className="ml-auto size-4" />
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
@@ -67,12 +60,19 @@ export function NavUser({
|
|||||||
<DropdownMenuLabel className="p-0 font-normal">
|
<DropdownMenuLabel className="p-0 font-normal">
|
||||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||||
<Avatar className="h-8 w-8 rounded-lg">
|
<Avatar className="h-8 w-8 rounded-lg">
|
||||||
<AvatarImage src={data.picture} alt={data.name} />
|
<AvatarImage
|
||||||
|
src={decodedIdToken.picture}
|
||||||
|
alt={decodedIdToken.name}
|
||||||
|
/>
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span className="truncate font-medium">{data.name}</span>
|
<span className="truncate font-medium">
|
||||||
<span className="truncate text-xs">{data.Email}</span>
|
{decodedIdToken.name}
|
||||||
|
</span>
|
||||||
|
<span className="truncate text-xs">
|
||||||
|
{decodedIdToken.email}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
@@ -99,12 +99,17 @@ export function NavUser({
|
|||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<a href={`${env.VITE_BACKEND_URI}/api/auth/logout`}>
|
<Button
|
||||||
|
onClick={() => logout({ redirectTo: 'specific url', url: '/' })}
|
||||||
|
variant={'ghost'}
|
||||||
|
className="w-full"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<LogOut />
|
<LogOut />
|
||||||
Log out
|
Log out
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</a>
|
</Button>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useNavigate } from '@tanstack/react-router'
|
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { env } from '@/env'
|
import { env } from '@/env'
|
||||||
|
import { fetchWithAuth } from '@/lib/oidc'
|
||||||
|
import { getBackendURI } from '@/routes'
|
||||||
|
|
||||||
const sessionKeys = {
|
const sessionKeys = {
|
||||||
all: ['sessions'] as const,
|
all: ['sessions'] as const,
|
||||||
@@ -28,8 +29,8 @@ export function useCurrentSession() {
|
|||||||
return useQuery<Session>({
|
return useQuery<Session>({
|
||||||
queryKey: sessionKeys.current(),
|
queryKey: sessionKeys.current(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await fetch(
|
const data = await fetchWithAuth(
|
||||||
env.VITE_BACKEND_URI + '/api/auth/currentSession',
|
(await getBackendURI()) + '/api/auth/currentSession',
|
||||||
{
|
{
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
},
|
},
|
||||||
@@ -41,17 +42,18 @@ export function useCurrentSession() {
|
|||||||
|
|
||||||
export function useProfile() {
|
export function useProfile() {
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
return useQuery<User>({
|
return useQuery<User>({
|
||||||
queryKey: sessionKeys.current(),
|
queryKey: sessionKeys.current(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await fetch(env.VITE_BACKEND_URI + '/v1/users/current', {
|
const data = await fetchWithAuth(
|
||||||
credentials: 'include',
|
(await getBackendURI()) + '/v1/users/current',
|
||||||
})
|
{
|
||||||
|
credentials: 'include',
|
||||||
|
},
|
||||||
|
)
|
||||||
if (data.status == 401) {
|
if (data.status == 401) {
|
||||||
queryClient.invalidateQueries()
|
queryClient.invalidateQueries()
|
||||||
navigate({ to: '/' })
|
|
||||||
}
|
}
|
||||||
return await data.json()
|
return await data.json()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import {
|
|||||||
useQueryClient,
|
useQueryClient,
|
||||||
useSuspenseQuery,
|
useSuspenseQuery,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
import { env } from '@/env'
|
import { fetchWithAuth } from '@/lib/oidc'
|
||||||
|
import { getBackendURI } from '@/routes'
|
||||||
|
|
||||||
const ansprechpartnerKeys = {
|
const ansprechpartnerKeys = {
|
||||||
all: ['ansprechpartner'] as const,
|
all: ['ansprechpartner'] as const,
|
||||||
@@ -36,8 +37,8 @@ export function useAllAnsprechpartners() {
|
|||||||
return useQuery<Ansprechpartner>({
|
return useQuery<Ansprechpartner>({
|
||||||
queryKey: ansprechpartnerKeys.lists(),
|
queryKey: ansprechpartnerKeys.lists(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await fetch(
|
const data = await fetchWithAuth(
|
||||||
env.VITE_BACKEND_URI + '/v1/ansprechpartner/all',
|
(await getBackendURI()) + '/v1/ansprechpartner/all',
|
||||||
{
|
{
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
},
|
},
|
||||||
@@ -51,8 +52,8 @@ export function useAnsprechpartner(id: number) {
|
|||||||
return useSuspenseQuery<Ansprechpartner>({
|
return useSuspenseQuery<Ansprechpartner>({
|
||||||
queryKey: ansprechpartnerKeys.detail(id),
|
queryKey: ansprechpartnerKeys.detail(id),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await fetch(
|
const data = await fetchWithAuth(
|
||||||
env.VITE_BACKEND_URI + '/v1/ansprechpartner/' + id,
|
(await getBackendURI()) + '/v1/ansprechpartner/' + id,
|
||||||
{ credentials: 'include' },
|
{ credentials: 'include' },
|
||||||
)
|
)
|
||||||
return await data.json()
|
return await data.json()
|
||||||
@@ -65,8 +66,8 @@ export function useAnsprechpartnerEditMutation() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (ansprechpartner: Ansprechpartner) => {
|
mutationFn: async (ansprechpartner: Ansprechpartner) => {
|
||||||
await fetch(
|
await fetchWithAuth(
|
||||||
env.VITE_BACKEND_URI + '/v1/ansprechpartner/' + ansprechpartner.ID,
|
(await getBackendURI()) + '/v1/ansprechpartner/' + ansprechpartner.ID,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
|
|||||||
@@ -1,11 +1,135 @@
|
|||||||
import type { ReactNode } from 'react'
|
import { createContext, Fragment, useContext, type ReactElement, type ReactNode } from 'react'
|
||||||
|
import KanbanDropzone from './KanbanDropzone'
|
||||||
|
import { DndContext } from '@dnd-kit/core'
|
||||||
|
import {
|
||||||
|
Sortable,
|
||||||
|
SortableContent,
|
||||||
|
SortableItem,
|
||||||
|
SortableOverlay,
|
||||||
|
} from "@/components/ui/sortable";
|
||||||
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
|
|
||||||
function Kanban({ children }: { children: ReactNode }) {
|
type ColumnRendererProps = {
|
||||||
|
name: string
|
||||||
|
itemCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type KanbanContextType = {
|
||||||
|
columns: any[]
|
||||||
|
setColumns: (cols: any[]) => void
|
||||||
|
columnRenderer: ReactElement<ColumnRendererProps, any>
|
||||||
|
}
|
||||||
|
|
||||||
|
const kanbanContext = createContext<KanbanContextType>({ columns: [], setColumns: () => {}, columnRenderer: <></> });
|
||||||
|
|
||||||
|
export const useKanban = () => {
|
||||||
|
const ctx = useContext(kanbanContext)
|
||||||
|
|
||||||
|
const reorderColumns = (name: string, toIndex: number) => {
|
||||||
|
const fromIndex = ctx.columns.findIndex((col: any) => col.name == name);
|
||||||
|
console.log(fromIndex, toIndex, ctx.columns)
|
||||||
|
let fromCompIndex;
|
||||||
|
if (toIndex < fromIndex) {
|
||||||
|
fromCompIndex = fromIndex - 1;
|
||||||
|
} else {
|
||||||
|
fromCompIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromIndex === -1 || toIndex < 0 || toIndex >= ctx.columns.length) { console.log("invalid"); return; }
|
||||||
|
|
||||||
|
|
||||||
|
const updatedColumns = [...ctx.columns];
|
||||||
|
|
||||||
|
const movedColumn = ctx.columns[fromIndex];
|
||||||
|
updatedColumns.splice(toIndex, 0, movedColumn);
|
||||||
|
console.log("valid", movedColumn, fromIndex, fromCompIndex, toIndex, updatedColumns);
|
||||||
|
updatedColumns.splice(fromCompIndex, 1);
|
||||||
|
console.log(movedColumn, fromIndex, fromCompIndex, toIndex, updatedColumns);
|
||||||
|
|
||||||
|
ctx.setColumns(updatedColumns);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { reorderColumns, ...ctx }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type KanbanColumn = {
|
||||||
|
id: number,
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TrickCardProps
|
||||||
|
extends Omit<React.ComponentPropsWithoutRef<typeof SortableItem>, "value"> {
|
||||||
|
trick: KanbanColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TrickCard({ trick, ...props }: TrickCardProps) {
|
||||||
return (
|
return (
|
||||||
<div className="box-border flex gap-2 h-full min-w-full max-w-screen overflow-x-auto snap-x snap-mandatory md:snap-none">
|
<SortableItem value={trick.id} asChild {...props}>
|
||||||
{children}
|
<div className="block flex-col gap-1 w-sm shrink-0 rounded-md border bg-zinc-100 p-4 text-foreground shadow-sm dark:bg-zinc-900">
|
||||||
</div>
|
<div className="font-medium text-sm leading-tight sm:text-base">
|
||||||
|
{trick.name}
|
||||||
|
</div>
|
||||||
|
<span className="line-clamp-2 hidden text-muted-foreground text-sm sm:inline-block">
|
||||||
|
{trick.id}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</SortableItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function KanbanOLD({ columns, setColumns, columnRenderer }: { columns: KanbanColumn[], setColumns: (v: KanbanColumn[]) => void, columnRenderer: ReactElement<ColumnRendererProps,any> }) {
|
||||||
|
return (
|
||||||
|
<Sortable value={columns} onValueChange={setColumns} getItemValue={(c) => c.id} orientation='horizontal'>
|
||||||
|
<SortableContent className="flex flex-row gap-2.5 h-full bg-emerald-500 min-w-full overflow-scroll">
|
||||||
|
{columns.map((c) => <TrickCard key={c.id} trick={c} asHandle />)}
|
||||||
|
</SortableContent>
|
||||||
|
<SortableOverlay>
|
||||||
|
{(activeItem) => {
|
||||||
|
const c = columns.find((c) => c.id === activeItem.value);
|
||||||
|
|
||||||
|
if (!c) return null;
|
||||||
|
|
||||||
|
return <TrickCard trick={c} />;
|
||||||
|
}}
|
||||||
|
</SortableOverlay>
|
||||||
|
</Sortable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function KanbanInner() {
|
||||||
|
const { columns, columnRenderer } = useKanban();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="box-border flex gap-2 h-full min-w-full max-w-screen overflow-x-auto snap-x snap-mandatory md:snap-none">
|
||||||
|
<KanbanDropzone idx={1} />
|
||||||
|
{columns.map((column, index) => (
|
||||||
|
<Fragment key={column.id}>
|
||||||
|
<div className="snap-start md:snap-none">
|
||||||
|
{<columnRenderer.type name={column.name} value={String(column.id)} />}
|
||||||
|
</div>
|
||||||
|
<KanbanDropzone idx={index + 2} />
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default Kanban
|
export default Kanban
|
||||||
|
|||||||
@@ -1,59 +1,91 @@
|
|||||||
|
import { listTodos } from '@/gen/todo/v1/todo-TodoService_connectquery'
|
||||||
|
import { Field, Operation } from '@/gen/todo/v1/todo_pb'
|
||||||
|
import { useSuspenseQuery } from '@connectrpc/connect-query'
|
||||||
|
import { useDroppable } from '@dnd-kit/core'
|
||||||
import { GripVertical, Tickets } from 'lucide-react'
|
import { GripVertical, Tickets } from 'lucide-react'
|
||||||
import { useRef } from 'react'
|
import { Suspense, useRef } from 'react'
|
||||||
import type { ReactNode } from 'react'
|
|
||||||
|
|
||||||
function KanbanColumn({
|
function KanbanColumn({
|
||||||
children,
|
|
||||||
name,
|
name,
|
||||||
itemCount,
|
value,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode
|
name: string,
|
||||||
name: string
|
value: string,
|
||||||
itemCount: number
|
|
||||||
}) {
|
}) {
|
||||||
const column = useRef<HTMLDivElement>(null)
|
// const column = useRef<HTMLDivElement>(null)
|
||||||
|
const { isOver, setNodeRef } = useDroppable({
|
||||||
|
id: name
|
||||||
|
})
|
||||||
|
|
||||||
const handleDraggableStart = () => {
|
// const handleDragStart = (e: any) => {
|
||||||
column.current?.setAttribute('draggable', 'true')
|
// e.dataTransfer?.setData('text/custom', name)
|
||||||
}
|
// }
|
||||||
|
|
||||||
const handleDraggableStop = () => {
|
// const handleDragStop = () => {
|
||||||
column.current?.setAttribute('draggable', 'false')
|
// column.current?.setAttribute('draggable', 'false')
|
||||||
}
|
// }
|
||||||
|
|
||||||
const handleDragStart = (e: DragEvent) => {
|
|
||||||
e.dataTransfer?.setData('text/custom', 'ASDF')
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDragStop = () => {
|
|
||||||
column.current?.setAttribute('draggable', 'false')
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="min-w-96 bg-accent rounded-md p-4 snap-center"
|
className="min-w-96 h-full bg-accent rounded-md p-4 snap-center"
|
||||||
ref={column}
|
ref={setNodeRef}
|
||||||
onDragStart={handleDragStart}
|
|
||||||
onDragEnd={handleDragStop}
|
|
||||||
>
|
>
|
||||||
<div className="flex justify-between mb-2">
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
<div className="flex place-items-center">
|
<KanbanColumnInner name={name} value={value} />
|
||||||
<GripVertical
|
</Suspense>
|
||||||
size={20}
|
|
||||||
className="mr-2 cursor-grab"
|
|
||||||
onMouseDown={handleDraggableStart}
|
|
||||||
onMouseUp={handleDraggableStop}
|
|
||||||
/>
|
|
||||||
<p>{name}</p>
|
|
||||||
</div>
|
|
||||||
<div className="flex place-items-center gap-2">
|
|
||||||
{itemCount}
|
|
||||||
<Tickets size={20} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{children}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function KanbanColumnInner({ columnRef, name, value }: { columnRef: React.RefObject<HTMLDivElement | null>, name: string, value: string }) {
|
||||||
|
const { data } = useSuspenseQuery(listTodos, {
|
||||||
|
perPage: 10,
|
||||||
|
page: 0,
|
||||||
|
orberBy: 'id',
|
||||||
|
asc: false,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: Field.FieldStatus,
|
||||||
|
operation: Operation.Equals,
|
||||||
|
value: value,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const handleDraggableStart = () => {
|
||||||
|
columnRef.current?.setAttribute('draggable', 'true')
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDraggableStop = () => {
|
||||||
|
columnRef.current?.setAttribute('draggable', 'false')
|
||||||
|
}
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
<div className="flex justify-between mb-2">
|
||||||
|
<div className="flex place-items-center">
|
||||||
|
<GripVertical
|
||||||
|
size={20}
|
||||||
|
className="mr-2 cursor-grab"
|
||||||
|
onMouseDown={handleDraggableStart}
|
||||||
|
onMouseUp={handleDraggableStop}
|
||||||
|
/>
|
||||||
|
<p>{name}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex place-items-center gap-2">
|
||||||
|
{data?.meta?.totalCount || 0}
|
||||||
|
<Tickets size={20} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="h-[500px] overflow-y-auto">
|
||||||
|
{data?.data.map((todo) => (
|
||||||
|
<div key={todo.id} className="bg-muted rounded-md p-2 mb-2">
|
||||||
|
<p className="font-semibold">{todo.title}</p>
|
||||||
|
<p className="text-sm text-muted-foreground">{todo.description}</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
|
||||||
export default KanbanColumn
|
export default KanbanColumn
|
||||||
|
|||||||
@@ -1,26 +1,43 @@
|
|||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
|
import { useKanban } from './Kanban';
|
||||||
|
|
||||||
function KanbanDropzone() {
|
function KanbanDropzone({idx}: {idx: number}) {
|
||||||
|
const ctx = useKanban();
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const handleDragEnter = () => {
|
const handleDragEnter = (e) => {
|
||||||
console.log('DRAGOVER')
|
e.preventDefault();
|
||||||
if (!ref.current) return
|
if (!ref.current) return
|
||||||
ref.current.style.backgroundColor = '#41b2b2'
|
ref.current.style.backgroundColor = '#41b2b2'
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDragLeave = () => {
|
const handleDragLeave = (e) => {
|
||||||
console.log('DRAGOVER')
|
e.preventDefault();
|
||||||
if (!ref.current) return
|
if (!ref.current) return
|
||||||
ref.current.style.backgroundColor = 'transparent'
|
ref.current.style.backgroundColor = 'transparent'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleDragOver = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDrop = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!ref.current) return
|
||||||
|
ref.current.style.backgroundColor = 'transparent'
|
||||||
|
console.log('Dropped', e.dataTransfer.getData('text/custom'))
|
||||||
|
ctx.reorderColumns(e.dataTransfer.getData('text/custom'), idx);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="min-w-1 h-full block"
|
className="min-w-1 h-full block"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onDragEnter={handleDragEnter}
|
onDragEnter={handleDragEnter}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDrop={handleDrop}
|
||||||
></div>
|
></div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { ChevronDown, Plus } from 'lucide-react'
|
import { ChevronDown, Plus } from 'lucide-react'
|
||||||
|
|
||||||
import {
|
|
||||||
useAllMandanten,
|
|
||||||
useCurrentMandant,
|
|
||||||
useCurrentMandantMutation,
|
|
||||||
} from '../queries'
|
|
||||||
import type { Mandant } from '../queries'
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -21,12 +15,16 @@ import {
|
|||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from '@/components/ui/sidebar'
|
} from '@/components/ui/sidebar'
|
||||||
|
import { useMutation, useQuery } from '@connectrpc/connect-query'
|
||||||
|
import { getAllTenants, getCurrentTenant, setCurrentTenant } from '@/gen/mandant/v1/mandant-MandantService_connectquery'
|
||||||
|
import { GetTenantResponse } from '@/gen/mandant/v1/mandant_pb'
|
||||||
|
|
||||||
export function TeamSwitcher() {
|
export function TeamSwitcher() {
|
||||||
const { data: currentMandant } = useCurrentMandant()
|
const { data: currentMandant } = useQuery(getCurrentTenant, {}, {})
|
||||||
const { data: mandanten } = useAllMandanten()
|
const { data: allMandanten } = useQuery(getAllTenants, {}, {})
|
||||||
|
const mandanten = allMandanten?.data || []
|
||||||
|
|
||||||
const editCurrentTeamMutation = useCurrentMandantMutation()
|
const editCurrentTeamMutation = useMutation(setCurrentTenant, {})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const down = (e: KeyboardEvent) => {
|
const down = (e: KeyboardEvent) => {
|
||||||
@@ -37,12 +35,13 @@ export function TeamSwitcher() {
|
|||||||
if (mandanten && currentMandant) {
|
if (mandanten && currentMandant) {
|
||||||
const mandant = mandanten[numKey]
|
const mandant = mandanten[numKey]
|
||||||
|
|
||||||
if (mandant.ID === currentMandant.ID) {
|
if (mandant.id === currentMandant.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log(mandant, currentMandant)
|
console.log(mandant, currentMandant)
|
||||||
|
|
||||||
|
|
||||||
editCurrentTeamMutation.mutate(mandanten[numKey])
|
editCurrentTeamMutation.mutate({ tenantId: mandanten[numKey].id })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,17 +110,17 @@ function MandantDMI({
|
|||||||
currentMandant,
|
currentMandant,
|
||||||
index,
|
index,
|
||||||
}: {
|
}: {
|
||||||
mandant: Mandant
|
mandant: GetTenantResponse
|
||||||
currentMandant: Mandant
|
currentMandant: GetTenantResponse
|
||||||
index: number
|
index: number
|
||||||
}) {
|
}) {
|
||||||
const editCurrentMandantMutaiton = useCurrentMandantMutation()
|
const editCurrentMandantMutaiton = useMutation(setCurrentTenant, {})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
key={mandant.ID}
|
key={mandant.id}
|
||||||
onClick={() => editCurrentMandantMutaiton.mutate(mandant)}
|
onClick={() => editCurrentMandantMutaiton.mutate({ tenantId: mandant.id })}
|
||||||
disabled={mandant.ID === currentMandant.ID}
|
disabled={mandant.id === currentMandant.id}
|
||||||
className="gap-2 p-2"
|
className="gap-2 p-2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import { env } from '@/env'
|
import { fetchWithAuth } from '@/lib/oidc'
|
||||||
|
import { getBackendURI } from '@/routes'
|
||||||
|
|
||||||
const mandantKeys = {
|
const mandantKeys = {
|
||||||
all: ['mandant'] as const,
|
all: ['mandant'] as const,
|
||||||
@@ -19,9 +20,12 @@ export function useCurrentMandant() {
|
|||||||
return useQuery<Mandant>({
|
return useQuery<Mandant>({
|
||||||
queryKey: mandantKeys.current(),
|
queryKey: mandantKeys.current(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await fetch(env.VITE_BACKEND_URI + '/v1/mandant/current', {
|
const data = await fetchWithAuth(
|
||||||
credentials: 'include',
|
(await getBackendURI()) + '/v1/mandant/current',
|
||||||
})
|
{
|
||||||
|
credentials: 'include',
|
||||||
|
},
|
||||||
|
)
|
||||||
return await data.json()
|
return await data.json()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -31,9 +35,12 @@ export function useAllMandanten() {
|
|||||||
return useQuery<Array<Mandant>>({
|
return useQuery<Array<Mandant>>({
|
||||||
queryKey: mandantKeys.lists(),
|
queryKey: mandantKeys.lists(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await fetch(env.VITE_BACKEND_URI + '/v1/mandant/all', {
|
const data = await fetchWithAuth(
|
||||||
credentials: 'include',
|
(await getBackendURI()) + '/v1/mandant/all',
|
||||||
})
|
{
|
||||||
|
credentials: 'include',
|
||||||
|
},
|
||||||
|
)
|
||||||
return await data.json()
|
return await data.json()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -44,19 +51,25 @@ export function useCurrentMandantMutation() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (mandant: Mandant) => {
|
mutationFn: async (mandant: Mandant) => {
|
||||||
const res = await fetch(env.VITE_BACKEND_URI + '/v1/mandant/current', {
|
const res = await fetchWithAuth(
|
||||||
headers: {
|
(await getBackendURI()) + '/v1/mandant/current',
|
||||||
'content-type': 'application/json',
|
{
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(mandant),
|
||||||
|
credentials: 'include',
|
||||||
},
|
},
|
||||||
method: 'PUT',
|
)
|
||||||
body: JSON.stringify(mandant),
|
|
||||||
credentials: 'include',
|
|
||||||
})
|
|
||||||
const newCurrentMandant = await res.json()
|
const newCurrentMandant = await res.json()
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData(
|
||||||
mandantKeys.current(),
|
mandantKeys.current(),
|
||||||
(_: Mandant) => newCurrentMandant,
|
(_: Mandant) => newCurrentMandant,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
onSettled(data, error, variables, onMutateResult, context) {
|
||||||
|
queryClient.invalidateQueries({ queryKey: [] });
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Folder, MoreHorizontal, Share, Trash2 } from 'lucide-react'
|
import { Folder, MoreHorizontal, Share, Trash2 } from 'lucide-react'
|
||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import { useAllProjects } from '../queries'
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -17,13 +16,12 @@ import {
|
|||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
useSidebar,
|
useSidebar,
|
||||||
} from '@/components/ui/sidebar'
|
} from '@/components/ui/sidebar'
|
||||||
|
import { useInfiniteQuery } from '@connectrpc/connect-query'
|
||||||
|
import { listProjects } from '@/gen/project/v1/project-ProjectService_connectquery'
|
||||||
|
|
||||||
export function NavProjects() {
|
export function NavProjects() {
|
||||||
const { isMobile } = useSidebar()
|
const { isMobile } = useSidebar()
|
||||||
const { data: projects } = useAllProjects({
|
const { data: projects } = useInfiniteQuery(listProjects, { perPage: 5, orberBy: 'id', asc: true, page: 0 }, { getNextPageParam: () => -1, pageParamKey: 'perPage' })
|
||||||
fetchSize: 5,
|
|
||||||
sorting: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!projects) {
|
if (!projects) {
|
||||||
return <p>Loading...</p>
|
return <p>Loading...</p>
|
||||||
@@ -36,8 +34,7 @@ export function NavProjects() {
|
|||||||
{projects.pages[0].data.map((item) => (
|
{projects.pages[0].data.map((item) => (
|
||||||
<SidebarMenuItem key={item.name}>
|
<SidebarMenuItem key={item.name}>
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild>
|
||||||
<Link to="/projects/view/$id" params={{ id: item.ID.toString() }}>
|
<Link to="/projects/view/$id" params={{ id: item.id.toString() }}>
|
||||||
{item.icon}
|
|
||||||
<span>{item.name}</span>
|
<span>{item.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
|
|||||||
@@ -8,14 +8,12 @@ import {
|
|||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import { useAllProjects } from '../queries'
|
|
||||||
import type {
|
import type {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
RowSelectionState,
|
RowSelectionState,
|
||||||
SortingState,
|
SortingState,
|
||||||
VisibilityState,
|
VisibilityState,
|
||||||
} from '@tanstack/react-table'
|
} from '@tanstack/react-table'
|
||||||
import type { PaginatedProject } from '../queries'
|
|
||||||
import { Checkbox } from '@/components/ui/checkbox'
|
import { Checkbox } from '@/components/ui/checkbox'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -28,10 +26,26 @@ import {
|
|||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { DataTableColumnHeader } from '@/components/data-table-column-header'
|
import { DataTableColumnHeader } from '@/components/data-table-column-header'
|
||||||
import { DataTable } from '@/components/data-table'
|
import { DataTable } from '@/components/data-table'
|
||||||
|
import {
|
||||||
|
useInfiniteQuery,
|
||||||
|
useSuspenseInfiniteQuery,
|
||||||
|
} from '@connectrpc/connect-query'
|
||||||
|
import { listProjects } from '@/gen/project/v1/project-ProjectService_connectquery'
|
||||||
|
import { GetProjectResponse } from '@/gen/project/v1/project_pb'
|
||||||
|
|
||||||
const iconSize = 16
|
const iconSize = 16
|
||||||
|
|
||||||
export const columnDefs: Array<ColumnDef<PaginatedProject>> = [
|
const getBooleanColor = (val: boolean | undefined) => {
|
||||||
|
if (val == null) {
|
||||||
|
return 'black'
|
||||||
|
} else if (val) {
|
||||||
|
return 'var(--color-emerald-500)'
|
||||||
|
} else {
|
||||||
|
return 'var(--color-red-500)'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const columnDefs: Array<ColumnDef<GetProjectResponse>> = [
|
||||||
{
|
{
|
||||||
id: 'select',
|
id: 'select',
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -56,6 +70,9 @@ export const columnDefs: Array<ColumnDef<PaginatedProject>> = [
|
|||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
size: 26,
|
size: 26,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'id',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'name',
|
accessorKey: 'name',
|
||||||
header: ({ column, header }) => {
|
header: ({ column, header }) => {
|
||||||
@@ -67,6 +84,14 @@ export const columnDefs: Array<ColumnDef<PaginatedProject>> = [
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
cell: ({ row, getValue }) => {
|
||||||
|
const project = row.original
|
||||||
|
return (
|
||||||
|
<Link to="/projects/view/$id" params={{ id: project.id.toString() }}>
|
||||||
|
{getValue() as string}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
},
|
||||||
size: 200,
|
size: 200,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -79,14 +104,30 @@ export const columnDefs: Array<ColumnDef<PaginatedProject>> = [
|
|||||||
{
|
{
|
||||||
accessorKey: 'progress',
|
accessorKey: 'progress',
|
||||||
header: 'Project progress',
|
header: 'Project progress',
|
||||||
cell: () => {
|
cell: ({ row }) => {
|
||||||
|
const project = row.original
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row gap-2 items-center h-4">
|
<div className="flex flex-row gap-2 items-center h-4">
|
||||||
<SpeakerIcon size={iconSize} />
|
<SpeakerIcon
|
||||||
<CircleUserIcon size={iconSize} />
|
size={iconSize}
|
||||||
<CircleCheckIcon size={iconSize} />
|
color={getBooleanColor(project.isMaterialized)}
|
||||||
<ReceiptEuroIcon size={iconSize} />
|
/>
|
||||||
<ClipboardCheckIcon size={iconSize} />
|
<CircleUserIcon
|
||||||
|
size={iconSize}
|
||||||
|
color={getBooleanColor(project.isPersonalized)}
|
||||||
|
/>
|
||||||
|
<CircleCheckIcon
|
||||||
|
size={iconSize}
|
||||||
|
color={getBooleanColor(project.isConfirmed)}
|
||||||
|
/>
|
||||||
|
<ReceiptEuroIcon
|
||||||
|
size={iconSize}
|
||||||
|
color={getBooleanColor(project.isPaid)}
|
||||||
|
/>
|
||||||
|
<ClipboardCheckIcon
|
||||||
|
size={iconSize}
|
||||||
|
color={getBooleanColor(project.isDone)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -134,7 +175,7 @@ export const columnDefs: Array<ColumnDef<PaginatedProject>> = [
|
|||||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigator.clipboard.writeText(project.ID.toString())
|
navigator.clipboard.writeText(project.id.toString())
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Copy project ID
|
Copy project ID
|
||||||
@@ -143,7 +184,7 @@ export const columnDefs: Array<ColumnDef<PaginatedProject>> = [
|
|||||||
<DropdownMenuItem asChild>
|
<DropdownMenuItem asChild>
|
||||||
<Link
|
<Link
|
||||||
to="/projects/view/$id"
|
to="/projects/view/$id"
|
||||||
params={{ id: project.ID.toString() }}
|
params={{ id: project.id.toString() }}
|
||||||
>
|
>
|
||||||
View Project
|
View Project
|
||||||
</Link>
|
</Link>
|
||||||
@@ -159,7 +200,9 @@ export const columnDefs: Array<ColumnDef<PaginatedProject>> = [
|
|||||||
const fetchSize = 25
|
const fetchSize = 25
|
||||||
|
|
||||||
function ProjectsTable() {
|
function ProjectsTable() {
|
||||||
const [sorting, setSorting] = useState<SortingState>([])
|
const [sorting, setSorting] = useState<SortingState>([
|
||||||
|
{ id: 'id', desc: false },
|
||||||
|
])
|
||||||
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({
|
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({
|
||||||
description: false,
|
description: false,
|
||||||
client: false,
|
client: false,
|
||||||
@@ -171,10 +214,25 @@ function ProjectsTable() {
|
|||||||
|
|
||||||
const [selected, setSelected] = useState<RowSelectionState>({})
|
const [selected, setSelected] = useState<RowSelectionState>({})
|
||||||
|
|
||||||
const { data, fetchNextPage, isFetching, isLoading } = useAllProjects({
|
const { data, fetchNextPage, isFetching, isLoading } =
|
||||||
fetchSize,
|
useSuspenseInfiniteQuery(
|
||||||
sorting,
|
listProjects,
|
||||||
})
|
{
|
||||||
|
$typeName: 'project.v1.ListProjectsRequest',
|
||||||
|
page: 0,
|
||||||
|
perPage: fetchSize,
|
||||||
|
orberBy: 'id',
|
||||||
|
asc: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getNextPageParam: (_, p) => {
|
||||||
|
console.log(p.length)
|
||||||
|
return p.length * fetchSize
|
||||||
|
// return ()
|
||||||
|
},
|
||||||
|
pageParamKey: 'page',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<DataTable
|
||||||
|
|||||||
112
src/features/Projects/components/timelinetable.tsx
Normal file
112
src/features/Projects/components/timelinetable.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useParams } from '@tanstack/react-router'
|
||||||
|
import { useAllTimelineEntries } from '../queries'
|
||||||
|
import type { Paginated, TimelineEntry } from '../queries'
|
||||||
|
import type {
|
||||||
|
ColumnDef,
|
||||||
|
RowSelectionState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
} from '@tanstack/react-table'
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox'
|
||||||
|
import { DataTableColumnHeader } from '@/components/data-table-column-header'
|
||||||
|
import { DataTable } from '@/components/data-table'
|
||||||
|
import Calendar24 from '@/components/calendar-24'
|
||||||
|
|
||||||
|
export const columnDefs: Array<ColumnDef<Paginated<TimelineEntry>>> = [
|
||||||
|
{
|
||||||
|
id: 'select',
|
||||||
|
header: ({ table }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
table.getIsAllPageRowsSelected() ||
|
||||||
|
(table.getIsSomePageRowsSelected() && 'indeterminate')
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||||
|
aria-label="Select all"
|
||||||
|
className="aspect-square"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||||
|
aria-label="Select row"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
size: 26,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'name',
|
||||||
|
header: ({ column, header }) => {
|
||||||
|
return (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
column={column}
|
||||||
|
title="Name"
|
||||||
|
style={{ width: header.getSize() }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
size: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'from',
|
||||||
|
header: 'From',
|
||||||
|
cell: ({ getValue }) => {
|
||||||
|
return <Calendar24 defaultDate={getValue()} />
|
||||||
|
},
|
||||||
|
size: 400,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'thru',
|
||||||
|
header: 'Thru',
|
||||||
|
cell: ({ getValue }) => {
|
||||||
|
return <Calendar24 defaultDate={getValue()} />
|
||||||
|
},
|
||||||
|
size: 400,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const fetchSize = 25
|
||||||
|
|
||||||
|
function TimelineTable() {
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([])
|
||||||
|
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({
|
||||||
|
description: false,
|
||||||
|
client: false,
|
||||||
|
location: false,
|
||||||
|
type: false,
|
||||||
|
enddate: false,
|
||||||
|
startdate: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState<RowSelectionState>({})
|
||||||
|
|
||||||
|
const { id } = useParams({ from: '/_sidebar/projects/view/$id/timeline' })
|
||||||
|
|
||||||
|
const { data, fetchNextPage, isFetching, isLoading } = useAllTimelineEntries({
|
||||||
|
projectId: Number(id),
|
||||||
|
fetchSize,
|
||||||
|
sorting,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
columns={columnDefs}
|
||||||
|
data={data}
|
||||||
|
columnVisibility={columnVisibility}
|
||||||
|
setColumnVisibility={setColumnVisibility}
|
||||||
|
fetchNextPage={fetchNextPage}
|
||||||
|
isFetching={isFetching}
|
||||||
|
isLoading={isLoading}
|
||||||
|
setSorting={setSorting}
|
||||||
|
sorting={sorting}
|
||||||
|
rowSelection={selected}
|
||||||
|
setRowSelection={setSelected}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TimelineTable
|
||||||
152
src/features/Projects/components/todotable.tsx
Normal file
152
src/features/Projects/components/todotable.tsx
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { useParams } from '@tanstack/react-router'
|
||||||
|
import type {
|
||||||
|
ColumnDef,
|
||||||
|
RowSelectionState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
} from '@tanstack/react-table'
|
||||||
|
import { Checkbox } from '@/components/ui/checkbox'
|
||||||
|
import { DataTableColumnHeader } from '@/components/data-table-column-header'
|
||||||
|
import { DataTable } from '@/components/data-table'
|
||||||
|
import { useSuspenseInfiniteQuery } from '@connectrpc/connect-query'
|
||||||
|
import { listTodos } from '@/gen/todo/v1/todo-TodoService_connectquery'
|
||||||
|
import { ListProjectsResponse } from '@/gen/project/v1/project_pb'
|
||||||
|
import { ListTodosResponse, Status } from '@/gen/todo/v1/todo_pb'
|
||||||
|
|
||||||
|
const getColorFromTodoState = (state: Status) => {
|
||||||
|
switch (state) {
|
||||||
|
case Status.Todo:
|
||||||
|
return 'data-[state=checked]:bg-yellow-500 data-[state=checked]:border-yellow-500 bg-yellow-500 border-yellow-500'
|
||||||
|
case Status.NeedsMoreInfo:
|
||||||
|
return 'data-[state=checked]:bg-orange-500 data-[state=checked]:border-orange-500 bg-orange-500 border-orange-500'
|
||||||
|
case Status.Doing:
|
||||||
|
return 'data-[state=checked]:bg-sky-500 data-[state=checked]:border-sky-500 bg-sky-500 border-sky-500'
|
||||||
|
case Status.Done:
|
||||||
|
return 'data-[state=checked]:bg-emerald-500 data-[state=checked]:border-emerald-500 bg-emerald-500 border-emerald-500'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const columnDefs: Array<ColumnDef<ListTodosResponse>> = [
|
||||||
|
{
|
||||||
|
id: 'select',
|
||||||
|
header: ({ table }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
table.getIsAllPageRowsSelected() ||
|
||||||
|
(table.getIsSomePageRowsSelected() && 'indeterminate')
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||||
|
aria-label="Select all"
|
||||||
|
className="aspect-square"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||||
|
aria-label="Select row"
|
||||||
|
className={getColorFromTodoState(row.original.status)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
size: 26,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'status',
|
||||||
|
header: ({ column, header }) => {
|
||||||
|
return (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
column={column}
|
||||||
|
title="Status"
|
||||||
|
style={{ width: header.getSize() }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
cell: ({ row }) => (
|
||||||
|
Status[row.original.status] === undefined ? <p>Unbekannt</p> :<p>{Status[row.original.status]}</p>
|
||||||
|
),
|
||||||
|
size: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'title',
|
||||||
|
header: ({ column, header }) => {
|
||||||
|
return (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
column={column}
|
||||||
|
title="Titel"
|
||||||
|
style={{ width: header.getSize() }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
size: 200,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'description',
|
||||||
|
header: ({ column, header }) => {
|
||||||
|
return (
|
||||||
|
<DataTableColumnHeader
|
||||||
|
column={column}
|
||||||
|
title="Beschreibung"
|
||||||
|
style={{ width: header.getSize() }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
size: 200,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const fetchSize = 25
|
||||||
|
|
||||||
|
function TodoTable() {
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([])
|
||||||
|
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
|
||||||
|
|
||||||
|
const [selected, setSelected] = useState<RowSelectionState>({})
|
||||||
|
|
||||||
|
const { id } = useParams({ from: '/_sidebar/projects/view/$id/todos' })
|
||||||
|
|
||||||
|
// const { data, fetchNextPage, isFetching, isLoading } = useAllProjectTodos({
|
||||||
|
// projectId: Number(id),
|
||||||
|
// fetchSize,
|
||||||
|
// sorting,
|
||||||
|
// })
|
||||||
|
|
||||||
|
const { data, fetchNextPage, isFetching, isLoading } =
|
||||||
|
useSuspenseInfiniteQuery(
|
||||||
|
listTodos,
|
||||||
|
{
|
||||||
|
page: 0,
|
||||||
|
perPage: fetchSize,
|
||||||
|
orberBy: 'id',
|
||||||
|
asc: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getNextPageParam: (_, p) => {
|
||||||
|
console.log(p.length)
|
||||||
|
return p.length * fetchSize
|
||||||
|
// return ()
|
||||||
|
},
|
||||||
|
pageParamKey: 'page',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
columns={columnDefs}
|
||||||
|
data={data}
|
||||||
|
columnVisibility={columnVisibility}
|
||||||
|
setColumnVisibility={setColumnVisibility}
|
||||||
|
fetchNextPage={fetchNextPage}
|
||||||
|
isFetching={isFetching}
|
||||||
|
isLoading={isLoading}
|
||||||
|
setSorting={setSorting}
|
||||||
|
sorting={sorting}
|
||||||
|
rowSelection={selected}
|
||||||
|
setRowSelection={setSelected}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TodoTable
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
import {
|
import {
|
||||||
|
infiniteQueryOptions,
|
||||||
keepPreviousData,
|
keepPreviousData,
|
||||||
|
queryOptions,
|
||||||
useInfiniteQuery,
|
useInfiniteQuery,
|
||||||
useMutation,
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
useQueryClient,
|
useQueryClient,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
import { env } from '@/env'
|
import { fetchWithAuth, getOidc } from '@/lib/oidc'
|
||||||
|
import { getBackendURI } from '@/routes'
|
||||||
|
|
||||||
const projectKeys = {
|
const projectKeys = {
|
||||||
all: ['projects'] as const,
|
all: ['projects'] as const,
|
||||||
@@ -15,10 +18,26 @@ const projectKeys = {
|
|||||||
get: (id: number) => [...projectKeys.all, 'get', id] as const,
|
get: (id: number) => [...projectKeys.all, 'get', id] as const,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PaginatedProject = {
|
const timelineKeys = {
|
||||||
data: Array<Project>
|
all: ['timeline'] as const,
|
||||||
|
lists: () => [...timelineKeys.all, 'list'] as const,
|
||||||
|
getAll: (fetchSize: number, sorting: any) =>
|
||||||
|
[...timelineKeys.lists(), 'all', fetchSize, sorting] as const,
|
||||||
|
get: (id: number) => [...timelineKeys.all, 'get', id] as const,
|
||||||
|
}
|
||||||
|
|
||||||
|
const todoKeys = {
|
||||||
|
all: ['todos'] as const,
|
||||||
|
lists: () => [...todoKeys.all, 'list'] as const,
|
||||||
|
getAll: (fetchSize: number, sorting: any) =>
|
||||||
|
[...todoKeys.lists(), 'all', fetchSize, sorting] as const,
|
||||||
|
get: (id: number) => [...todoKeys.all, 'get', id] as const,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Paginated<TData> = {
|
||||||
|
data: Array<TData>
|
||||||
meta: {
|
meta: {
|
||||||
totalProjectsCount: number
|
totalCount: number
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,22 +49,62 @@ export type Project = {
|
|||||||
MandantID: number
|
MandantID: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TimelineEntry = {
|
||||||
|
ID: number
|
||||||
|
name: string
|
||||||
|
from: Date
|
||||||
|
manualFrom: boolean
|
||||||
|
thru: Date
|
||||||
|
manualThru: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export function getProjectQueryObject(id: number) {
|
export function getProjectQueryObject(id: number) {
|
||||||
return {
|
return queryOptions<Project>({
|
||||||
queryKey: projectKeys.get(id),
|
queryKey: projectKeys.get(id),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const data = await fetch(env.VITE_BACKEND_URI + '/v1/projects/' + id, {
|
const data = await fetchWithAuth(
|
||||||
credentials: 'include',
|
(await getBackendURI()) + '/v1/projects/' + id,
|
||||||
})
|
{
|
||||||
|
credentials: 'include',
|
||||||
|
},
|
||||||
|
)
|
||||||
return await data.json()
|
return await data.json()
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useProject(id: number) {
|
export function useProject(id: number) {
|
||||||
return useQuery<Project>(getProjectQueryObject(id))
|
return useQuery<Project>(getProjectQueryObject(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAllProjectsQueryObject({ fetchSize, sorting }: any) {
|
||||||
|
return infiniteQueryOptions<Paginated<Project>>({
|
||||||
|
queryKey: projectKeys.getAll(fetchSize, sorting),
|
||||||
|
|
||||||
|
queryFn: async ({ pageParam = 0 }) => {
|
||||||
|
const start = (pageParam as number) * fetchSize
|
||||||
|
|
||||||
|
const { id, desc } = sorting[0]
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams()
|
||||||
|
|
||||||
|
searchParams.append('id', id)
|
||||||
|
searchParams.append('desc', desc)
|
||||||
|
searchParams.append('per-page', fetchSize)
|
||||||
|
searchParams.append('offset', start.toString())
|
||||||
|
|
||||||
|
const url = (await getBackendURI()) + '/v1/projects/all?' + searchParams
|
||||||
|
const data = await fetchWithAuth(url)
|
||||||
|
return await data.json()
|
||||||
|
},
|
||||||
|
|
||||||
|
initialPageParam: 0,
|
||||||
|
getNextPageParam: (_lastGroup, groups) => groups.length,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function useAllProjects({
|
export function useAllProjects({
|
||||||
fetchSize,
|
fetchSize,
|
||||||
sorting,
|
sorting,
|
||||||
@@ -53,23 +112,79 @@ export function useAllProjects({
|
|||||||
fetchSize: number
|
fetchSize: number
|
||||||
sorting: any
|
sorting: any
|
||||||
}) {
|
}) {
|
||||||
return useInfiniteQuery<PaginatedProject>({
|
return useInfiniteQuery<Paginated<Project>>(
|
||||||
queryKey: projectKeys.getAll(fetchSize, sorting),
|
getAllProjectsQueryObject({ fetchSize, sorting }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAllTimelineEntries({
|
||||||
|
projectId,
|
||||||
|
fetchSize,
|
||||||
|
sorting,
|
||||||
|
}: {
|
||||||
|
projectId: number
|
||||||
|
fetchSize: number
|
||||||
|
sorting: any
|
||||||
|
}) {
|
||||||
|
return useInfiniteQuery<Paginated<TimelineEntry>>({
|
||||||
|
queryKey: timelineKeys.getAll(fetchSize, sorting),
|
||||||
|
|
||||||
|
queryFn: ({ pageParam = 0 }) => {
|
||||||
|
return {
|
||||||
|
data: Array(25).fill({
|
||||||
|
ID: projectId,
|
||||||
|
name: 'Aufbau',
|
||||||
|
from: new Date('2005-11-13'),
|
||||||
|
thru: new Date('2005-11-14'),
|
||||||
|
manualFrom: false,
|
||||||
|
manualThru: false,
|
||||||
|
}),
|
||||||
|
meta: {
|
||||||
|
totalCount: 75,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
// const data = await setTimeout<Array<Paginated<Project>>>(() => {}, 2500)
|
||||||
|
// return data
|
||||||
|
},
|
||||||
|
|
||||||
|
initialPageParam: 0,
|
||||||
|
getNextPageParam: (_lastGroup, groups) => groups.length,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
placeholderData: keepPreviousData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAllProjectTodos({
|
||||||
|
projectId,
|
||||||
|
fetchSize,
|
||||||
|
sorting,
|
||||||
|
}: {
|
||||||
|
projectId: number
|
||||||
|
fetchSize: number
|
||||||
|
sorting: any
|
||||||
|
}) {
|
||||||
|
return useInfiniteQuery<Paginated<TimelineEntry>>({
|
||||||
|
queryKey: todoKeys.getAll(fetchSize, sorting),
|
||||||
|
|
||||||
queryFn: async ({ pageParam = 0 }) => {
|
queryFn: async ({ pageParam = 0 }) => {
|
||||||
const start = (pageParam as number) * fetchSize
|
const start = (pageParam as number) * fetchSize
|
||||||
const data = await fetch(
|
|
||||||
env.VITE_BACKEND_URI +
|
const { id, desc } = sorting[0]
|
||||||
'/v1/projects/all?' +
|
|
||||||
new URLSearchParams(sorting[0]),
|
const searchParams = new URLSearchParams()
|
||||||
{
|
|
||||||
credentials: 'include',
|
searchParams.append('id', id)
|
||||||
headers: {
|
searchParams.append('desc', desc)
|
||||||
'X-OFFSET': start.toString(),
|
searchParams.append('per-page', fetchSize.toString())
|
||||||
'X-PER-PAGE': fetchSize.toString(),
|
searchParams.append('offset', start.toString())
|
||||||
},
|
|
||||||
},
|
const url =
|
||||||
)
|
(await getBackendURI()) +
|
||||||
|
'/v1/todos/project/' +
|
||||||
|
projectId +
|
||||||
|
'?' +
|
||||||
|
searchParams
|
||||||
|
const data = await fetchWithAuth(url)
|
||||||
return await data.json()
|
return await data.json()
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -83,14 +198,25 @@ export function useAllProjects({
|
|||||||
export function useProjectEdit(id: number) {
|
export function useProjectEdit(id: number) {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (project: Project) => {
|
mutationFn: async (project: Project) => {
|
||||||
await fetch(env.VITE_BACKEND_URI + '/v1/projects/' + id + '/edit', {
|
const oidc = await getOidc()
|
||||||
headers: {
|
if (!oidc.isUserLoggedIn) {
|
||||||
'content-type': 'application/json',
|
throw Error('Logical error in our application flow')
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessToken = await oidc.getAccessToken()
|
||||||
|
|
||||||
|
await fetchWithAuth(
|
||||||
|
(await getBackendURI()) + '/v1/projects/' + id + '/edit',
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(project),
|
||||||
|
credentials: 'include',
|
||||||
},
|
},
|
||||||
method: 'POST',
|
)
|
||||||
body: JSON.stringify(project),
|
|
||||||
credentials: 'include',
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -100,14 +226,17 @@ export function useProjectCreate() {
|
|||||||
|
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (project: Project) => {
|
mutationFn: async (project: Project) => {
|
||||||
const res = await fetch(env.VITE_BACKEND_URI + '/v1/projects/new', {
|
const res = await fetchWithAuth(
|
||||||
headers: {
|
(await getBackendURI()) + '/v1/projects/new',
|
||||||
'content-type': 'application/json',
|
{
|
||||||
|
headers: {
|
||||||
|
'content-type': 'application/json',
|
||||||
|
},
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(project),
|
||||||
|
credentials: 'include',
|
||||||
},
|
},
|
||||||
method: 'POST',
|
)
|
||||||
body: JSON.stringify(project),
|
|
||||||
credentials: 'include',
|
|
||||||
})
|
|
||||||
const newCurrentMandant = await res.json()
|
const newCurrentMandant = await res.json()
|
||||||
queryClient.invalidateQueries({ queryKey: projectKeys.lists() })
|
queryClient.invalidateQueries({ queryKey: projectKeys.lists() })
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData(
|
||||||
|
|||||||
20
src/fetch-polyfill.js
Normal file
20
src/fetch-polyfill.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// fetch-polyfill.js
|
||||||
|
import fetch, {
|
||||||
|
Blob,
|
||||||
|
blobFrom,
|
||||||
|
blobFromSync,
|
||||||
|
File,
|
||||||
|
fileFrom,
|
||||||
|
fileFromSync,
|
||||||
|
FormData,
|
||||||
|
Headers,
|
||||||
|
Request,
|
||||||
|
Response,
|
||||||
|
} from 'node-fetch'
|
||||||
|
|
||||||
|
if (!globalThis.fetch) {
|
||||||
|
globalThis.fetch = fetch
|
||||||
|
globalThis.Headers = Headers
|
||||||
|
globalThis.Request = Request
|
||||||
|
globalThis.Response = Response
|
||||||
|
}
|
||||||
20
src/gen/mandant/v1/mandant-MandantService_connectquery.ts
Normal file
20
src/gen/mandant/v1/mandant-MandantService_connectquery.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts"
|
||||||
|
// @generated from file mandant/v1/mandant.proto (package mandant.v1, syntax proto3)
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
import { MandantService } from "./mandant_pb";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from rpc mandant.v1.MandantService.GetCurrentTenant
|
||||||
|
*/
|
||||||
|
export const getCurrentTenant = MandantService.method.getCurrentTenant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from rpc mandant.v1.MandantService.GetAllTenants
|
||||||
|
*/
|
||||||
|
export const getAllTenants = MandantService.method.getAllTenants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from rpc mandant.v1.MandantService.SetCurrentTenant
|
||||||
|
*/
|
||||||
|
export const setCurrentTenant = MandantService.method.setCurrentTenant;
|
||||||
217
src/gen/mandant/v1/mandant_pb.ts
Normal file
217
src/gen/mandant/v1/mandant_pb.ts
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
// @generated by protoc-gen-es v2.10.0 with parameter "target=ts"
|
||||||
|
// @generated from file mandant/v1/mandant.proto (package mandant.v1, syntax proto3)
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
||||||
|
import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
|
||||||
|
import type { Message } from "@bufbuild/protobuf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the file mandant/v1/mandant.proto.
|
||||||
|
*/
|
||||||
|
export const file_mandant_v1_mandant: GenFile = /*@__PURE__*/
|
||||||
|
fileDesc("ChhtYW5kYW50L3YxL21hbmRhbnQucHJvdG8SCm1hbmRhbnQudjEiGQoXR2V0Q3VycmVudFRlbmFudFJlcXVlc3QiHgoQR2V0VGVuYW50UmVxdWVzdBIKCgJpZBgBIAEoAyJYChFHZXRUZW5hbnRSZXNwb25zZRIKCgJpZBgBIAEoAxIMCgRuYW1lGAIgASgJEgwKBHBsYW4YAyABKAkSDAoEbG9nbxgEIAEoCRINCgVjb2xvchgFIAEoCSJSChFMaXN0VGVuYW50UmVxdWVzdBIMCgRwYWdlGAEgASgFEhAKCHBlcl9wYWdlGAIgASgFEhAKCG9yYmVyX2J5GAMgASgJEgsKA2FzYxgEIAEoCCIeCghNZXRhZGF0YRISCgp0b3RhbENvdW50GAEgASgFImcKFExpc3RQcm9qZWN0c1Jlc3BvbnNlEisKBGRhdGEYASADKAsyHS5tYW5kYW50LnYxLkdldFRlbmFudFJlc3BvbnNlEiIKBG1ldGEYAiABKAsyFC5tYW5kYW50LnYxLk1ldGFkYXRhIiwKF1NldEN1cnJlbnRUZW5hbnRSZXF1ZXN0EhEKCXRlbmFudF9pZBgBIAEoAyIrChhTZXRDdXJyZW50VGVuYW50UmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCDKZAgoOTWFuZGFudFNlcnZpY2USVgoQR2V0Q3VycmVudFRlbmFudBIjLm1hbmRhbnQudjEuR2V0Q3VycmVudFRlbmFudFJlcXVlc3QaHS5tYW5kYW50LnYxLkdldFRlbmFudFJlc3BvbnNlElAKDUdldEFsbFRlbmFudHMSHS5tYW5kYW50LnYxLkxpc3RUZW5hbnRSZXF1ZXN0GiAubWFuZGFudC52MS5MaXN0UHJvamVjdHNSZXNwb25zZRJdChBTZXRDdXJyZW50VGVuYW50EiMubWFuZGFudC52MS5TZXRDdXJyZW50VGVuYW50UmVxdWVzdBokLm1hbmRhbnQudjEuU2V0Q3VycmVudFRlbmFudFJlc3BvbnNlQpwBCg5jb20ubWFuZGFudC52MUIMTWFuZGFudFByb3RvUAFaM2dpdC5rb2NvZGVyLnh5ei9rb2NvZGVkL3Z0L2dlbi9tYW5kYW50L3YxO21hbmRhbnR2MaICA01YWKoCCk1hbmRhbnQuVjHKAgpNYW5kYW50XFYx4gIWTWFuZGFudFxWMVxHUEJNZXRhZGF0YeoCC01hbmRhbnQ6OlYxYgZwcm90bzM");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message mandant.v1.GetCurrentTenantRequest
|
||||||
|
*/
|
||||||
|
export type GetCurrentTenantRequest = Message<"mandant.v1.GetCurrentTenantRequest"> & {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message mandant.v1.GetCurrentTenantRequest.
|
||||||
|
* Use `create(GetCurrentTenantRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const GetCurrentTenantRequestSchema: GenMessage<GetCurrentTenantRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_mandant_v1_mandant, 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message mandant.v1.GetTenantRequest
|
||||||
|
*/
|
||||||
|
export type GetTenantRequest = Message<"mandant.v1.GetTenantRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int64 id = 1;
|
||||||
|
*/
|
||||||
|
id: bigint;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message mandant.v1.GetTenantRequest.
|
||||||
|
* Use `create(GetTenantRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const GetTenantRequestSchema: GenMessage<GetTenantRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_mandant_v1_mandant, 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message mandant.v1.GetTenantResponse
|
||||||
|
*/
|
||||||
|
export type GetTenantResponse = Message<"mandant.v1.GetTenantResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int64 id = 1;
|
||||||
|
*/
|
||||||
|
id: bigint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string name = 2;
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string plan = 3;
|
||||||
|
*/
|
||||||
|
plan: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string logo = 4;
|
||||||
|
*/
|
||||||
|
logo: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string color = 5;
|
||||||
|
*/
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message mandant.v1.GetTenantResponse.
|
||||||
|
* Use `create(GetTenantResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const GetTenantResponseSchema: GenMessage<GetTenantResponse> = /*@__PURE__*/
|
||||||
|
messageDesc(file_mandant_v1_mandant, 2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message mandant.v1.ListTenantRequest
|
||||||
|
*/
|
||||||
|
export type ListTenantRequest = Message<"mandant.v1.ListTenantRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int32 page = 1;
|
||||||
|
*/
|
||||||
|
page: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: int32 per_page = 2;
|
||||||
|
*/
|
||||||
|
perPage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string orber_by = 3;
|
||||||
|
*/
|
||||||
|
orberBy: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: bool asc = 4;
|
||||||
|
*/
|
||||||
|
asc: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message mandant.v1.ListTenantRequest.
|
||||||
|
* Use `create(ListTenantRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const ListTenantRequestSchema: GenMessage<ListTenantRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_mandant_v1_mandant, 3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message mandant.v1.Metadata
|
||||||
|
*/
|
||||||
|
export type Metadata = Message<"mandant.v1.Metadata"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int32 totalCount = 1;
|
||||||
|
*/
|
||||||
|
totalCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message mandant.v1.Metadata.
|
||||||
|
* Use `create(MetadataSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const MetadataSchema: GenMessage<Metadata> = /*@__PURE__*/
|
||||||
|
messageDesc(file_mandant_v1_mandant, 4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message mandant.v1.ListProjectsResponse
|
||||||
|
*/
|
||||||
|
export type ListProjectsResponse = Message<"mandant.v1.ListProjectsResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated mandant.v1.GetTenantResponse data = 1;
|
||||||
|
*/
|
||||||
|
data: GetTenantResponse[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: mandant.v1.Metadata meta = 2;
|
||||||
|
*/
|
||||||
|
meta?: Metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message mandant.v1.ListProjectsResponse.
|
||||||
|
* Use `create(ListProjectsResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const ListProjectsResponseSchema: GenMessage<ListProjectsResponse> = /*@__PURE__*/
|
||||||
|
messageDesc(file_mandant_v1_mandant, 5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message mandant.v1.SetCurrentTenantRequest
|
||||||
|
*/
|
||||||
|
export type SetCurrentTenantRequest = Message<"mandant.v1.SetCurrentTenantRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int64 tenant_id = 1;
|
||||||
|
*/
|
||||||
|
tenantId: bigint;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message mandant.v1.SetCurrentTenantRequest.
|
||||||
|
* Use `create(SetCurrentTenantRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const SetCurrentTenantRequestSchema: GenMessage<SetCurrentTenantRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_mandant_v1_mandant, 6);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message mandant.v1.SetCurrentTenantResponse
|
||||||
|
*/
|
||||||
|
export type SetCurrentTenantResponse = Message<"mandant.v1.SetCurrentTenantResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: bool success = 1;
|
||||||
|
*/
|
||||||
|
success: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message mandant.v1.SetCurrentTenantResponse.
|
||||||
|
* Use `create(SetCurrentTenantResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const SetCurrentTenantResponseSchema: GenMessage<SetCurrentTenantResponse> = /*@__PURE__*/
|
||||||
|
messageDesc(file_mandant_v1_mandant, 7);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from service mandant.v1.MandantService
|
||||||
|
*/
|
||||||
|
export const MandantService: GenService<{
|
||||||
|
/**
|
||||||
|
* @generated from rpc mandant.v1.MandantService.GetCurrentTenant
|
||||||
|
*/
|
||||||
|
getCurrentTenant: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof GetCurrentTenantRequestSchema;
|
||||||
|
output: typeof GetTenantResponseSchema;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @generated from rpc mandant.v1.MandantService.GetAllTenants
|
||||||
|
*/
|
||||||
|
getAllTenants: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof ListTenantRequestSchema;
|
||||||
|
output: typeof ListProjectsResponseSchema;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @generated from rpc mandant.v1.MandantService.SetCurrentTenant
|
||||||
|
*/
|
||||||
|
setCurrentTenant: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof SetCurrentTenantRequestSchema;
|
||||||
|
output: typeof SetCurrentTenantResponseSchema;
|
||||||
|
},
|
||||||
|
}> = /*@__PURE__*/
|
||||||
|
serviceDesc(file_mandant_v1_mandant, 0);
|
||||||
|
|
||||||
80
src/gen/messagebus/v1/messagebus_pb.ts
Normal file
80
src/gen/messagebus/v1/messagebus_pb.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// @generated by protoc-gen-es v2.10.0 with parameter "target=ts"
|
||||||
|
// @generated from file messagebus/v1/messagebus.proto (package messagebus.v1, syntax proto3)
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
||||||
|
import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
|
||||||
|
import type { Message } from "@bufbuild/protobuf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the file messagebus/v1/messagebus.proto.
|
||||||
|
*/
|
||||||
|
export const file_messagebus_v1_messagebus: GenFile = /*@__PURE__*/
|
||||||
|
fileDesc("Ch5tZXNzYWdlYnVzL3YxL21lc3NhZ2VidXMucHJvdG8SDW1lc3NhZ2VidXMudjEiJAoQTWVzc2FnZUJ1c0VudGl0eRIQCghxdWVyeUtleRgBIAEoCSIvCi1TdWJzY3JpYmVUb0Nvbm5lY3RJbnZhbGlkYXRpb25SZXF1ZXN0c1JlcXVlc3QqOwoUTWVzc2FnZUJ1c0VudGl0eVR5cGUSCQoFT1RIRVIQABIYChRJTlZBTElEQVRJT05fUkVRVUVTVBABMp8BChFNZXNzYWdlQnVzU2VydmljZRKJAQomU3Vic2NyaWJlVG9Db25uZWN0SW52YWxpZGF0aW9uUmVxdWVzdHMSPC5tZXNzYWdlYnVzLnYxLlN1YnNjcmliZVRvQ29ubmVjdEludmFsaWRhdGlvblJlcXVlc3RzUmVxdWVzdBofLm1lc3NhZ2VidXMudjEuTWVzc2FnZUJ1c0VudGl0eTABQrQBChFjb20ubWVzc2FnZWJ1cy52MUIPTWVzc2FnZWJ1c1Byb3RvUAFaOWdpdC5rb2NvZGVyLnh5ei9rb2NvZGVkL3Z0L2dlbi9tZXNzYWdlYnVzL3YxO21lc3NhZ2VidXN2MaICA01YWKoCDU1lc3NhZ2VidXMuVjHKAg1NZXNzYWdlYnVzXFYx4gIZTWVzc2FnZWJ1c1xWMVxHUEJNZXRhZGF0YeoCDk1lc3NhZ2VidXM6OlYxYgZwcm90bzM");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message messagebus.v1.MessageBusEntity
|
||||||
|
*/
|
||||||
|
export type MessageBusEntity = Message<"messagebus.v1.MessageBusEntity"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: string queryKey = 1;
|
||||||
|
*/
|
||||||
|
queryKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message messagebus.v1.MessageBusEntity.
|
||||||
|
* Use `create(MessageBusEntitySchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const MessageBusEntitySchema: GenMessage<MessageBusEntity> = /*@__PURE__*/
|
||||||
|
messageDesc(file_messagebus_v1_messagebus, 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message messagebus.v1.SubscribeToConnectInvalidationRequestsRequest
|
||||||
|
*/
|
||||||
|
export type SubscribeToConnectInvalidationRequestsRequest = Message<"messagebus.v1.SubscribeToConnectInvalidationRequestsRequest"> & {
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message messagebus.v1.SubscribeToConnectInvalidationRequestsRequest.
|
||||||
|
* Use `create(SubscribeToConnectInvalidationRequestsRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const SubscribeToConnectInvalidationRequestsRequestSchema: GenMessage<SubscribeToConnectInvalidationRequestsRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_messagebus_v1_messagebus, 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum messagebus.v1.MessageBusEntityType
|
||||||
|
*/
|
||||||
|
export enum MessageBusEntityType {
|
||||||
|
/**
|
||||||
|
* @generated from enum value: OTHER = 0;
|
||||||
|
*/
|
||||||
|
OTHER = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum value: INVALIDATION_REQUEST = 1;
|
||||||
|
*/
|
||||||
|
INVALIDATION_REQUEST = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the enum messagebus.v1.MessageBusEntityType.
|
||||||
|
*/
|
||||||
|
export const MessageBusEntityTypeSchema: GenEnum<MessageBusEntityType> = /*@__PURE__*/
|
||||||
|
enumDesc(file_messagebus_v1_messagebus, 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from service messagebus.v1.MessageBusService
|
||||||
|
*/
|
||||||
|
export const MessageBusService: GenService<{
|
||||||
|
/**
|
||||||
|
* @generated from rpc messagebus.v1.MessageBusService.SubscribeToConnectInvalidationRequests
|
||||||
|
*/
|
||||||
|
subscribeToConnectInvalidationRequests: {
|
||||||
|
methodKind: "server_streaming";
|
||||||
|
input: typeof SubscribeToConnectInvalidationRequestsRequestSchema;
|
||||||
|
output: typeof MessageBusEntitySchema;
|
||||||
|
},
|
||||||
|
}> = /*@__PURE__*/
|
||||||
|
serviceDesc(file_messagebus_v1_messagebus, 0);
|
||||||
|
|
||||||
15
src/gen/project/v1/project-ProjectService_connectquery.ts
Normal file
15
src/gen/project/v1/project-ProjectService_connectquery.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts"
|
||||||
|
// @generated from file project/v1/project.proto (package project.v1, syntax proto3)
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
import { ProjectService } from "./project_pb";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from rpc project.v1.ProjectService.GetProject
|
||||||
|
*/
|
||||||
|
export const getProject = ProjectService.method.getProject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from rpc project.v1.ProjectService.ListProjects
|
||||||
|
*/
|
||||||
|
export const listProjects = ProjectService.method.listProjects;
|
||||||
177
src/gen/project/v1/project_pb.ts
Normal file
177
src/gen/project/v1/project_pb.ts
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
// @generated by protoc-gen-es v2.10.0 with parameter "target=ts"
|
||||||
|
// @generated from file project/v1/project.proto (package project.v1, syntax proto3)
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
||||||
|
import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
|
||||||
|
import type { Message } from "@bufbuild/protobuf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the file project/v1/project.proto.
|
||||||
|
*/
|
||||||
|
export const file_project_v1_project: GenFile = /*@__PURE__*/
|
||||||
|
fileDesc("Chhwcm9qZWN0L3YxL3Byb2plY3QucHJvdG8SCnByb2plY3QudjEiHwoRR2V0UHJvamVjdFJlcXVlc3QSCgoCaWQYASABKAUilwIKEkdldFByb2plY3RSZXNwb25zZRIKCgJpZBgBIAEoAxIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEhwKD2lzX21hdGVyaWFsaXplZBgFIAEoCEgAiAEBEhwKD2lzX3BlcnNvbmFsaXplZBgGIAEoCEgBiAEBEhkKDGlzX2NvbmZpcm1lZBgHIAEoCEgCiAEBEhQKB2lzX3BhaWQYCCABKAhIA4gBARIUCgdpc19kb25lGAkgASgISASIAQFCEgoQX2lzX21hdGVyaWFsaXplZEISChBfaXNfcGVyc29uYWxpemVkQg8KDV9pc19jb25maXJtZWRCCgoIX2lzX3BhaWRCCgoIX2lzX2RvbmUiVAoTTGlzdFByb2plY3RzUmVxdWVzdBIMCgRwYWdlGAEgASgFEhAKCHBlcl9wYWdlGAIgASgFEhAKCG9yYmVyX2J5GAMgASgJEgsKA2FzYxgEIAEoCCIeCghNZXRhZGF0YRISCgp0b3RhbENvdW50GAEgASgFImgKFExpc3RQcm9qZWN0c1Jlc3BvbnNlEiwKBGRhdGEYASADKAsyHi5wcm9qZWN0LnYxLkdldFByb2plY3RSZXNwb25zZRIiCgRtZXRhGAIgASgLMhQucHJvamVjdC52MS5NZXRhZGF0YTKwAQoOUHJvamVjdFNlcnZpY2USSwoKR2V0UHJvamVjdBIdLnByb2plY3QudjEuR2V0UHJvamVjdFJlcXVlc3QaHi5wcm9qZWN0LnYxLkdldFByb2plY3RSZXNwb25zZRJRCgxMaXN0UHJvamVjdHMSHy5wcm9qZWN0LnYxLkxpc3RQcm9qZWN0c1JlcXVlc3QaIC5wcm9qZWN0LnYxLkxpc3RQcm9qZWN0c1Jlc3BvbnNlQpwBCg5jb20ucHJvamVjdC52MUIMUHJvamVjdFByb3RvUAFaM2dpdC5rb2NvZGVyLnh5ei9rb2NvZGVkL3Z0L2dlbi9wcm9qZWN0L3YxO3Byb2plY3R2MaICA1BYWKoCClByb2plY3QuVjHKAgpQcm9qZWN0XFYx4gIWUHJvamVjdFxWMVxHUEJNZXRhZGF0YeoCC1Byb2plY3Q6OlYxYgZwcm90bzM");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message project.v1.GetProjectRequest
|
||||||
|
*/
|
||||||
|
export type GetProjectRequest = Message<"project.v1.GetProjectRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int32 id = 1;
|
||||||
|
*/
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message project.v1.GetProjectRequest.
|
||||||
|
* Use `create(GetProjectRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const GetProjectRequestSchema: GenMessage<GetProjectRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_project_v1_project, 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message project.v1.GetProjectResponse
|
||||||
|
*/
|
||||||
|
export type GetProjectResponse = Message<"project.v1.GetProjectResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int64 id = 1;
|
||||||
|
*/
|
||||||
|
id: bigint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string name = 2;
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string description = 3;
|
||||||
|
*/
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: optional bool is_materialized = 5;
|
||||||
|
*/
|
||||||
|
isMaterialized?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: optional bool is_personalized = 6;
|
||||||
|
*/
|
||||||
|
isPersonalized?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: optional bool is_confirmed = 7;
|
||||||
|
*/
|
||||||
|
isConfirmed?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: optional bool is_paid = 8;
|
||||||
|
*/
|
||||||
|
isPaid?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: optional bool is_done = 9;
|
||||||
|
*/
|
||||||
|
isDone?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message project.v1.GetProjectResponse.
|
||||||
|
* Use `create(GetProjectResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const GetProjectResponseSchema: GenMessage<GetProjectResponse> = /*@__PURE__*/
|
||||||
|
messageDesc(file_project_v1_project, 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message project.v1.ListProjectsRequest
|
||||||
|
*/
|
||||||
|
export type ListProjectsRequest = Message<"project.v1.ListProjectsRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int32 page = 1;
|
||||||
|
*/
|
||||||
|
page: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: int32 per_page = 2;
|
||||||
|
*/
|
||||||
|
perPage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string orber_by = 3;
|
||||||
|
*/
|
||||||
|
orberBy: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: bool asc = 4;
|
||||||
|
*/
|
||||||
|
asc: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message project.v1.ListProjectsRequest.
|
||||||
|
* Use `create(ListProjectsRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const ListProjectsRequestSchema: GenMessage<ListProjectsRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_project_v1_project, 2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message project.v1.Metadata
|
||||||
|
*/
|
||||||
|
export type Metadata = Message<"project.v1.Metadata"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int32 totalCount = 1;
|
||||||
|
*/
|
||||||
|
totalCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message project.v1.Metadata.
|
||||||
|
* Use `create(MetadataSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const MetadataSchema: GenMessage<Metadata> = /*@__PURE__*/
|
||||||
|
messageDesc(file_project_v1_project, 3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message project.v1.ListProjectsResponse
|
||||||
|
*/
|
||||||
|
export type ListProjectsResponse = Message<"project.v1.ListProjectsResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated project.v1.GetProjectResponse data = 1;
|
||||||
|
*/
|
||||||
|
data: GetProjectResponse[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: project.v1.Metadata meta = 2;
|
||||||
|
*/
|
||||||
|
meta?: Metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message project.v1.ListProjectsResponse.
|
||||||
|
* Use `create(ListProjectsResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const ListProjectsResponseSchema: GenMessage<ListProjectsResponse> = /*@__PURE__*/
|
||||||
|
messageDesc(file_project_v1_project, 4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from service project.v1.ProjectService
|
||||||
|
*/
|
||||||
|
export const ProjectService: GenService<{
|
||||||
|
/**
|
||||||
|
* @generated from rpc project.v1.ProjectService.GetProject
|
||||||
|
*/
|
||||||
|
getProject: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof GetProjectRequestSchema;
|
||||||
|
output: typeof GetProjectResponseSchema;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @generated from rpc project.v1.ProjectService.ListProjects
|
||||||
|
*/
|
||||||
|
listProjects: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof ListProjectsRequestSchema;
|
||||||
|
output: typeof ListProjectsResponseSchema;
|
||||||
|
},
|
||||||
|
}> = /*@__PURE__*/
|
||||||
|
serviceDesc(file_project_v1_project, 0);
|
||||||
|
|
||||||
15
src/gen/todo/v1/todo-ProjectService_connectquery.ts
Normal file
15
src/gen/todo/v1/todo-ProjectService_connectquery.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts"
|
||||||
|
// @generated from file todo/v1/todo.proto (package todo.v1, syntax proto3)
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
import { ProjectService } from "./todo_pb";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from rpc todo.v1.ProjectService.GetProject
|
||||||
|
*/
|
||||||
|
export const getProject = ProjectService.method.getProject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from rpc todo.v1.ProjectService.ListProjects
|
||||||
|
*/
|
||||||
|
export const listProjects = ProjectService.method.listProjects;
|
||||||
15
src/gen/todo/v1/todo-TodoService_connectquery.ts
Normal file
15
src/gen/todo/v1/todo-TodoService_connectquery.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// @generated by protoc-gen-connect-query v2.2.0 with parameter "target=ts"
|
||||||
|
// @generated from file todo/v1/todo.proto (package todo.v1, syntax proto3)
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
import { TodoService } from "./todo_pb";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from rpc todo.v1.TodoService.GetTodo
|
||||||
|
*/
|
||||||
|
export const getTodo = TodoService.method.getTodo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from rpc todo.v1.TodoService.ListTodos
|
||||||
|
*/
|
||||||
|
export const listTodos = TodoService.method.listTodos;
|
||||||
287
src/gen/todo/v1/todo_pb.ts
Normal file
287
src/gen/todo/v1/todo_pb.ts
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
// @generated by protoc-gen-es v2.10.0 with parameter "target=ts"
|
||||||
|
// @generated from file todo/v1/todo.proto (package todo.v1, syntax proto3)
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2";
|
||||||
|
import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2";
|
||||||
|
import type { Message } from "@bufbuild/protobuf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the file todo/v1/todo.proto.
|
||||||
|
*/
|
||||||
|
export const file_todo_v1_todo: GenFile = /*@__PURE__*/
|
||||||
|
fileDesc("ChJ0b2RvL3YxL3RvZG8ucHJvdG8SB3RvZG8udjEiHQoPR2V0VG9kb3NSZXF1ZXN0EgoKAmlkGAEgASgFImMKEEdldFRvZG9zUmVzcG9uc2USCgoCaWQYASABKAMSDQoFdGl0bGUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSHwoGc3RhdHVzGAQgASgOMg8udG9kby52MS5TdGF0dXMicwoQTGlzdFRvZG9zUmVxdWVzdBIMCgRwYWdlGAEgASgFEhAKCHBlcl9wYWdlGAIgASgFEhAKCG9yYmVyX2J5GAMgASgJEgsKA2FzYxgEIAEoCBIgCgdmaWx0ZXJzGAUgAygLMg8udG9kby52MS5GaWx0ZXIiXQoGRmlsdGVyEh0KBWZpZWxkGAEgASgOMg4udG9kby52MS5GaWVsZBINCgV2YWx1ZRgCIAEoCRIlCglvcGVyYXRpb24YAyABKA4yEi50b2RvLnYxLk9wZXJhdGlvbiIeCghNZXRhZGF0YRISCgp0b3RhbENvdW50GAEgASgFIl0KEUxpc3RUb2Rvc1Jlc3BvbnNlEicKBGRhdGEYASADKAsyGS50b2RvLnYxLkdldFRvZG9zUmVzcG9uc2USHwoEbWV0YRgCIAEoCzIRLnRvZG8udjEuTWV0YWRhdGEqOgoGU3RhdHVzEggKBFRvZG8QABIRCg1OZWVkc01vcmVJbmZvEAESCQoFRG9pbmcQAhIICgREb25lEAMqSwoFRmllbGQSCwoHRmllbGRJZBAAEg4KCkZpZWxkVGl0bGUQARIUChBGaWVsZERlc2NyaXB0aW9uEAISDwoLRmllbGRTdGF0dXMQAypPCglPcGVyYXRpb24SCgoGRXF1YWxzEAASDQoJTm90RXF1YWxzEAESDwoLR3JlYXRlclRoYW4QAhIMCghMZXNzVGhhbhADEggKBExpa2UQBDKRAQoLVG9kb1NlcnZpY2USPgoHR2V0VG9kbxIYLnRvZG8udjEuR2V0VG9kb3NSZXF1ZXN0GhkudG9kby52MS5HZXRUb2Rvc1Jlc3BvbnNlEkIKCUxpc3RUb2RvcxIZLnRvZG8udjEuTGlzdFRvZG9zUmVxdWVzdBoaLnRvZG8udjEuTGlzdFRvZG9zUmVzcG9uc2VChAEKC2NvbS50b2RvLnYxQglUb2RvUHJvdG9QAVotZ2l0LmtvY29kZXIueHl6L2tvY29kZWQvdnQvZ2VuL3RvZG8vdjE7dG9kb3YxogIDVFhYqgIHVG9kby5WMcoCB1RvZG9cVjHiAhNUb2RvXFYxXEdQQk1ldGFkYXRh6gIIVG9kbzo6VjFiBnByb3RvMw");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message todo.v1.GetTodosRequest
|
||||||
|
*/
|
||||||
|
export type GetTodosRequest = Message<"todo.v1.GetTodosRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int32 id = 1;
|
||||||
|
*/
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message todo.v1.GetTodosRequest.
|
||||||
|
* Use `create(GetTodosRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const GetTodosRequestSchema: GenMessage<GetTodosRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_todo_v1_todo, 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message todo.v1.GetTodosResponse
|
||||||
|
*/
|
||||||
|
export type GetTodosResponse = Message<"todo.v1.GetTodosResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int64 id = 1;
|
||||||
|
*/
|
||||||
|
id: bigint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string title = 2;
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string description = 3;
|
||||||
|
*/
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: todo.v1.Status status = 4;
|
||||||
|
*/
|
||||||
|
status: Status;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message todo.v1.GetTodosResponse.
|
||||||
|
* Use `create(GetTodosResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const GetTodosResponseSchema: GenMessage<GetTodosResponse> = /*@__PURE__*/
|
||||||
|
messageDesc(file_todo_v1_todo, 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message todo.v1.ListTodosRequest
|
||||||
|
*/
|
||||||
|
export type ListTodosRequest = Message<"todo.v1.ListTodosRequest"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int32 page = 1;
|
||||||
|
*/
|
||||||
|
page: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: int32 per_page = 2;
|
||||||
|
*/
|
||||||
|
perPage: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string orber_by = 3;
|
||||||
|
*/
|
||||||
|
orberBy: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: bool asc = 4;
|
||||||
|
*/
|
||||||
|
asc: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated todo.v1.Filter filters = 5;
|
||||||
|
*/
|
||||||
|
filters: Filter[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message todo.v1.ListTodosRequest.
|
||||||
|
* Use `create(ListTodosRequestSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const ListTodosRequestSchema: GenMessage<ListTodosRequest> = /*@__PURE__*/
|
||||||
|
messageDesc(file_todo_v1_todo, 2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message todo.v1.Filter
|
||||||
|
*/
|
||||||
|
export type Filter = Message<"todo.v1.Filter"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: todo.v1.Field field = 1;
|
||||||
|
*/
|
||||||
|
field: Field;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: string value = 2;
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: todo.v1.Operation operation = 3;
|
||||||
|
*/
|
||||||
|
operation: Operation;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message todo.v1.Filter.
|
||||||
|
* Use `create(FilterSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const FilterSchema: GenMessage<Filter> = /*@__PURE__*/
|
||||||
|
messageDesc(file_todo_v1_todo, 3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message todo.v1.Metadata
|
||||||
|
*/
|
||||||
|
export type Metadata = Message<"todo.v1.Metadata"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: int32 totalCount = 1;
|
||||||
|
*/
|
||||||
|
totalCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message todo.v1.Metadata.
|
||||||
|
* Use `create(MetadataSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const MetadataSchema: GenMessage<Metadata> = /*@__PURE__*/
|
||||||
|
messageDesc(file_todo_v1_todo, 4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from message todo.v1.ListTodosResponse
|
||||||
|
*/
|
||||||
|
export type ListTodosResponse = Message<"todo.v1.ListTodosResponse"> & {
|
||||||
|
/**
|
||||||
|
* @generated from field: repeated todo.v1.GetTodosResponse data = 1;
|
||||||
|
*/
|
||||||
|
data: GetTodosResponse[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from field: todo.v1.Metadata meta = 2;
|
||||||
|
*/
|
||||||
|
meta?: Metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the message todo.v1.ListTodosResponse.
|
||||||
|
* Use `create(ListTodosResponseSchema)` to create a new message.
|
||||||
|
*/
|
||||||
|
export const ListTodosResponseSchema: GenMessage<ListTodosResponse> = /*@__PURE__*/
|
||||||
|
messageDesc(file_todo_v1_todo, 5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum todo.v1.Status
|
||||||
|
*/
|
||||||
|
export enum Status {
|
||||||
|
/**
|
||||||
|
* @generated from enum value: Todo = 0;
|
||||||
|
*/
|
||||||
|
Todo = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum value: NeedsMoreInfo = 1;
|
||||||
|
*/
|
||||||
|
NeedsMoreInfo = 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum value: Doing = 2;
|
||||||
|
*/
|
||||||
|
Doing = 2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum value: Done = 3;
|
||||||
|
*/
|
||||||
|
Done = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the enum todo.v1.Status.
|
||||||
|
*/
|
||||||
|
export const StatusSchema: GenEnum<Status> = /*@__PURE__*/
|
||||||
|
enumDesc(file_todo_v1_todo, 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum todo.v1.Field
|
||||||
|
*/
|
||||||
|
export enum Field {
|
||||||
|
/**
|
||||||
|
* @generated from enum value: FieldId = 0;
|
||||||
|
*/
|
||||||
|
FieldId = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum value: FieldTitle = 1;
|
||||||
|
*/
|
||||||
|
FieldTitle = 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum value: FieldDescription = 2;
|
||||||
|
*/
|
||||||
|
FieldDescription = 2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum value: FieldStatus = 3;
|
||||||
|
*/
|
||||||
|
FieldStatus = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the enum todo.v1.Field.
|
||||||
|
*/
|
||||||
|
export const FieldSchema: GenEnum<Field> = /*@__PURE__*/
|
||||||
|
enumDesc(file_todo_v1_todo, 1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum todo.v1.Operation
|
||||||
|
*/
|
||||||
|
export enum Operation {
|
||||||
|
/**
|
||||||
|
* @generated from enum value: Equals = 0;
|
||||||
|
*/
|
||||||
|
Equals = 0,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum value: NotEquals = 1;
|
||||||
|
*/
|
||||||
|
NotEquals = 1,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum value: GreaterThan = 2;
|
||||||
|
*/
|
||||||
|
GreaterThan = 2,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum value: LessThan = 3;
|
||||||
|
*/
|
||||||
|
LessThan = 3,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from enum value: Like = 4;
|
||||||
|
*/
|
||||||
|
Like = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes the enum todo.v1.Operation.
|
||||||
|
*/
|
||||||
|
export const OperationSchema: GenEnum<Operation> = /*@__PURE__*/
|
||||||
|
enumDesc(file_todo_v1_todo, 2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @generated from service todo.v1.TodoService
|
||||||
|
*/
|
||||||
|
export const TodoService: GenService<{
|
||||||
|
/**
|
||||||
|
* @generated from rpc todo.v1.TodoService.GetTodo
|
||||||
|
*/
|
||||||
|
getTodo: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof GetTodosRequestSchema;
|
||||||
|
output: typeof GetTodosResponseSchema;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* @generated from rpc todo.v1.TodoService.ListTodos
|
||||||
|
*/
|
||||||
|
listTodos: {
|
||||||
|
methodKind: "unary";
|
||||||
|
input: typeof ListTodosRequestSchema;
|
||||||
|
output: typeof ListTodosResponseSchema;
|
||||||
|
},
|
||||||
|
}> = /*@__PURE__*/
|
||||||
|
serviceDesc(file_todo_v1_todo, 0);
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
|
|||||||
* Accepts callback refs and RefObject(s)
|
* Accepts callback refs and RefObject(s)
|
||||||
*/
|
*/
|
||||||
function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
|
function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
|
||||||
// biome-ignore lint/correctness/useExhaustiveDependencies: we don't want to re-run this callback when the refs change
|
// biome-ignore lint/correctness/useExhaustiveDependencies: we want to memoize by all values
|
||||||
return React.useCallback(composeRefs(...refs), refs);
|
return React.useCallback(composeRefs(...refs), refs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
248
src/lib/ogimage.ts
Normal file
248
src/lib/ogimage.ts
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
import { env } from '@/env'
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { encode } from 'blurhash'
|
||||||
|
import { getPixels } from '@unpic/pixels'
|
||||||
|
import { blurhashToCssGradientString } from '@unpic/placeholder'
|
||||||
|
import { ImageResponse } from '@vercel/og'
|
||||||
|
|
||||||
|
const SITE = {
|
||||||
|
TITLE: 'Eventory',
|
||||||
|
URL_SHORT: 'https://vt.kocoder.xyz',
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Loads a font from Google Fonts
|
||||||
|
* @param font - The font name
|
||||||
|
* @param text - The text to load the font for
|
||||||
|
* @param weight - The font weight to load
|
||||||
|
* @returns The font data
|
||||||
|
*/
|
||||||
|
async function loadFont(font: string, text: string, weight: number) {
|
||||||
|
const url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weight}&text=${encodeURIComponent(text)}`
|
||||||
|
const css = await (await fetch(url)).text()
|
||||||
|
const resource = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)
|
||||||
|
if (resource) {
|
||||||
|
const response = await fetch(resource[1])
|
||||||
|
if (response.status == 200) {
|
||||||
|
return await response.arrayBuffer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error('failed to load font data')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a SEO image for a blog post or project
|
||||||
|
* @param title - The title of the blog post or project
|
||||||
|
* @param text - The text to display in the image
|
||||||
|
* @param description - The description of the blog post or project
|
||||||
|
* @param tags - The tags of the blog post or project
|
||||||
|
* @param image - The image to display in the image
|
||||||
|
* @returns The SEO image
|
||||||
|
*/
|
||||||
|
export interface SeoImageGeneratorProps {
|
||||||
|
title: string
|
||||||
|
text: string
|
||||||
|
image?: {
|
||||||
|
src: string
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
format: 'png' | 'jpg' | 'jpeg' | 'webp' | 'tiff' | 'gif' | 'svg' | 'avif'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateSeoImage({
|
||||||
|
title,
|
||||||
|
image,
|
||||||
|
text,
|
||||||
|
}: SeoImageGeneratorProps) {
|
||||||
|
console.log(env.VITE_ENVIRONMENT, env.VITE_ENVIRONMENT == 'development')
|
||||||
|
const assetsPrefix =
|
||||||
|
env.VITE_ENVIRONMENT == 'development' ? './public' : './dist'
|
||||||
|
const siteImage = fs.readFileSync(path.resolve(assetsPrefix, 'logo512.png'))
|
||||||
|
|
||||||
|
const siteImageElement = {
|
||||||
|
type: 'img',
|
||||||
|
props: {
|
||||||
|
tw: 'w-6 h-6 rounded-full mr-2',
|
||||||
|
src: siteImage.buffer,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const siteTitleElement = {
|
||||||
|
type: 'div',
|
||||||
|
props: {
|
||||||
|
tw: ' opacity-70',
|
||||||
|
style: {
|
||||||
|
fontFamily: 'Geist',
|
||||||
|
},
|
||||||
|
children: SITE.TITLE,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const separatorElement = {
|
||||||
|
type: 'div',
|
||||||
|
props: {
|
||||||
|
tw: 'px-2 opacity-70',
|
||||||
|
children: '•',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const siteSubtitleElement = {
|
||||||
|
type: 'div',
|
||||||
|
props: {
|
||||||
|
tw: 'opacity-70',
|
||||||
|
style: {
|
||||||
|
fontFamily: 'Geist',
|
||||||
|
},
|
||||||
|
children: title,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const siteImageAndTitleContainer = {
|
||||||
|
type: 'div',
|
||||||
|
props: {
|
||||||
|
tw: 'flex items-center justify-start w-full mb-2',
|
||||||
|
children: [
|
||||||
|
siteImageElement,
|
||||||
|
siteTitleElement,
|
||||||
|
separatorElement,
|
||||||
|
siteSubtitleElement,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
let imageElement = null
|
||||||
|
let shouldBlur = false
|
||||||
|
if (image && shouldBlur) {
|
||||||
|
const img = fs.readFileSync(
|
||||||
|
env.VITE_ENVIRONMENT === 'development'
|
||||||
|
? path.resolve(image.src.replace(/\?.*/, '').replace('/@fs', ''))
|
||||||
|
: path.resolve(image.src.replace('/', 'dist/')),
|
||||||
|
)
|
||||||
|
const imgData = await getPixels(new Uint8Array(img))
|
||||||
|
const data = Uint8ClampedArray.from(imgData.data)
|
||||||
|
const blurhash = encode(data, imgData.width, imgData.height, 4, 4)
|
||||||
|
const gradient = blurhashToCssGradientString(blurhash, 5, 5)
|
||||||
|
|
||||||
|
imageElement = {
|
||||||
|
type: 'div',
|
||||||
|
props: {
|
||||||
|
tw: 'w-full h-full absolute inset-0 rotate-90',
|
||||||
|
style: {
|
||||||
|
opacity: 0.07,
|
||||||
|
background: gradient,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const gradientElement = {
|
||||||
|
type: 'div',
|
||||||
|
props: {
|
||||||
|
style: {
|
||||||
|
background:
|
||||||
|
'radial-gradient(circle at bottom right, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0))',
|
||||||
|
},
|
||||||
|
tw: 'w-full h-full absolute flex inset-0 opacity-40',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainTextElement = {
|
||||||
|
type: 'div',
|
||||||
|
props: {
|
||||||
|
tw: 'text-5xl leading-none text-left',
|
||||||
|
style: {
|
||||||
|
fontFamily: 'Geist',
|
||||||
|
fontWeight: '400',
|
||||||
|
},
|
||||||
|
children: text,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerElement = {
|
||||||
|
type: 'div',
|
||||||
|
props: {
|
||||||
|
tw: 'flex flex-col items-center justify-center max-w-3xl w-full',
|
||||||
|
children: [
|
||||||
|
siteImageAndTitleContainer,
|
||||||
|
{
|
||||||
|
type: 'div',
|
||||||
|
props: {
|
||||||
|
tw: 'shrink flex w-full',
|
||||||
|
children: [mainTextElement],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const footerElement = {
|
||||||
|
type: 'div',
|
||||||
|
props: {
|
||||||
|
tw: 'absolute right-[15px] bottom-[15px] flex items-center justify-center opacity-70 text-xs',
|
||||||
|
children: [SITE.URL_SHORT],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = {
|
||||||
|
type: 'div',
|
||||||
|
props: {
|
||||||
|
tw: 'relative w-full h-full flex flex-col items-center justify-center relative relative text-white',
|
||||||
|
style: {
|
||||||
|
background: '#0a0a0a',
|
||||||
|
fontFamily: 'Geist',
|
||||||
|
fontSmoothing: 'antialiased',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
imageElement,
|
||||||
|
gradientElement,
|
||||||
|
containerElement,
|
||||||
|
footerElement,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const weights = [100, 200, 300, 400, 900] as const
|
||||||
|
|
||||||
|
const fontConfigs = [
|
||||||
|
...weights.map((weight) => ({ name: 'Geist', weight })),
|
||||||
|
...weights.map((weight) => ({ name: 'Geist Mono', weight })),
|
||||||
|
]
|
||||||
|
|
||||||
|
const fonts = await Promise.all(
|
||||||
|
fontConfigs.map(async ({ name, weight }) => ({
|
||||||
|
name,
|
||||||
|
data: await loadFont(name, text, weight),
|
||||||
|
style: 'normal' as const,
|
||||||
|
weight,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
return new ImageResponse(html, {
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
fonts,
|
||||||
|
debug: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a SEO image for a blog post or project
|
||||||
|
* @param entry - The entry to generate the SEO image for
|
||||||
|
* @param type - The type of the entry
|
||||||
|
* @returns The SEO image
|
||||||
|
*/
|
||||||
|
export interface SeoImageGeneratorForContentProps {
|
||||||
|
entry: object
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateSeoImageForContent() {
|
||||||
|
return generateSeoImage({
|
||||||
|
title: 'Eventory',
|
||||||
|
text: 'A simple deellele',
|
||||||
|
// image: entry.data.cover,
|
||||||
|
|
||||||
|
// ...rest,
|
||||||
|
})
|
||||||
|
}
|
||||||
157
src/lib/oidc.ts
Normal file
157
src/lib/oidc.ts
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
// import 'webcrypto-liner/build/webcrypto-liner.shim'
|
||||||
|
import { oidcSpa } from 'oidc-spa/react-tanstack-start'
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
export const {
|
||||||
|
bootstrapOidc,
|
||||||
|
useOidc,
|
||||||
|
getOidc,
|
||||||
|
// NOTE: Each time you enforceLogin on a route the oidc-spa vite plugin
|
||||||
|
// will automatically switch this route to `ssr: false`.
|
||||||
|
// This ensures that everything that can be SSR'd is and the rest is delayed to the client.
|
||||||
|
enforceLogin,
|
||||||
|
oidcFnMiddleware,
|
||||||
|
oidcRequestMiddleware,
|
||||||
|
} = oidcSpa
|
||||||
|
.withExpectedDecodedIdTokenShape({
|
||||||
|
decodedIdTokenSchema: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
picture: z.string().optional(),
|
||||||
|
email: z.string().email().optional(),
|
||||||
|
preferred_username: z.string().optional(),
|
||||||
|
realm_access: z.object({ roles: z.array(z.string()) }).optional(),
|
||||||
|
}),
|
||||||
|
decodedIdToken_mock: {
|
||||||
|
name: 'John Doe',
|
||||||
|
preferred_username: 'john.doe',
|
||||||
|
realm_access: {
|
||||||
|
roles: ['realm-admin'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.withAccessTokenValidation({
|
||||||
|
type: 'RFC 9068: JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens',
|
||||||
|
expectedAudience: (/* { paramsOfBootstrap, process } */) => 'account',
|
||||||
|
accessTokenClaimsSchema: z.object({
|
||||||
|
sub: z.string(),
|
||||||
|
realm_access: z.object({ roles: z.array(z.string()) }).optional(),
|
||||||
|
}),
|
||||||
|
accessTokenClaims_mock: {
|
||||||
|
sub: 'u123',
|
||||||
|
realm_access: {
|
||||||
|
roles: ['realm-admin'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// See: https://docs.oidc-spa.dev/features/auto-login#tanstack-start
|
||||||
|
// .withAutoLogin()
|
||||||
|
.createUtils()
|
||||||
|
|
||||||
|
// Can be call anywhere, even in the body of a React component.
|
||||||
|
// All subsequent calls will be safely ignored.
|
||||||
|
bootstrapOidc(({ process }) =>
|
||||||
|
process.env.OIDC_USE_MOCK === 'true'
|
||||||
|
? {
|
||||||
|
implementation: 'mock',
|
||||||
|
isUserInitiallyLoggedIn: true,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
implementation: 'real',
|
||||||
|
issuerUri: process.env.OIDC_ISSUER_URI,
|
||||||
|
clientId: process.env.OIDC_CLIENT_ID,
|
||||||
|
debugLogs: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const fetchWithAuth: typeof fetch = async (input, init) => {
|
||||||
|
const oidc = await getOidc()
|
||||||
|
|
||||||
|
if (oidc.isUserLoggedIn) {
|
||||||
|
const accessToken = await oidc.getAccessToken()
|
||||||
|
const headers = new Headers(init?.headers)
|
||||||
|
headers.set('Authorization', `Bearer ${accessToken}`)
|
||||||
|
;(init ??= {}).headers = headers
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(input, init)
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
// import { createOidcBackend } from 'oidc-spa/backend'
|
||||||
|
|
||||||
|
// const zDecodedAccessToken = z.object({
|
||||||
|
// sub: z.string(),
|
||||||
|
// aud: z.union([z.string(), z.array(z.string())]),
|
||||||
|
// realm_access: z.object({
|
||||||
|
// roles: z.array(z.string()),
|
||||||
|
// }),
|
||||||
|
// // Some other info you might want to read from the accessToken, example:
|
||||||
|
// // preferred_username: z.string()
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export type DecodedAccessToken = z.infer<typeof zDecodedAccessToken>
|
||||||
|
|
||||||
|
// export async function createDecodeAccessToken(params: {
|
||||||
|
// issuerUri: string
|
||||||
|
// audience: string
|
||||||
|
// }) {
|
||||||
|
// const { issuerUri, audience } = params
|
||||||
|
|
||||||
|
// const { verifyAndDecodeAccessToken } = await createOidcBackend({
|
||||||
|
// issuerUri,
|
||||||
|
// decodedAccessTokenSchema: zDecodedAccessToken,
|
||||||
|
// })
|
||||||
|
|
||||||
|
// async function decodeAccessToken(params: {
|
||||||
|
// authorizationHeaderValue: string | undefined
|
||||||
|
// requiredRole?: string
|
||||||
|
// }): Promise<DecodedAccessToken> {
|
||||||
|
// const { authorizationHeaderValue, requiredRole } = params
|
||||||
|
|
||||||
|
// if (authorizationHeaderValue === undefined) {
|
||||||
|
// throw Error('401 Unauthorized')
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const result = await verifyAndDecodeAccessToken({
|
||||||
|
// accessToken: authorizationHeaderValue.replace(/^Bearer /, ''),
|
||||||
|
// })
|
||||||
|
|
||||||
|
// if (!result) throw new Error(`The result is invalid`)
|
||||||
|
|
||||||
|
// if (!result.isValid) {
|
||||||
|
// switch (result.errorCase) {
|
||||||
|
// case 'does not respect schema':
|
||||||
|
// throw new Error(
|
||||||
|
// `The access token does not respect the schema ${result.errorMessage}`,
|
||||||
|
// )
|
||||||
|
// case 'invalid signature':
|
||||||
|
// case 'expired':
|
||||||
|
// throw new Error('401')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const { decodedAccessToken } = result
|
||||||
|
|
||||||
|
// if (
|
||||||
|
// requiredRole !== undefined &&
|
||||||
|
// !decodedAccessToken.realm_access.roles.includes(requiredRole)
|
||||||
|
// ) {
|
||||||
|
// throw new Error('401')
|
||||||
|
// }
|
||||||
|
|
||||||
|
// {
|
||||||
|
// const { aud } = decodedAccessToken
|
||||||
|
|
||||||
|
// const aud_array = typeof aud === 'string' ? [aud] : aud
|
||||||
|
|
||||||
|
// if (!aud_array.includes(audience)) {
|
||||||
|
// throw new Error('401')
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return decodedAccessToken
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return { decodeAccessToken }
|
||||||
|
// }
|
||||||
@@ -3,43 +3,70 @@ import { twMerge } from 'tailwind-merge'
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useQueryClient } from '@tanstack/react-query'
|
import { useQueryClient } from '@tanstack/react-query'
|
||||||
import type { ClassValue } from 'clsx'
|
import type { ClassValue } from 'clsx'
|
||||||
import { env } from '@/env'
|
import { getBackendURI } from '@/routes'
|
||||||
|
import { createCallbackClient, createClient } from '@connectrpc/connect'
|
||||||
|
import { MessageBusService } from '@/gen/messagebus/v1/messagebus_pb'
|
||||||
|
import { useTransport } from '@connectrpc/connect-query'
|
||||||
|
|
||||||
export function cn(...inputs: Array<ClassValue>) {
|
export function cn(...inputs: Array<ClassValue>) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useReactQuerySubscription = () => {
|
export const useReactQuerySubscription = () => {
|
||||||
|
const transport = useTransport();
|
||||||
|
const mbsclient = createCallbackClient(MessageBusService, transport)
|
||||||
const queryClient = useQueryClient()
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let interval: any
|
let closer = mbsclient.subscribeToConnectInvalidationRequests({}, (res) => {
|
||||||
const websocket = new WebSocket(env.VITE_BACKEND_URI + '/ws')
|
console.log(res);
|
||||||
websocket.onopen = () => {
|
}, (err) => {
|
||||||
console.log('[Message Bus] opened.')
|
console.log("Closed Subscription. Error is: ", err)
|
||||||
interval = setInterval(() => {
|
});
|
||||||
websocket.send('PING')
|
|
||||||
}, 10000)
|
|
||||||
}
|
|
||||||
|
|
||||||
websocket.onmessage = (event) => {
|
return closer
|
||||||
const data = JSON.parse(event.data)
|
}, [])
|
||||||
const queryKey = [...data.entity, data.id].filter(Boolean)
|
|
||||||
queryClient.invalidateQueries({ queryKey })
|
|
||||||
console.log('[Message Bus] invalidating: ' + event.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
websocket.onclose = (e) => {
|
|
||||||
console.warn('[Message Bus] closed!', e)
|
|
||||||
interval.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
websocket.onerror = () => {
|
|
||||||
console.error('[Message Bus] errored!')
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
// let callback: (code?: number, reason?: string) => void | undefined
|
||||||
websocket.close()
|
|
||||||
}
|
// useEffect(() => {
|
||||||
}, [queryClient])
|
// getBackendURI().then((uri) => {
|
||||||
|
// let interval: any
|
||||||
|
// const websocket = new WebSocket(uri + '/ws')
|
||||||
|
|
||||||
|
// websocket.onopen = () => {
|
||||||
|
// console.log('[Message Bus] opened.')
|
||||||
|
// interval = setInterval(() => {
|
||||||
|
// websocket.send('PING')
|
||||||
|
// }, 10000)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// websocket.onmessage = (event) => {
|
||||||
|
// const data = JSON.parse(event.data)
|
||||||
|
// const queryKey = [...data.entity, data.id].filter(Boolean)
|
||||||
|
// console.log('[Message Bus] invalidating: ' + event.data)
|
||||||
|
// queryClient.invalidateQueries({ queryKey })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// websocket.onclose = (e) => {
|
||||||
|
// console.warn('[Message Bus] closed!', e)
|
||||||
|
// clearInterval(interval)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// websocket.onerror = () => {
|
||||||
|
// console.error('[Message Bus] errored!')
|
||||||
|
// }
|
||||||
|
|
||||||
|
// callback = websocket.close
|
||||||
|
// })
|
||||||
|
|
||||||
|
// return () => {
|
||||||
|
// if (callback != null) {
|
||||||
|
// return callback()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }, [queryClient])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,19 +9,22 @@
|
|||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
|
import { Route as ImageDotpngRouteImport } from './routes/image[.]png'
|
||||||
import { Route as SidebarRouteImport } from './routes/_sidebar'
|
import { Route as SidebarRouteImport } from './routes/_sidebar'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
import { Route as DemoTanstackQueryRouteImport } from './routes/demo.tanstack-query'
|
import { Route as DemoTanstackQueryRouteImport } from './routes/demo.tanstack-query'
|
||||||
import { Route as SidebarScannerRouteImport } from './routes/_sidebar/scanner'
|
import { Route as SidebarScannerRouteImport } from './routes/_sidebar/scanner'
|
||||||
|
import { Route as SidebarProjectsRouteImport } from './routes/_sidebar/projects'
|
||||||
import { Route as SidebarNotificationsRouteImport } from './routes/_sidebar/notifications'
|
import { Route as SidebarNotificationsRouteImport } from './routes/_sidebar/notifications'
|
||||||
import { Route as SidebarKanbanRouteImport } from './routes/_sidebar/kanban'
|
import { Route as SidebarKanbanRouteImport } from './routes/_sidebar/kanban'
|
||||||
import { Route as SidebarKalendarRouteImport } from './routes/_sidebar/kalendar'
|
|
||||||
import { Route as SidebarDocumentsRouteImport } from './routes/_sidebar/documents'
|
import { Route as SidebarDocumentsRouteImport } from './routes/_sidebar/documents'
|
||||||
import { Route as SidebarDashboardRouteImport } from './routes/_sidebar/dashboard'
|
import { Route as SidebarDashboardRouteImport } from './routes/_sidebar/dashboard'
|
||||||
import { Route as SidebarChangelogRouteImport } from './routes/_sidebar/changelog'
|
import { Route as SidebarChangelogRouteImport } from './routes/_sidebar/changelog'
|
||||||
|
import { Route as SidebarCalendarRouteImport } from './routes/_sidebar/calendar'
|
||||||
import { Route as SidebarAboutRouteImport } from './routes/_sidebar/about'
|
import { Route as SidebarAboutRouteImport } from './routes/_sidebar/about'
|
||||||
import { Route as SidebarStorageIndexRouteImport } from './routes/_sidebar/storage/index'
|
import { Route as SidebarStorageIndexRouteImport } from './routes/_sidebar/storage/index'
|
||||||
import { Route as SidebarProjectsIndexRouteImport } from './routes/_sidebar/projects/index'
|
import { Route as SidebarProjectsIndexRouteImport } from './routes/_sidebar/projects/index'
|
||||||
|
import { Route as SidebarNotificationsIndexRouteImport } from './routes/_sidebar/notifications/index'
|
||||||
import { Route as SidebarCrmIndexRouteImport } from './routes/_sidebar/crm/index'
|
import { Route as SidebarCrmIndexRouteImport } from './routes/_sidebar/crm/index'
|
||||||
import { Route as SidebarStorageRacksRouteImport } from './routes/_sidebar/storage/racks'
|
import { Route as SidebarStorageRacksRouteImport } from './routes/_sidebar/storage/racks'
|
||||||
import { Route as SidebarStorageMaterialRouteImport } from './routes/_sidebar/storage/material'
|
import { Route as SidebarStorageMaterialRouteImport } from './routes/_sidebar/storage/material'
|
||||||
@@ -42,6 +45,11 @@ import { Route as SidebarProjectsViewIdFinanceRouteImport } from './routes/_side
|
|||||||
import { Route as SidebarProjectsViewIdEquipmentRouteImport } from './routes/_sidebar/projects/view/$id/equipment'
|
import { Route as SidebarProjectsViewIdEquipmentRouteImport } from './routes/_sidebar/projects/view/$id/equipment'
|
||||||
import { Route as SidebarProjectsViewIdAuditRouteImport } from './routes/_sidebar/projects/view/$id/audit'
|
import { Route as SidebarProjectsViewIdAuditRouteImport } from './routes/_sidebar/projects/view/$id/audit'
|
||||||
|
|
||||||
|
const ImageDotpngRoute = ImageDotpngRouteImport.update({
|
||||||
|
id: '/image.png',
|
||||||
|
path: '/image.png',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const SidebarRoute = SidebarRouteImport.update({
|
const SidebarRoute = SidebarRouteImport.update({
|
||||||
id: '/_sidebar',
|
id: '/_sidebar',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
@@ -61,6 +69,11 @@ const SidebarScannerRoute = SidebarScannerRouteImport.update({
|
|||||||
path: '/scanner',
|
path: '/scanner',
|
||||||
getParentRoute: () => SidebarRoute,
|
getParentRoute: () => SidebarRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const SidebarProjectsRoute = SidebarProjectsRouteImport.update({
|
||||||
|
id: '/projects',
|
||||||
|
path: '/projects',
|
||||||
|
getParentRoute: () => SidebarRoute,
|
||||||
|
} as any)
|
||||||
const SidebarNotificationsRoute = SidebarNotificationsRouteImport.update({
|
const SidebarNotificationsRoute = SidebarNotificationsRouteImport.update({
|
||||||
id: '/notifications',
|
id: '/notifications',
|
||||||
path: '/notifications',
|
path: '/notifications',
|
||||||
@@ -71,11 +84,6 @@ const SidebarKanbanRoute = SidebarKanbanRouteImport.update({
|
|||||||
path: '/kanban',
|
path: '/kanban',
|
||||||
getParentRoute: () => SidebarRoute,
|
getParentRoute: () => SidebarRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const SidebarKalendarRoute = SidebarKalendarRouteImport.update({
|
|
||||||
id: '/kalendar',
|
|
||||||
path: '/kalendar',
|
|
||||||
getParentRoute: () => SidebarRoute,
|
|
||||||
} as any)
|
|
||||||
const SidebarDocumentsRoute = SidebarDocumentsRouteImport.update({
|
const SidebarDocumentsRoute = SidebarDocumentsRouteImport.update({
|
||||||
id: '/documents',
|
id: '/documents',
|
||||||
path: '/documents',
|
path: '/documents',
|
||||||
@@ -91,6 +99,11 @@ const SidebarChangelogRoute = SidebarChangelogRouteImport.update({
|
|||||||
path: '/changelog',
|
path: '/changelog',
|
||||||
getParentRoute: () => SidebarRoute,
|
getParentRoute: () => SidebarRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const SidebarCalendarRoute = SidebarCalendarRouteImport.update({
|
||||||
|
id: '/calendar',
|
||||||
|
path: '/calendar',
|
||||||
|
getParentRoute: () => SidebarRoute,
|
||||||
|
} as any)
|
||||||
const SidebarAboutRoute = SidebarAboutRouteImport.update({
|
const SidebarAboutRoute = SidebarAboutRouteImport.update({
|
||||||
id: '/about',
|
id: '/about',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
@@ -102,10 +115,16 @@ const SidebarStorageIndexRoute = SidebarStorageIndexRouteImport.update({
|
|||||||
getParentRoute: () => SidebarRoute,
|
getParentRoute: () => SidebarRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const SidebarProjectsIndexRoute = SidebarProjectsIndexRouteImport.update({
|
const SidebarProjectsIndexRoute = SidebarProjectsIndexRouteImport.update({
|
||||||
id: '/projects/',
|
id: '/',
|
||||||
path: '/projects/',
|
path: '/',
|
||||||
getParentRoute: () => SidebarRoute,
|
getParentRoute: () => SidebarProjectsRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const SidebarNotificationsIndexRoute =
|
||||||
|
SidebarNotificationsIndexRouteImport.update({
|
||||||
|
id: '/',
|
||||||
|
path: '/',
|
||||||
|
getParentRoute: () => SidebarNotificationsRoute,
|
||||||
|
} as any)
|
||||||
const SidebarCrmIndexRoute = SidebarCrmIndexRouteImport.update({
|
const SidebarCrmIndexRoute = SidebarCrmIndexRouteImport.update({
|
||||||
id: '/crm/',
|
id: '/crm/',
|
||||||
path: '/crm/',
|
path: '/crm/',
|
||||||
@@ -122,19 +141,19 @@ const SidebarStorageMaterialRoute = SidebarStorageMaterialRouteImport.update({
|
|||||||
getParentRoute: () => SidebarRoute,
|
getParentRoute: () => SidebarRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const SidebarProjectsCurrentRoute = SidebarProjectsCurrentRouteImport.update({
|
const SidebarProjectsCurrentRoute = SidebarProjectsCurrentRouteImport.update({
|
||||||
id: '/projects/current',
|
id: '/current',
|
||||||
path: '/projects/current',
|
path: '/current',
|
||||||
getParentRoute: () => SidebarRoute,
|
getParentRoute: () => SidebarProjectsRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const SidebarProjectsCreateRoute = SidebarProjectsCreateRouteImport.update({
|
const SidebarProjectsCreateRoute = SidebarProjectsCreateRouteImport.update({
|
||||||
id: '/projects/create',
|
id: '/create',
|
||||||
path: '/projects/create',
|
path: '/create',
|
||||||
getParentRoute: () => SidebarRoute,
|
getParentRoute: () => SidebarProjectsRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const SidebarProjectsArchiveRoute = SidebarProjectsArchiveRouteImport.update({
|
const SidebarProjectsArchiveRoute = SidebarProjectsArchiveRouteImport.update({
|
||||||
id: '/projects/archive',
|
id: '/archive',
|
||||||
path: '/projects/archive',
|
path: '/archive',
|
||||||
getParentRoute: () => SidebarRoute,
|
getParentRoute: () => SidebarProjectsRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const SidebarCrmLeieferantenRoute = SidebarCrmLeieferantenRouteImport.update({
|
const SidebarCrmLeieferantenRoute = SidebarCrmLeieferantenRouteImport.update({
|
||||||
id: '/crm/leieferanten',
|
id: '/crm/leieferanten',
|
||||||
@@ -163,9 +182,9 @@ const SidebarCrmAnsprechpartnerRoute =
|
|||||||
getParentRoute: () => SidebarRoute,
|
getParentRoute: () => SidebarRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const SidebarProjectsViewIdRoute = SidebarProjectsViewIdRouteImport.update({
|
const SidebarProjectsViewIdRoute = SidebarProjectsViewIdRouteImport.update({
|
||||||
id: '/projects/view/$id',
|
id: '/view/$id',
|
||||||
path: '/projects/view/$id',
|
path: '/view/$id',
|
||||||
getParentRoute: () => SidebarRoute,
|
getParentRoute: () => SidebarProjectsRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const SidebarProjectsViewIdIndexRoute =
|
const SidebarProjectsViewIdIndexRoute =
|
||||||
SidebarProjectsViewIdIndexRouteImport.update({
|
SidebarProjectsViewIdIndexRouteImport.update({
|
||||||
@@ -212,13 +231,15 @@ const SidebarProjectsViewIdAuditRoute =
|
|||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/image.png': typeof ImageDotpngRoute
|
||||||
'/about': typeof SidebarAboutRoute
|
'/about': typeof SidebarAboutRoute
|
||||||
|
'/calendar': typeof SidebarCalendarRoute
|
||||||
'/changelog': typeof SidebarChangelogRoute
|
'/changelog': typeof SidebarChangelogRoute
|
||||||
'/dashboard': typeof SidebarDashboardRoute
|
'/dashboard': typeof SidebarDashboardRoute
|
||||||
'/documents': typeof SidebarDocumentsRoute
|
'/documents': typeof SidebarDocumentsRoute
|
||||||
'/kalendar': typeof SidebarKalendarRoute
|
|
||||||
'/kanban': typeof SidebarKanbanRoute
|
'/kanban': typeof SidebarKanbanRoute
|
||||||
'/notifications': typeof SidebarNotificationsRoute
|
'/notifications': typeof SidebarNotificationsRouteWithChildren
|
||||||
|
'/projects': typeof SidebarProjectsRouteWithChildren
|
||||||
'/scanner': typeof SidebarScannerRoute
|
'/scanner': typeof SidebarScannerRoute
|
||||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||||
'/crm/ansprechpartner': typeof SidebarCrmAnsprechpartnerRoute
|
'/crm/ansprechpartner': typeof SidebarCrmAnsprechpartnerRoute
|
||||||
@@ -232,7 +253,8 @@ export interface FileRoutesByFullPath {
|
|||||||
'/storage/material': typeof SidebarStorageMaterialRoute
|
'/storage/material': typeof SidebarStorageMaterialRoute
|
||||||
'/storage/racks': typeof SidebarStorageRacksRoute
|
'/storage/racks': typeof SidebarStorageRacksRoute
|
||||||
'/crm': typeof SidebarCrmIndexRoute
|
'/crm': typeof SidebarCrmIndexRoute
|
||||||
'/projects': typeof SidebarProjectsIndexRoute
|
'/notifications/': typeof SidebarNotificationsIndexRoute
|
||||||
|
'/projects/': typeof SidebarProjectsIndexRoute
|
||||||
'/storage': typeof SidebarStorageIndexRoute
|
'/storage': typeof SidebarStorageIndexRoute
|
||||||
'/projects/view/$id': typeof SidebarProjectsViewIdRouteWithChildren
|
'/projects/view/$id': typeof SidebarProjectsViewIdRouteWithChildren
|
||||||
'/projects/view/$id/audit': typeof SidebarProjectsViewIdAuditRoute
|
'/projects/view/$id/audit': typeof SidebarProjectsViewIdAuditRoute
|
||||||
@@ -245,13 +267,13 @@ export interface FileRoutesByFullPath {
|
|||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/image.png': typeof ImageDotpngRoute
|
||||||
'/about': typeof SidebarAboutRoute
|
'/about': typeof SidebarAboutRoute
|
||||||
|
'/calendar': typeof SidebarCalendarRoute
|
||||||
'/changelog': typeof SidebarChangelogRoute
|
'/changelog': typeof SidebarChangelogRoute
|
||||||
'/dashboard': typeof SidebarDashboardRoute
|
'/dashboard': typeof SidebarDashboardRoute
|
||||||
'/documents': typeof SidebarDocumentsRoute
|
'/documents': typeof SidebarDocumentsRoute
|
||||||
'/kalendar': typeof SidebarKalendarRoute
|
|
||||||
'/kanban': typeof SidebarKanbanRoute
|
'/kanban': typeof SidebarKanbanRoute
|
||||||
'/notifications': typeof SidebarNotificationsRoute
|
|
||||||
'/scanner': typeof SidebarScannerRoute
|
'/scanner': typeof SidebarScannerRoute
|
||||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||||
'/crm/ansprechpartner': typeof SidebarCrmAnsprechpartnerRoute
|
'/crm/ansprechpartner': typeof SidebarCrmAnsprechpartnerRoute
|
||||||
@@ -265,6 +287,7 @@ export interface FileRoutesByTo {
|
|||||||
'/storage/material': typeof SidebarStorageMaterialRoute
|
'/storage/material': typeof SidebarStorageMaterialRoute
|
||||||
'/storage/racks': typeof SidebarStorageRacksRoute
|
'/storage/racks': typeof SidebarStorageRacksRoute
|
||||||
'/crm': typeof SidebarCrmIndexRoute
|
'/crm': typeof SidebarCrmIndexRoute
|
||||||
|
'/notifications': typeof SidebarNotificationsIndexRoute
|
||||||
'/projects': typeof SidebarProjectsIndexRoute
|
'/projects': typeof SidebarProjectsIndexRoute
|
||||||
'/storage': typeof SidebarStorageIndexRoute
|
'/storage': typeof SidebarStorageIndexRoute
|
||||||
'/projects/view/$id/audit': typeof SidebarProjectsViewIdAuditRoute
|
'/projects/view/$id/audit': typeof SidebarProjectsViewIdAuditRoute
|
||||||
@@ -279,13 +302,15 @@ export interface FileRoutesById {
|
|||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/_sidebar': typeof SidebarRouteWithChildren
|
'/_sidebar': typeof SidebarRouteWithChildren
|
||||||
|
'/image.png': typeof ImageDotpngRoute
|
||||||
'/_sidebar/about': typeof SidebarAboutRoute
|
'/_sidebar/about': typeof SidebarAboutRoute
|
||||||
|
'/_sidebar/calendar': typeof SidebarCalendarRoute
|
||||||
'/_sidebar/changelog': typeof SidebarChangelogRoute
|
'/_sidebar/changelog': typeof SidebarChangelogRoute
|
||||||
'/_sidebar/dashboard': typeof SidebarDashboardRoute
|
'/_sidebar/dashboard': typeof SidebarDashboardRoute
|
||||||
'/_sidebar/documents': typeof SidebarDocumentsRoute
|
'/_sidebar/documents': typeof SidebarDocumentsRoute
|
||||||
'/_sidebar/kalendar': typeof SidebarKalendarRoute
|
|
||||||
'/_sidebar/kanban': typeof SidebarKanbanRoute
|
'/_sidebar/kanban': typeof SidebarKanbanRoute
|
||||||
'/_sidebar/notifications': typeof SidebarNotificationsRoute
|
'/_sidebar/notifications': typeof SidebarNotificationsRouteWithChildren
|
||||||
|
'/_sidebar/projects': typeof SidebarProjectsRouteWithChildren
|
||||||
'/_sidebar/scanner': typeof SidebarScannerRoute
|
'/_sidebar/scanner': typeof SidebarScannerRoute
|
||||||
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
'/demo/tanstack-query': typeof DemoTanstackQueryRoute
|
||||||
'/_sidebar/crm/ansprechpartner': typeof SidebarCrmAnsprechpartnerRoute
|
'/_sidebar/crm/ansprechpartner': typeof SidebarCrmAnsprechpartnerRoute
|
||||||
@@ -299,6 +324,7 @@ export interface FileRoutesById {
|
|||||||
'/_sidebar/storage/material': typeof SidebarStorageMaterialRoute
|
'/_sidebar/storage/material': typeof SidebarStorageMaterialRoute
|
||||||
'/_sidebar/storage/racks': typeof SidebarStorageRacksRoute
|
'/_sidebar/storage/racks': typeof SidebarStorageRacksRoute
|
||||||
'/_sidebar/crm/': typeof SidebarCrmIndexRoute
|
'/_sidebar/crm/': typeof SidebarCrmIndexRoute
|
||||||
|
'/_sidebar/notifications/': typeof SidebarNotificationsIndexRoute
|
||||||
'/_sidebar/projects/': typeof SidebarProjectsIndexRoute
|
'/_sidebar/projects/': typeof SidebarProjectsIndexRoute
|
||||||
'/_sidebar/storage/': typeof SidebarStorageIndexRoute
|
'/_sidebar/storage/': typeof SidebarStorageIndexRoute
|
||||||
'/_sidebar/projects/view/$id': typeof SidebarProjectsViewIdRouteWithChildren
|
'/_sidebar/projects/view/$id': typeof SidebarProjectsViewIdRouteWithChildren
|
||||||
@@ -314,13 +340,15 @@ export interface FileRouteTypes {
|
|||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths:
|
fullPaths:
|
||||||
| '/'
|
| '/'
|
||||||
|
| '/image.png'
|
||||||
| '/about'
|
| '/about'
|
||||||
|
| '/calendar'
|
||||||
| '/changelog'
|
| '/changelog'
|
||||||
| '/dashboard'
|
| '/dashboard'
|
||||||
| '/documents'
|
| '/documents'
|
||||||
| '/kalendar'
|
|
||||||
| '/kanban'
|
| '/kanban'
|
||||||
| '/notifications'
|
| '/notifications'
|
||||||
|
| '/projects'
|
||||||
| '/scanner'
|
| '/scanner'
|
||||||
| '/demo/tanstack-query'
|
| '/demo/tanstack-query'
|
||||||
| '/crm/ansprechpartner'
|
| '/crm/ansprechpartner'
|
||||||
@@ -334,7 +362,8 @@ export interface FileRouteTypes {
|
|||||||
| '/storage/material'
|
| '/storage/material'
|
||||||
| '/storage/racks'
|
| '/storage/racks'
|
||||||
| '/crm'
|
| '/crm'
|
||||||
| '/projects'
|
| '/notifications/'
|
||||||
|
| '/projects/'
|
||||||
| '/storage'
|
| '/storage'
|
||||||
| '/projects/view/$id'
|
| '/projects/view/$id'
|
||||||
| '/projects/view/$id/audit'
|
| '/projects/view/$id/audit'
|
||||||
@@ -347,13 +376,13 @@ export interface FileRouteTypes {
|
|||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
|
| '/image.png'
|
||||||
| '/about'
|
| '/about'
|
||||||
|
| '/calendar'
|
||||||
| '/changelog'
|
| '/changelog'
|
||||||
| '/dashboard'
|
| '/dashboard'
|
||||||
| '/documents'
|
| '/documents'
|
||||||
| '/kalendar'
|
|
||||||
| '/kanban'
|
| '/kanban'
|
||||||
| '/notifications'
|
|
||||||
| '/scanner'
|
| '/scanner'
|
||||||
| '/demo/tanstack-query'
|
| '/demo/tanstack-query'
|
||||||
| '/crm/ansprechpartner'
|
| '/crm/ansprechpartner'
|
||||||
@@ -367,6 +396,7 @@ export interface FileRouteTypes {
|
|||||||
| '/storage/material'
|
| '/storage/material'
|
||||||
| '/storage/racks'
|
| '/storage/racks'
|
||||||
| '/crm'
|
| '/crm'
|
||||||
|
| '/notifications'
|
||||||
| '/projects'
|
| '/projects'
|
||||||
| '/storage'
|
| '/storage'
|
||||||
| '/projects/view/$id/audit'
|
| '/projects/view/$id/audit'
|
||||||
@@ -380,13 +410,15 @@ export interface FileRouteTypes {
|
|||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
| '/_sidebar'
|
| '/_sidebar'
|
||||||
|
| '/image.png'
|
||||||
| '/_sidebar/about'
|
| '/_sidebar/about'
|
||||||
|
| '/_sidebar/calendar'
|
||||||
| '/_sidebar/changelog'
|
| '/_sidebar/changelog'
|
||||||
| '/_sidebar/dashboard'
|
| '/_sidebar/dashboard'
|
||||||
| '/_sidebar/documents'
|
| '/_sidebar/documents'
|
||||||
| '/_sidebar/kalendar'
|
|
||||||
| '/_sidebar/kanban'
|
| '/_sidebar/kanban'
|
||||||
| '/_sidebar/notifications'
|
| '/_sidebar/notifications'
|
||||||
|
| '/_sidebar/projects'
|
||||||
| '/_sidebar/scanner'
|
| '/_sidebar/scanner'
|
||||||
| '/demo/tanstack-query'
|
| '/demo/tanstack-query'
|
||||||
| '/_sidebar/crm/ansprechpartner'
|
| '/_sidebar/crm/ansprechpartner'
|
||||||
@@ -400,6 +432,7 @@ export interface FileRouteTypes {
|
|||||||
| '/_sidebar/storage/material'
|
| '/_sidebar/storage/material'
|
||||||
| '/_sidebar/storage/racks'
|
| '/_sidebar/storage/racks'
|
||||||
| '/_sidebar/crm/'
|
| '/_sidebar/crm/'
|
||||||
|
| '/_sidebar/notifications/'
|
||||||
| '/_sidebar/projects/'
|
| '/_sidebar/projects/'
|
||||||
| '/_sidebar/storage/'
|
| '/_sidebar/storage/'
|
||||||
| '/_sidebar/projects/view/$id'
|
| '/_sidebar/projects/view/$id'
|
||||||
@@ -415,11 +448,19 @@ export interface FileRouteTypes {
|
|||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
SidebarRoute: typeof SidebarRouteWithChildren
|
SidebarRoute: typeof SidebarRouteWithChildren
|
||||||
|
ImageDotpngRoute: typeof ImageDotpngRoute
|
||||||
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
|
DemoTanstackQueryRoute: typeof DemoTanstackQueryRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
|
'/image.png': {
|
||||||
|
id: '/image.png'
|
||||||
|
path: '/image.png'
|
||||||
|
fullPath: '/image.png'
|
||||||
|
preLoaderRoute: typeof ImageDotpngRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/_sidebar': {
|
'/_sidebar': {
|
||||||
id: '/_sidebar'
|
id: '/_sidebar'
|
||||||
path: ''
|
path: ''
|
||||||
@@ -448,6 +489,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof SidebarScannerRouteImport
|
preLoaderRoute: typeof SidebarScannerRouteImport
|
||||||
parentRoute: typeof SidebarRoute
|
parentRoute: typeof SidebarRoute
|
||||||
}
|
}
|
||||||
|
'/_sidebar/projects': {
|
||||||
|
id: '/_sidebar/projects'
|
||||||
|
path: '/projects'
|
||||||
|
fullPath: '/projects'
|
||||||
|
preLoaderRoute: typeof SidebarProjectsRouteImport
|
||||||
|
parentRoute: typeof SidebarRoute
|
||||||
|
}
|
||||||
'/_sidebar/notifications': {
|
'/_sidebar/notifications': {
|
||||||
id: '/_sidebar/notifications'
|
id: '/_sidebar/notifications'
|
||||||
path: '/notifications'
|
path: '/notifications'
|
||||||
@@ -462,13 +510,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof SidebarKanbanRouteImport
|
preLoaderRoute: typeof SidebarKanbanRouteImport
|
||||||
parentRoute: typeof SidebarRoute
|
parentRoute: typeof SidebarRoute
|
||||||
}
|
}
|
||||||
'/_sidebar/kalendar': {
|
|
||||||
id: '/_sidebar/kalendar'
|
|
||||||
path: '/kalendar'
|
|
||||||
fullPath: '/kalendar'
|
|
||||||
preLoaderRoute: typeof SidebarKalendarRouteImport
|
|
||||||
parentRoute: typeof SidebarRoute
|
|
||||||
}
|
|
||||||
'/_sidebar/documents': {
|
'/_sidebar/documents': {
|
||||||
id: '/_sidebar/documents'
|
id: '/_sidebar/documents'
|
||||||
path: '/documents'
|
path: '/documents'
|
||||||
@@ -490,6 +531,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof SidebarChangelogRouteImport
|
preLoaderRoute: typeof SidebarChangelogRouteImport
|
||||||
parentRoute: typeof SidebarRoute
|
parentRoute: typeof SidebarRoute
|
||||||
}
|
}
|
||||||
|
'/_sidebar/calendar': {
|
||||||
|
id: '/_sidebar/calendar'
|
||||||
|
path: '/calendar'
|
||||||
|
fullPath: '/calendar'
|
||||||
|
preLoaderRoute: typeof SidebarCalendarRouteImport
|
||||||
|
parentRoute: typeof SidebarRoute
|
||||||
|
}
|
||||||
'/_sidebar/about': {
|
'/_sidebar/about': {
|
||||||
id: '/_sidebar/about'
|
id: '/_sidebar/about'
|
||||||
path: '/about'
|
path: '/about'
|
||||||
@@ -506,10 +554,17 @@ declare module '@tanstack/react-router' {
|
|||||||
}
|
}
|
||||||
'/_sidebar/projects/': {
|
'/_sidebar/projects/': {
|
||||||
id: '/_sidebar/projects/'
|
id: '/_sidebar/projects/'
|
||||||
path: '/projects'
|
path: '/'
|
||||||
fullPath: '/projects'
|
fullPath: '/projects/'
|
||||||
preLoaderRoute: typeof SidebarProjectsIndexRouteImport
|
preLoaderRoute: typeof SidebarProjectsIndexRouteImport
|
||||||
parentRoute: typeof SidebarRoute
|
parentRoute: typeof SidebarProjectsRoute
|
||||||
|
}
|
||||||
|
'/_sidebar/notifications/': {
|
||||||
|
id: '/_sidebar/notifications/'
|
||||||
|
path: '/'
|
||||||
|
fullPath: '/notifications/'
|
||||||
|
preLoaderRoute: typeof SidebarNotificationsIndexRouteImport
|
||||||
|
parentRoute: typeof SidebarNotificationsRoute
|
||||||
}
|
}
|
||||||
'/_sidebar/crm/': {
|
'/_sidebar/crm/': {
|
||||||
id: '/_sidebar/crm/'
|
id: '/_sidebar/crm/'
|
||||||
@@ -534,24 +589,24 @@ declare module '@tanstack/react-router' {
|
|||||||
}
|
}
|
||||||
'/_sidebar/projects/current': {
|
'/_sidebar/projects/current': {
|
||||||
id: '/_sidebar/projects/current'
|
id: '/_sidebar/projects/current'
|
||||||
path: '/projects/current'
|
path: '/current'
|
||||||
fullPath: '/projects/current'
|
fullPath: '/projects/current'
|
||||||
preLoaderRoute: typeof SidebarProjectsCurrentRouteImport
|
preLoaderRoute: typeof SidebarProjectsCurrentRouteImport
|
||||||
parentRoute: typeof SidebarRoute
|
parentRoute: typeof SidebarProjectsRoute
|
||||||
}
|
}
|
||||||
'/_sidebar/projects/create': {
|
'/_sidebar/projects/create': {
|
||||||
id: '/_sidebar/projects/create'
|
id: '/_sidebar/projects/create'
|
||||||
path: '/projects/create'
|
path: '/create'
|
||||||
fullPath: '/projects/create'
|
fullPath: '/projects/create'
|
||||||
preLoaderRoute: typeof SidebarProjectsCreateRouteImport
|
preLoaderRoute: typeof SidebarProjectsCreateRouteImport
|
||||||
parentRoute: typeof SidebarRoute
|
parentRoute: typeof SidebarProjectsRoute
|
||||||
}
|
}
|
||||||
'/_sidebar/projects/archive': {
|
'/_sidebar/projects/archive': {
|
||||||
id: '/_sidebar/projects/archive'
|
id: '/_sidebar/projects/archive'
|
||||||
path: '/projects/archive'
|
path: '/archive'
|
||||||
fullPath: '/projects/archive'
|
fullPath: '/projects/archive'
|
||||||
preLoaderRoute: typeof SidebarProjectsArchiveRouteImport
|
preLoaderRoute: typeof SidebarProjectsArchiveRouteImport
|
||||||
parentRoute: typeof SidebarRoute
|
parentRoute: typeof SidebarProjectsRoute
|
||||||
}
|
}
|
||||||
'/_sidebar/crm/leieferanten': {
|
'/_sidebar/crm/leieferanten': {
|
||||||
id: '/_sidebar/crm/leieferanten'
|
id: '/_sidebar/crm/leieferanten'
|
||||||
@@ -590,10 +645,10 @@ declare module '@tanstack/react-router' {
|
|||||||
}
|
}
|
||||||
'/_sidebar/projects/view/$id': {
|
'/_sidebar/projects/view/$id': {
|
||||||
id: '/_sidebar/projects/view/$id'
|
id: '/_sidebar/projects/view/$id'
|
||||||
path: '/projects/view/$id'
|
path: '/view/$id'
|
||||||
fullPath: '/projects/view/$id'
|
fullPath: '/projects/view/$id'
|
||||||
preLoaderRoute: typeof SidebarProjectsViewIdRouteImport
|
preLoaderRoute: typeof SidebarProjectsViewIdRouteImport
|
||||||
parentRoute: typeof SidebarRoute
|
parentRoute: typeof SidebarProjectsRoute
|
||||||
}
|
}
|
||||||
'/_sidebar/projects/view/$id/': {
|
'/_sidebar/projects/view/$id/': {
|
||||||
id: '/_sidebar/projects/view/$id/'
|
id: '/_sidebar/projects/view/$id/'
|
||||||
@@ -647,6 +702,17 @@ declare module '@tanstack/react-router' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SidebarNotificationsRouteChildren {
|
||||||
|
SidebarNotificationsIndexRoute: typeof SidebarNotificationsIndexRoute
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarNotificationsRouteChildren: SidebarNotificationsRouteChildren = {
|
||||||
|
SidebarNotificationsIndexRoute: SidebarNotificationsIndexRoute,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarNotificationsRouteWithChildren =
|
||||||
|
SidebarNotificationsRoute._addFileChildren(SidebarNotificationsRouteChildren)
|
||||||
|
|
||||||
interface SidebarProjectsViewIdRouteChildren {
|
interface SidebarProjectsViewIdRouteChildren {
|
||||||
SidebarProjectsViewIdAuditRoute: typeof SidebarProjectsViewIdAuditRoute
|
SidebarProjectsViewIdAuditRoute: typeof SidebarProjectsViewIdAuditRoute
|
||||||
SidebarProjectsViewIdEquipmentRoute: typeof SidebarProjectsViewIdEquipmentRoute
|
SidebarProjectsViewIdEquipmentRoute: typeof SidebarProjectsViewIdEquipmentRoute
|
||||||
@@ -672,54 +738,66 @@ const SidebarProjectsViewIdRouteWithChildren =
|
|||||||
SidebarProjectsViewIdRouteChildren,
|
SidebarProjectsViewIdRouteChildren,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface SidebarProjectsRouteChildren {
|
||||||
|
SidebarProjectsArchiveRoute: typeof SidebarProjectsArchiveRoute
|
||||||
|
SidebarProjectsCreateRoute: typeof SidebarProjectsCreateRoute
|
||||||
|
SidebarProjectsCurrentRoute: typeof SidebarProjectsCurrentRoute
|
||||||
|
SidebarProjectsIndexRoute: typeof SidebarProjectsIndexRoute
|
||||||
|
SidebarProjectsViewIdRoute: typeof SidebarProjectsViewIdRouteWithChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarProjectsRouteChildren: SidebarProjectsRouteChildren = {
|
||||||
|
SidebarProjectsArchiveRoute: SidebarProjectsArchiveRoute,
|
||||||
|
SidebarProjectsCreateRoute: SidebarProjectsCreateRoute,
|
||||||
|
SidebarProjectsCurrentRoute: SidebarProjectsCurrentRoute,
|
||||||
|
SidebarProjectsIndexRoute: SidebarProjectsIndexRoute,
|
||||||
|
SidebarProjectsViewIdRoute: SidebarProjectsViewIdRouteWithChildren,
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarProjectsRouteWithChildren = SidebarProjectsRoute._addFileChildren(
|
||||||
|
SidebarProjectsRouteChildren,
|
||||||
|
)
|
||||||
|
|
||||||
interface SidebarRouteChildren {
|
interface SidebarRouteChildren {
|
||||||
SidebarAboutRoute: typeof SidebarAboutRoute
|
SidebarAboutRoute: typeof SidebarAboutRoute
|
||||||
|
SidebarCalendarRoute: typeof SidebarCalendarRoute
|
||||||
SidebarChangelogRoute: typeof SidebarChangelogRoute
|
SidebarChangelogRoute: typeof SidebarChangelogRoute
|
||||||
SidebarDashboardRoute: typeof SidebarDashboardRoute
|
SidebarDashboardRoute: typeof SidebarDashboardRoute
|
||||||
SidebarDocumentsRoute: typeof SidebarDocumentsRoute
|
SidebarDocumentsRoute: typeof SidebarDocumentsRoute
|
||||||
SidebarKalendarRoute: typeof SidebarKalendarRoute
|
|
||||||
SidebarKanbanRoute: typeof SidebarKanbanRoute
|
SidebarKanbanRoute: typeof SidebarKanbanRoute
|
||||||
SidebarNotificationsRoute: typeof SidebarNotificationsRoute
|
SidebarNotificationsRoute: typeof SidebarNotificationsRouteWithChildren
|
||||||
|
SidebarProjectsRoute: typeof SidebarProjectsRouteWithChildren
|
||||||
SidebarScannerRoute: typeof SidebarScannerRoute
|
SidebarScannerRoute: typeof SidebarScannerRoute
|
||||||
SidebarCrmAnsprechpartnerRoute: typeof SidebarCrmAnsprechpartnerRoute
|
SidebarCrmAnsprechpartnerRoute: typeof SidebarCrmAnsprechpartnerRoute
|
||||||
SidebarCrmDienstleisterRoute: typeof SidebarCrmDienstleisterRoute
|
SidebarCrmDienstleisterRoute: typeof SidebarCrmDienstleisterRoute
|
||||||
SidebarCrmFirmenRoute: typeof SidebarCrmFirmenRoute
|
SidebarCrmFirmenRoute: typeof SidebarCrmFirmenRoute
|
||||||
SidebarCrmKostenstelleRoute: typeof SidebarCrmKostenstelleRoute
|
SidebarCrmKostenstelleRoute: typeof SidebarCrmKostenstelleRoute
|
||||||
SidebarCrmLeieferantenRoute: typeof SidebarCrmLeieferantenRoute
|
SidebarCrmLeieferantenRoute: typeof SidebarCrmLeieferantenRoute
|
||||||
SidebarProjectsArchiveRoute: typeof SidebarProjectsArchiveRoute
|
|
||||||
SidebarProjectsCreateRoute: typeof SidebarProjectsCreateRoute
|
|
||||||
SidebarProjectsCurrentRoute: typeof SidebarProjectsCurrentRoute
|
|
||||||
SidebarStorageMaterialRoute: typeof SidebarStorageMaterialRoute
|
SidebarStorageMaterialRoute: typeof SidebarStorageMaterialRoute
|
||||||
SidebarStorageRacksRoute: typeof SidebarStorageRacksRoute
|
SidebarStorageRacksRoute: typeof SidebarStorageRacksRoute
|
||||||
SidebarCrmIndexRoute: typeof SidebarCrmIndexRoute
|
SidebarCrmIndexRoute: typeof SidebarCrmIndexRoute
|
||||||
SidebarProjectsIndexRoute: typeof SidebarProjectsIndexRoute
|
|
||||||
SidebarStorageIndexRoute: typeof SidebarStorageIndexRoute
|
SidebarStorageIndexRoute: typeof SidebarStorageIndexRoute
|
||||||
SidebarProjectsViewIdRoute: typeof SidebarProjectsViewIdRouteWithChildren
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SidebarRouteChildren: SidebarRouteChildren = {
|
const SidebarRouteChildren: SidebarRouteChildren = {
|
||||||
SidebarAboutRoute: SidebarAboutRoute,
|
SidebarAboutRoute: SidebarAboutRoute,
|
||||||
|
SidebarCalendarRoute: SidebarCalendarRoute,
|
||||||
SidebarChangelogRoute: SidebarChangelogRoute,
|
SidebarChangelogRoute: SidebarChangelogRoute,
|
||||||
SidebarDashboardRoute: SidebarDashboardRoute,
|
SidebarDashboardRoute: SidebarDashboardRoute,
|
||||||
SidebarDocumentsRoute: SidebarDocumentsRoute,
|
SidebarDocumentsRoute: SidebarDocumentsRoute,
|
||||||
SidebarKalendarRoute: SidebarKalendarRoute,
|
|
||||||
SidebarKanbanRoute: SidebarKanbanRoute,
|
SidebarKanbanRoute: SidebarKanbanRoute,
|
||||||
SidebarNotificationsRoute: SidebarNotificationsRoute,
|
SidebarNotificationsRoute: SidebarNotificationsRouteWithChildren,
|
||||||
|
SidebarProjectsRoute: SidebarProjectsRouteWithChildren,
|
||||||
SidebarScannerRoute: SidebarScannerRoute,
|
SidebarScannerRoute: SidebarScannerRoute,
|
||||||
SidebarCrmAnsprechpartnerRoute: SidebarCrmAnsprechpartnerRoute,
|
SidebarCrmAnsprechpartnerRoute: SidebarCrmAnsprechpartnerRoute,
|
||||||
SidebarCrmDienstleisterRoute: SidebarCrmDienstleisterRoute,
|
SidebarCrmDienstleisterRoute: SidebarCrmDienstleisterRoute,
|
||||||
SidebarCrmFirmenRoute: SidebarCrmFirmenRoute,
|
SidebarCrmFirmenRoute: SidebarCrmFirmenRoute,
|
||||||
SidebarCrmKostenstelleRoute: SidebarCrmKostenstelleRoute,
|
SidebarCrmKostenstelleRoute: SidebarCrmKostenstelleRoute,
|
||||||
SidebarCrmLeieferantenRoute: SidebarCrmLeieferantenRoute,
|
SidebarCrmLeieferantenRoute: SidebarCrmLeieferantenRoute,
|
||||||
SidebarProjectsArchiveRoute: SidebarProjectsArchiveRoute,
|
|
||||||
SidebarProjectsCreateRoute: SidebarProjectsCreateRoute,
|
|
||||||
SidebarProjectsCurrentRoute: SidebarProjectsCurrentRoute,
|
|
||||||
SidebarStorageMaterialRoute: SidebarStorageMaterialRoute,
|
SidebarStorageMaterialRoute: SidebarStorageMaterialRoute,
|
||||||
SidebarStorageRacksRoute: SidebarStorageRacksRoute,
|
SidebarStorageRacksRoute: SidebarStorageRacksRoute,
|
||||||
SidebarCrmIndexRoute: SidebarCrmIndexRoute,
|
SidebarCrmIndexRoute: SidebarCrmIndexRoute,
|
||||||
SidebarProjectsIndexRoute: SidebarProjectsIndexRoute,
|
|
||||||
SidebarStorageIndexRoute: SidebarStorageIndexRoute,
|
SidebarStorageIndexRoute: SidebarStorageIndexRoute,
|
||||||
SidebarProjectsViewIdRoute: SidebarProjectsViewIdRouteWithChildren,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const SidebarRouteWithChildren =
|
const SidebarRouteWithChildren =
|
||||||
@@ -728,8 +806,19 @@ const SidebarRouteWithChildren =
|
|||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
SidebarRoute: SidebarRouteWithChildren,
|
SidebarRoute: SidebarRouteWithChildren,
|
||||||
|
ImageDotpngRoute: ImageDotpngRoute,
|
||||||
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
|
DemoTanstackQueryRoute: DemoTanstackQueryRoute,
|
||||||
}
|
}
|
||||||
export const routeTree = rootRouteImport
|
export const routeTree = rootRouteImport
|
||||||
._addFileChildren(rootRouteChildren)
|
._addFileChildren(rootRouteChildren)
|
||||||
._addFileTypes<FileRouteTypes>()
|
._addFileTypes<FileRouteTypes>()
|
||||||
|
|
||||||
|
import type { getRouter } from './router.tsx'
|
||||||
|
import type { startInstance } from './start.tsx'
|
||||||
|
declare module '@tanstack/react-start' {
|
||||||
|
interface Register {
|
||||||
|
ssr: true
|
||||||
|
router: Awaited<ReturnType<typeof getRouter>>
|
||||||
|
config: Awaited<ReturnType<typeof startInstance.getOptions>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,25 +1,65 @@
|
|||||||
import { createRouter as createTanstackRouter } from '@tanstack/react-router'
|
import { createRouter as createTanstackRouter } from '@tanstack/react-router'
|
||||||
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
|
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
|
||||||
import * as TanstackQuery from './integrations/tanstack-query/root-provider'
|
import * as TanstackQuery from './integrations/tanstack-query/root-provider'
|
||||||
|
import { createConnectTransport } from '@connectrpc/connect-web'
|
||||||
|
import { TransportProvider } from '@connectrpc/connect-query'
|
||||||
|
|
||||||
// Import the generated route tree
|
// Import the generated route tree
|
||||||
import { routeTree } from './routeTree.gen'
|
import { routeTree } from './routeTree.gen'
|
||||||
|
import { Interceptor } from '@connectrpc/connect'
|
||||||
|
import { getOidc } from './lib/oidc'
|
||||||
|
import { getRuntimeRPCURI } from './routes'
|
||||||
|
import NotFound from './components/404'
|
||||||
|
|
||||||
|
const logingIC: Interceptor = (next) => (req) => {
|
||||||
|
console.log(
|
||||||
|
`[ConnectRPC] ${new Date().toLocaleDateString()} - ${req.service}`,
|
||||||
|
)
|
||||||
|
return next(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
const auth: Interceptor = (next) => async (req) => {
|
||||||
|
const oidc = await getOidc()
|
||||||
|
if (oidc && oidc.getAccessToken)
|
||||||
|
req.header.set('Authentication', 'Bearer ' + (await oidc.getAccessToken()))
|
||||||
|
|
||||||
|
return await next(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
const transport = async () =>{
|
||||||
|
const baseUrl = await getRuntimeRPCURI();
|
||||||
|
console.log("BASE_URL", baseUrl)
|
||||||
|
return createConnectTransport({
|
||||||
|
baseUrl: baseUrl!,
|
||||||
|
interceptors: [logingIC, auth],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Create a new router instance
|
// Create a new router instance
|
||||||
export const createRouter = () => {
|
export const getRouter = async () => {
|
||||||
const rqContext = TanstackQuery.getContext()
|
const rqContext = TanstackQuery.getContext()
|
||||||
|
|
||||||
|
const finalTransport = await transport()
|
||||||
|
|
||||||
const router = createTanstackRouter({
|
const router = createTanstackRouter({
|
||||||
routeTree,
|
routeTree,
|
||||||
context: { ...rqContext },
|
context: { ...rqContext, transport: finalTransport },
|
||||||
defaultPreload: 'intent',
|
defaultPreload: 'intent',
|
||||||
Wrap: (props: { children: React.ReactNode }) => {
|
Wrap: (props: { children: React.ReactNode }) => {
|
||||||
return (
|
return (
|
||||||
<TanstackQuery.Provider {...rqContext}>
|
<TransportProvider transport={finalTransport}>
|
||||||
{props.children}
|
<TanstackQuery.Provider {...rqContext}>
|
||||||
</TanstackQuery.Provider>
|
{props.children}
|
||||||
|
</TanstackQuery.Provider>
|
||||||
|
</TransportProvider>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
defaultErrorComponent: ({ error }) => {
|
||||||
|
return <div>ERROR {error.message}</div>
|
||||||
|
},
|
||||||
|
defaultNotFoundComponent: () => {
|
||||||
|
return <NotFound />
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
setupRouterSsrQueryIntegration({ router, queryClient: rqContext.queryClient })
|
setupRouterSsrQueryIntegration({ router, queryClient: rqContext.queryClient })
|
||||||
@@ -30,6 +70,6 @@ export const createRouter = () => {
|
|||||||
// Register the router instance for type safety
|
// Register the router instance for type safety
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface Register {
|
interface Register {
|
||||||
router: ReturnType<typeof createRouter>
|
router: ReturnType<typeof getRouter>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
createRootRouteWithContext,
|
createRootRouteWithContext,
|
||||||
} from '@tanstack/react-router'
|
} from '@tanstack/react-router'
|
||||||
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
|
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
|
||||||
import { TanstackDevtools } from '@tanstack/react-devtools'
|
import { TanStackDevtools } from '@tanstack/react-devtools'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { enableDragDropTouch } from '@dragdroptouch/drag-drop-touch'
|
import { enableDragDropTouch } from '@dragdroptouch/drag-drop-touch'
|
||||||
|
|
||||||
@@ -16,9 +16,11 @@ import appCss from '../styles.css?url'
|
|||||||
|
|
||||||
import type { QueryClient } from '@tanstack/react-query'
|
import type { QueryClient } from '@tanstack/react-query'
|
||||||
import { useReactQuerySubscription } from '@/lib/utils'
|
import { useReactQuerySubscription } from '@/lib/utils'
|
||||||
|
import { Transport } from '@connectrpc/connect'
|
||||||
|
// import * as wc from 'webcrypto-liner';
|
||||||
interface MyRouterContext {
|
interface MyRouterContext {
|
||||||
queryClient: QueryClient
|
queryClient: QueryClient
|
||||||
|
transport: Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
||||||
@@ -34,6 +36,39 @@ export const Route = createRootRouteWithContext<MyRouterContext>()({
|
|||||||
{
|
{
|
||||||
title: 'Eventory',
|
title: 'Eventory',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'og:title',
|
||||||
|
content: 'Eventory',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'og:description',
|
||||||
|
content: 'A simple self-hostable rentalware.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'og:image',
|
||||||
|
content: '/image.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'og:url',
|
||||||
|
content: 'https://vt.kocoder.xyz',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'twitter:title',
|
||||||
|
content: 'Eventory',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'twitter:description',
|
||||||
|
content: 'A simple self-hostable rentalware.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'twitter:image',
|
||||||
|
content: '/image.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'twitter:url',
|
||||||
|
content: 'https://vt.kocoder.xyz',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
@@ -41,6 +76,28 @@ export const Route = createRootRouteWithContext<MyRouterContext>()({
|
|||||||
href: appCss,
|
href: appCss,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
scripts: [
|
||||||
|
// {
|
||||||
|
// src: 'https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.7.0/polyfill.min.js',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// src: 'https://cdnjs.cloudflare.com/ajax/libs/asmCrypto/2.3.2/asmcrypto.all.es8.min.js',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// src: 'https://cdn.rawgit.com/indutny/elliptic/master/dist/elliptic.min.js',
|
||||||
|
// },
|
||||||
|
// ...(import.meta.env.MODE != 'development'
|
||||||
|
// ? []
|
||||||
|
// : [
|
||||||
|
// {
|
||||||
|
// src: '/node_modules/webcrypto-liner/dist/webcrypto-liner.shim.js',
|
||||||
|
// },
|
||||||
|
// ]),
|
||||||
|
// {
|
||||||
|
// type: "module",
|
||||||
|
// src: "https://cdn.jsdelivr.net/npm/webcrypto-liner@1.4.3/+esm"
|
||||||
|
// }
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
|
|
||||||
shellComponent: RootDocument,
|
shellComponent: RootDocument,
|
||||||
@@ -59,7 +116,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{children}
|
{children}
|
||||||
<TanstackDevtools
|
<TanStackDevtools
|
||||||
config={{
|
config={{
|
||||||
position: 'middle-right',
|
position: 'middle-right',
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -7,16 +7,27 @@ import {
|
|||||||
SidebarTrigger,
|
SidebarTrigger,
|
||||||
} from '@/components/ui/sidebar'
|
} from '@/components/ui/sidebar'
|
||||||
import Breadcrumbs from '@/components/applicationBreadcrumbs'
|
import Breadcrumbs from '@/components/applicationBreadcrumbs'
|
||||||
|
import { enforceLogin } from '@/lib/oidc'
|
||||||
|
import { Suspense } from 'react'
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton'
|
||||||
|
import NotFound from '@/components/404'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar')({
|
export const Route = createFileRoute('/_sidebar')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: enforceLogin,
|
||||||
|
errorComponent: ({ error }) => {
|
||||||
|
return <div>ERROR {error.message}</div>
|
||||||
|
},
|
||||||
|
notFoundComponent: () => {
|
||||||
|
return <NotFound />
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
<SidebarInset className="sidebar-width block max-h-screen">
|
<SidebarInset className="sidebar-width max-h-screen flex">
|
||||||
<header className="flex h-16 shrink-0 items-center gap-2">
|
<header className="flex h-16 shrink-0 items-center gap-2">
|
||||||
<div className="flex items-center gap-2 px-4">
|
<div className="flex items-center gap-2 px-4">
|
||||||
<SidebarTrigger className="-ml-1" />
|
<SidebarTrigger className="-ml-1" />
|
||||||
@@ -27,8 +38,10 @@ function RouteComponent() {
|
|||||||
<Breadcrumbs />
|
<Breadcrumbs />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="p-4 pt-0 full-h w-full overflow-scroll">
|
<div className="p-4 pt-0 flex grow w-full overflow-scroll">
|
||||||
<Outlet />
|
<Suspense fallback={<Skeleton className="w-full h-full"></Skeleton>}>
|
||||||
|
<Outlet />
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
|
|||||||
15
src/routes/_sidebar/calendar.tsx
Normal file
15
src/routes/_sidebar/calendar.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import Calendar from '@/components/calendar'
|
||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_sidebar/calendar')({
|
||||||
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'Calendar',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <Calendar />
|
||||||
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/dashboard')({
|
export const Route = createFileRoute('/_sidebar/dashboard')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'Dashboard',
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Map, MapControls, MapMarker, MarkerContent, MarkerPopup, MarkerTooltip } from '@/components/ui/map'
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/documents')({
|
export const Route = createFileRoute('/_sidebar/documents')({
|
||||||
@@ -5,5 +6,35 @@ export const Route = createFileRoute('/_sidebar/documents')({
|
|||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <div>Hello "/_sidebar/documents"!</div>
|
return <div className='w-full h-full rounded-md overflow-hidden '>
|
||||||
|
<Map
|
||||||
|
center={{lat: 48.140134, lon:16.318053}}
|
||||||
|
zoom={17}
|
||||||
|
>
|
||||||
|
<MapControls position='bottom-right' showZoom
|
||||||
|
showCompass
|
||||||
|
showLocate
|
||||||
|
showFullscreen
|
||||||
|
/>
|
||||||
|
<MapMarker
|
||||||
|
key={"E5/27"}
|
||||||
|
longitude={16.318053}
|
||||||
|
latitude={48.140134}
|
||||||
|
draggable
|
||||||
|
>
|
||||||
|
<MarkerContent>
|
||||||
|
<div className="size-4 rounded-full bg-emerald-500 border-2 border-white shadow-lg" />
|
||||||
|
</MarkerContent>
|
||||||
|
<MarkerTooltip>Ellmingergasse 5/27</MarkerTooltip>
|
||||||
|
<MarkerPopup>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="font-medium text-foreground">Ellmingergasse 5/27</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{48.140134.toFixed(4)}, {16.318053.toFixed(4)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</MarkerPopup>
|
||||||
|
</MapMarker>
|
||||||
|
</Map>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,287 +1,159 @@
|
|||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { Kanban, KanbanBoard, KanbanColumn, KanbanColumnHandle, KanbanItem, KanbanOverlay } from '@/components/ui/kanban'
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { GripVertical } from 'lucide-react'
|
||||||
|
|
||||||
import KanbanColumn from '@/features/Kanban/components/KanbanColumn'
|
import { useState } from 'react'
|
||||||
import Kanban from '@/features/Kanban/components/Kanban'
|
|
||||||
import KanbanCard from '@/features/Kanban/components/KanbanCard'
|
|
||||||
import KanbanDropzone from '@/features/Kanban/components/KanbanDropzone'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/kanban')({
|
export const Route = createFileRoute('/_sidebar/kanban')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'Kanban',
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const kanbanboard = {
|
const kanbanboard = {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: 'Todo',
|
id: 0,
|
||||||
itemCount: 5,
|
name: 'Backlog'
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Doing',
|
id: 1,
|
||||||
itemCount: 6,
|
name: 'Todo'
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Done',
|
id: 2,
|
||||||
itemCount: 20,
|
name: 'Doing'
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Todo',
|
id: 3,
|
||||||
itemCount: 5,
|
name: 'Done'
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Doing',
|
id: 4,
|
||||||
itemCount: 6,
|
name: 'Backlog'
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Done',
|
id: 5,
|
||||||
itemCount: 20,
|
name: 'Todo'
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Todo',
|
id: 6,
|
||||||
itemCount: 5,
|
name: 'Doing'
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Doing',
|
id: 7,
|
||||||
itemCount: 6,
|
name: 'Done'
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Done',
|
|
||||||
itemCount: 20,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Todo',
|
|
||||||
itemCount: 5,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Doing',
|
|
||||||
itemCount: 6,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Done',
|
|
||||||
itemCount: 20,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
name: 'Hello World!',
|
|
||||||
path: 'https://google.com',
|
|
||||||
description: 'Das ist eine Testkarte',
|
|
||||||
labels: [
|
|
||||||
{
|
|
||||||
name: 'flow::7 Done',
|
|
||||||
className: 'bg-emerald-500',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Wichtig',
|
|
||||||
className: 'bg-red-500',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Task {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
priority: "low" | "medium" | "high";
|
||||||
|
assignee?: string;
|
||||||
|
dueDate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const COLUMN_TITLES: Record<string, string> = {
|
||||||
|
backlog: "Backlog",
|
||||||
|
inProgress: "In Progress",
|
||||||
|
done: "Done",
|
||||||
|
};
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
|
// const [columns, setColumns] = useState(kanbanboard.columns)
|
||||||
|
|
||||||
|
const [columns, setColumns] = useState<string[]>([
|
||||||
|
"backlog",
|
||||||
|
"inProgress",
|
||||||
|
"done",
|
||||||
|
"sprintbacl",
|
||||||
|
"toStart",
|
||||||
|
"notDone",
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full">
|
<div className="h-full w-full">
|
||||||
<Kanban>
|
<Kanban value={columns} onValueChange={setColumns} getItemValue={(c) => c.id}>
|
||||||
{kanbanboard.columns.map((val) => {
|
<KanbanBoard className="flex min-w-full overflow-scroll">
|
||||||
return (
|
{Object.entries(columns).map(([columnValue]) => (
|
||||||
<>
|
<KanbanColumn key={columnValue} value={columnValue} className='min-w-md'>
|
||||||
<KanbanColumn name={val.name} itemCount={val.itemCount}>
|
<div className="flex items-center justify-between">
|
||||||
{val.children.map((card) => {
|
<div className="flex items-center gap-2">
|
||||||
return <KanbanCard card={card} />
|
<span className="font-semibold text-sm">
|
||||||
})}
|
{COLUMN_TITLES[columnValue]}
|
||||||
</KanbanColumn>
|
</span>
|
||||||
<KanbanDropzone />
|
<Badge
|
||||||
</>
|
variant="secondary"
|
||||||
)
|
className="pointer-events-none rounded-sm"
|
||||||
})}
|
>
|
||||||
|
0
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<KanbanColumnHandle asChild>
|
||||||
|
<Button variant="ghost" size="icon">
|
||||||
|
<GripVertical className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</KanbanColumnHandle>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 p-0.5">
|
||||||
|
{/* List Tasks using Tanstack Query
|
||||||
|
|
||||||
|
{tasks.map((task) => (
|
||||||
|
<KanbanItem key={task.id} value={task.id} asHandle asChild>
|
||||||
|
<div className="rounded-md border bg-card p-3 shadow-xs">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<span className="line-clamp-1 font-medium text-sm">
|
||||||
|
{task.title}
|
||||||
|
</span>
|
||||||
|
<Badge
|
||||||
|
variant={
|
||||||
|
task.priority === "high"
|
||||||
|
? "destructive"
|
||||||
|
: task.priority === "medium"
|
||||||
|
? "default"
|
||||||
|
: "secondary"
|
||||||
|
}
|
||||||
|
className="pointer-events-none h-5 rounded-sm px-1.5 text-[11px] capitalize"
|
||||||
|
>
|
||||||
|
{task.priority}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between text-muted-foreground text-xs">
|
||||||
|
{task.assignee && (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<div className="size-2 rounded-full bg-primary/20" />
|
||||||
|
<span className="line-clamp-1">
|
||||||
|
{task.assignee}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{task.dueDate && (
|
||||||
|
<time className="text-[10px] tabular-nums">
|
||||||
|
{task.dueDate}
|
||||||
|
</time>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</KanbanItem>
|
||||||
|
))}
|
||||||
|
|
||||||
|
*/}
|
||||||
|
</div>
|
||||||
|
</KanbanColumn>
|
||||||
|
))}
|
||||||
|
</KanbanBoard>
|
||||||
|
<KanbanOverlay>
|
||||||
|
<div className="size-full rounded-md bg-primary/10" />
|
||||||
|
</KanbanOverlay>
|
||||||
</Kanban>
|
</Kanban>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,32 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
||||||
|
import {
|
||||||
|
ResizableHandle,
|
||||||
|
ResizablePanel,
|
||||||
|
ResizablePanelGroup,
|
||||||
|
} from "@/components/ui/resizable"
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/notifications')({
|
export const Route = createFileRoute('/_sidebar/notifications')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <div>Hello "/_sidebar/notifications"!</div>
|
return (
|
||||||
|
<ResizablePanelGroup
|
||||||
|
className="w-full max-w-full rounded-lg border"
|
||||||
|
>
|
||||||
|
<ResizablePanel defaultSize={"90px"}>
|
||||||
|
<div className="flex h-[200px] items-center justify-center p-6">
|
||||||
|
<span className="font-semibold">One</span>
|
||||||
|
</div>
|
||||||
|
</ResizablePanel>
|
||||||
|
<ResizableHandle />
|
||||||
|
<ResizablePanel defaultSize={"200px"}>
|
||||||
|
<ResizablePanel defaultSize={25}>
|
||||||
|
<div className="flex h-full items-center justify-center p-6">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
</ResizablePanel>
|
||||||
|
</ResizablePanel>
|
||||||
|
</ResizablePanelGroup>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/kalendar')({
|
export const Route = createFileRoute('/_sidebar/notifications/')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <div>Hello "/_sidebar/kalendar"!</div>
|
return <div>Hello "/_sidebar/notifications"!</div>
|
||||||
}
|
}
|
||||||
14
src/routes/_sidebar/projects.tsx
Normal file
14
src/routes/_sidebar/projects.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_sidebar/projects')({
|
||||||
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'Projekte',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <Outlet />
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import { ProjectService } from '@/gen/project/v1/project_pb'
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { createQueryOptions } from '@connectrpc/connect-query'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/projects/current')({
|
export const Route = createFileRoute('/_sidebar/projects/current')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
|
const {} = useQuery(createQueryOptions())
|
||||||
return <div>Hello "/_sidebar/projects/current"!</div>
|
return <div>Hello "/_sidebar/projects/current"!</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,40 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
import ProjectsTable from '@/features/Projects/components/table'
|
import ProjectsTable from '@/features/Projects/components/table'
|
||||||
|
import { createInfiniteQueryOptions } from '@connectrpc/connect-query'
|
||||||
|
import { listProjects } from '@/gen/project/v1/project-ProjectService_connectquery'
|
||||||
|
|
||||||
|
const fetchSize = 25
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/projects/')({
|
export const Route = createFileRoute('/_sidebar/projects/')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'Projekte',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loader: async ({ context }) => {
|
||||||
|
await context.queryClient.ensureInfiniteQueryData(
|
||||||
|
createInfiniteQueryOptions(
|
||||||
|
listProjects,
|
||||||
|
{
|
||||||
|
$typeName: 'project.v1.ListProjectsRequest',
|
||||||
|
page: 0,
|
||||||
|
perPage: fetchSize,
|
||||||
|
orberBy: 'id',
|
||||||
|
asc: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
transport: context.transport,
|
||||||
|
getNextPageParam: (_, p) => {
|
||||||
|
console.log(p.length)
|
||||||
|
return p.length * fetchSize
|
||||||
|
// return ()
|
||||||
|
},
|
||||||
|
pageParamKey: 'page',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
|
|||||||
@@ -3,33 +3,57 @@ import {
|
|||||||
Outlet,
|
Outlet,
|
||||||
createFileRoute,
|
createFileRoute,
|
||||||
useLocation,
|
useLocation,
|
||||||
useRouter,
|
|
||||||
} from '@tanstack/react-router'
|
} from '@tanstack/react-router'
|
||||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||||
import { getProjectQueryObject, useProject } from '@/features/Projects/queries'
|
import { getProject } from '@/gen/project/v1/project-ProjectService_connectquery'
|
||||||
|
import { createQueryOptions, useSuspenseQuery } from '@connectrpc/connect-query'
|
||||||
|
import { Suspense } from 'react'
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/projects/view/$id')({
|
export const Route = createFileRoute('/_sidebar/projects/view/$id')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: ({ params: { id } }) => {
|
||||||
|
return {
|
||||||
|
breadcrumb: '' + id,
|
||||||
|
}
|
||||||
|
},
|
||||||
loader: async ({ params: { id }, context }) => {
|
loader: async ({ params: { id }, context }) => {
|
||||||
// await context.queryClient.ensureQueryData(getProjectQueryObject(Number(id)))
|
await context.queryClient.ensureQueryData(
|
||||||
|
createQueryOptions(
|
||||||
|
getProject,
|
||||||
|
{
|
||||||
|
id: Number(id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
transport: context.transport,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function ProjectName({ id }: { id: string }) {
|
||||||
|
const { data } = useSuspenseQuery(getProject, {
|
||||||
|
id: Number(id),
|
||||||
|
})
|
||||||
|
|
||||||
|
return <div className="my-2 text-xl">{data?.name + ' #' + data?.id}</div>
|
||||||
|
}
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { id } = Route.useParams()
|
const { id } = Route.useParams()
|
||||||
const router = useLocation()
|
const router = useLocation()
|
||||||
const { data, isLoading } = useProject(Number(id))
|
|
||||||
|
|
||||||
const res = router.pathname.split('/')
|
const res = router.pathname.split('/')
|
||||||
const val = res[res.length - 1]
|
const val = res[res.length - 1]
|
||||||
|
|
||||||
if (isLoading) return <div>Loading...</div>
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="max-h-full grow overflow-auto flex flex-col">
|
||||||
<div className="my-2 text-xl">{data?.name + ' #' + data?.ID}</div>
|
<Suspense fallback={<Skeleton className="w-full h-[31px] my-2" />}>
|
||||||
<Tabs defaultValue={val} className="w-full">
|
<ProjectName id={id} />
|
||||||
<TabsList className="w-full overflow-auto">
|
</Suspense>
|
||||||
|
<Tabs value={val} className="w-full flex-none">
|
||||||
|
<TabsList className="w-full flex-wrap *:h-9 h-[inherit]">
|
||||||
<TabsTrigger value={id} asChild>
|
<TabsTrigger value={id} asChild>
|
||||||
<Link to="/projects/view/$id" params={{ id }}>
|
<Link to="/projects/view/$id" params={{ id }}>
|
||||||
General
|
General
|
||||||
@@ -67,7 +91,11 @@ function RouteComponent() {
|
|||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<Outlet />
|
<div className="w-full grow overflow-scroll">
|
||||||
</>
|
<Suspense fallback={<Skeleton className="w-full h-full mt-2" />}>
|
||||||
|
<Outlet />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,240 @@
|
|||||||
|
import { cn } from '@/lib/utils'
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import {
|
||||||
|
BotIcon,
|
||||||
|
CircleUserIcon,
|
||||||
|
ClipboardCheckIcon,
|
||||||
|
ProjectorIcon,
|
||||||
|
ReceiptEuroIcon,
|
||||||
|
SpeakerIcon,
|
||||||
|
} from 'lucide-react'
|
||||||
|
import { ReactNode } from 'react'
|
||||||
|
import { string } from 'zod'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/audit')({
|
export const Route = createFileRoute('/_sidebar/projects/view/$id/audit')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'Audit',
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
enum TimelineAction {
|
||||||
return <div>Hello "/_sidebar/projects/view/$id/audit"!</div>
|
General,
|
||||||
|
Equipment,
|
||||||
|
Personal,
|
||||||
|
Finance,
|
||||||
|
Todo,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TimelineActionType {
|
||||||
|
Good,
|
||||||
|
Bad,
|
||||||
|
Neutral,
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimelineEntry = {
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
date: Date
|
||||||
|
action: TimelineAction
|
||||||
|
actionType: TimelineActionType
|
||||||
|
}
|
||||||
|
|
||||||
|
const timelineEntries: Array<TimelineEntry> = [
|
||||||
|
{
|
||||||
|
title: 'Payed #5',
|
||||||
|
description: 'Client payed bill number 5.',
|
||||||
|
action: TimelineAction.Finance,
|
||||||
|
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Confirmed #1',
|
||||||
|
description: 'Client confirmed project 1.',
|
||||||
|
action: TimelineAction.General,
|
||||||
|
date: new Date(2025, 12, 4, 11),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'New Task',
|
||||||
|
description: 'Konstantin Hintermayer added task 1.',
|
||||||
|
action: TimelineAction.Todo,
|
||||||
|
date: new Date(),
|
||||||
|
actionType: TimelineActionType.Neutral,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Payed #5',
|
||||||
|
description: 'Client payed bill number 5.',
|
||||||
|
action: TimelineAction.Finance,
|
||||||
|
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Confirmed #1',
|
||||||
|
description: 'Client confirmed project 1.',
|
||||||
|
action: TimelineAction.General,
|
||||||
|
date: new Date(2025, 12, 4, 11),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'New Task',
|
||||||
|
description: 'Konstantin Hintermayer added task 1.',
|
||||||
|
action: TimelineAction.Todo,
|
||||||
|
date: new Date(),
|
||||||
|
actionType: TimelineActionType.Neutral,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Payed #5',
|
||||||
|
description: 'Client payed bill number 5.',
|
||||||
|
action: TimelineAction.Finance,
|
||||||
|
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Confirmed #1',
|
||||||
|
description: 'Client confirmed project 1.',
|
||||||
|
action: TimelineAction.General,
|
||||||
|
date: new Date(2025, 12, 4, 11),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'New Task',
|
||||||
|
description: 'Konstantin Hintermayer added task 1.',
|
||||||
|
action: TimelineAction.Todo,
|
||||||
|
date: new Date(),
|
||||||
|
actionType: TimelineActionType.Neutral,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Payed #5',
|
||||||
|
description: 'Client payed bill number 5.',
|
||||||
|
action: TimelineAction.Finance,
|
||||||
|
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Confirmed #1',
|
||||||
|
description: 'Client confirmed project 1.',
|
||||||
|
action: TimelineAction.General,
|
||||||
|
date: new Date(2025, 12, 4, 11),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'New Task',
|
||||||
|
description: 'Konstantin Hintermayer added task 1.',
|
||||||
|
action: TimelineAction.Todo,
|
||||||
|
date: new Date(),
|
||||||
|
actionType: TimelineActionType.Neutral,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Payed #5',
|
||||||
|
description: 'Client payed bill number 5.',
|
||||||
|
action: TimelineAction.Finance,
|
||||||
|
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Confirmed #1',
|
||||||
|
description: 'Client confirmed project 1.',
|
||||||
|
action: TimelineAction.General,
|
||||||
|
date: new Date(2025, 12, 4, 11),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'New Task',
|
||||||
|
description: 'Konstantin Hintermayer added task 1.',
|
||||||
|
action: TimelineAction.Todo,
|
||||||
|
date: new Date(),
|
||||||
|
actionType: TimelineActionType.Neutral,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Payed #5',
|
||||||
|
description: 'Client payed bill number 5.',
|
||||||
|
action: TimelineAction.Finance,
|
||||||
|
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Confirmed #1',
|
||||||
|
description: 'Client confirmed project 1.',
|
||||||
|
action: TimelineAction.General,
|
||||||
|
date: new Date(2025, 12, 4, 11),
|
||||||
|
actionType: TimelineActionType.Good,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'New Task',
|
||||||
|
description: 'Konstantin Hintermayer added task 1.',
|
||||||
|
action: TimelineAction.Todo,
|
||||||
|
date: new Date(),
|
||||||
|
actionType: TimelineActionType.Neutral,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function GetIconFromAction(ta: TimelineAction) {
|
||||||
|
switch (ta) {
|
||||||
|
case TimelineAction.Equipment:
|
||||||
|
return <SpeakerIcon className="h-6" />
|
||||||
|
case TimelineAction.Finance:
|
||||||
|
return <ReceiptEuroIcon className="h-6" />
|
||||||
|
case TimelineAction.General:
|
||||||
|
return <BotIcon className="h-6" />
|
||||||
|
case TimelineAction.Personal:
|
||||||
|
return <CircleUserIcon className="h-6" />
|
||||||
|
case TimelineAction.Todo:
|
||||||
|
return <ClipboardCheckIcon className="h-6" />
|
||||||
|
default:
|
||||||
|
return <p>Icon not found</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function GetColorFromActionType(at: TimelineActionType) {
|
||||||
|
switch (at) {
|
||||||
|
case TimelineActionType.Bad:
|
||||||
|
return 'bg-red-500'
|
||||||
|
case TimelineActionType.Good:
|
||||||
|
return 'bg-green-500'
|
||||||
|
case TimelineActionType.Neutral:
|
||||||
|
return 'bg-muted'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Timeline({ children }: { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
{children}
|
||||||
|
<div className="h-full absolute top-0 left-timeline-line w-[2px] bg-muted"></div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TimelineEntryComponent({ te }: { te: TimelineEntry }) {
|
||||||
|
return (
|
||||||
|
<div className="flex m-2 gap-3">
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
`h-8 aspect-square flex items-center justify-center rounded-full border-2 border-background z-10`,
|
||||||
|
GetColorFromActionType(te.actionType),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{GetIconFromAction(te.action)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{format(te.date, 'hh:mm dd.MM.yyyy')} {te.title}
|
||||||
|
<br />
|
||||||
|
{te.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return (
|
||||||
|
<Timeline>
|
||||||
|
{timelineEntries.map((te, i) => {
|
||||||
|
return <TimelineEntryComponent te={te} key={i}></TimelineEntryComponent>
|
||||||
|
})}
|
||||||
|
</Timeline>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,17 @@ import { createFileRoute } from '@tanstack/react-router'
|
|||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/equipment')({
|
export const Route = createFileRoute('/_sidebar/projects/view/$id/equipment')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'Equipment',
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <div>Hello "/_sidebar/projects/view/$id/equipment"!</div>
|
return (
|
||||||
|
<div className="bg-emerald-500 h-full">
|
||||||
|
Hello "/_sidebar/projects/view/$id/equipment"!
|
||||||
|
</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import { createFileRoute } from '@tanstack/react-router'
|
|||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/finance')({
|
export const Route = createFileRoute('/_sidebar/projects/view/$id/finance')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'Finanzen',
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
|
|||||||
@@ -27,9 +27,19 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select'
|
} from '@/components/ui/select'
|
||||||
|
import { useSuspenseQuery } from '@connectrpc/connect-query'
|
||||||
|
import { getProject } from '@/gen/project/v1/project-ProjectService_connectquery'
|
||||||
|
import { Breadcrumb } from '@/components/ui/breadcrumb'
|
||||||
|
import { Map, MapControls, MapMarker, MapRef, MarkerContent, MarkerPopup, MarkerTooltip } from '@/components/ui/map'
|
||||||
|
import { useRef } from 'react'
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/')({
|
export const Route = createFileRoute('/_sidebar/projects/view/$id/')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'General',
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const managers = [
|
const managers = [
|
||||||
@@ -100,6 +110,21 @@ const clients = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const locations = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'SZU',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Stadthalle',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Gasometer',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
title: z
|
title: z
|
||||||
.string()
|
.string()
|
||||||
@@ -113,11 +138,12 @@ const formSchema = z.object({
|
|||||||
type: z.string(),
|
type: z.string(),
|
||||||
status: z.string(),
|
status: z.string(),
|
||||||
client: z.number(),
|
client: z.number(),
|
||||||
|
location: z.number(),
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const { id } = Route.useParams()
|
const { id } = Route.useParams()
|
||||||
const { data } = useProject(Number(id))
|
const { data } = useSuspenseQuery(getProject, { id: Number(id) })
|
||||||
const { mutate } = useProjectEdit(Number(id))
|
const { mutate } = useProjectEdit(Number(id))
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
@@ -128,6 +154,7 @@ function RouteComponent() {
|
|||||||
type: 'Tour',
|
type: 'Tour',
|
||||||
status: 'Confirmed',
|
status: 'Confirmed',
|
||||||
client: 1,
|
client: 1,
|
||||||
|
location: 1,
|
||||||
},
|
},
|
||||||
validators: {
|
validators: {
|
||||||
onSubmit: formSchema,
|
onSubmit: formSchema,
|
||||||
@@ -138,91 +165,222 @@ function RouteComponent() {
|
|||||||
name: value.title!,
|
name: value.title!,
|
||||||
description: value.description!,
|
description: value.description!,
|
||||||
icon: '',
|
icon: '',
|
||||||
MandantID: data?.MandantID,
|
MandantID: 1,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className='w-full mt-2 grid grid-cols-6 lg:grid-cols-12 gap-6'>
|
||||||
<form
|
<div className="col-span-6 rounded-md bg-sidebar p-2 overflow-hidden">
|
||||||
id="project-esther-graf-general-form"
|
<form
|
||||||
className="w-2xl"
|
id="project-esther-graf-general-form"
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
form.handleSubmit()
|
form.handleSubmit()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FieldGroup>
|
|
||||||
<FieldGroup>
|
<FieldGroup>
|
||||||
<FieldLegend>Projekt Info</FieldLegend>
|
|
||||||
<FieldDescription>
|
|
||||||
Allgemeine Infos über das Projekt
|
|
||||||
</FieldDescription>
|
|
||||||
<FieldGroup>
|
<FieldGroup>
|
||||||
<form.Field
|
<FieldLegend>Projekt Info</FieldLegend>
|
||||||
name="title"
|
<FieldDescription>
|
||||||
children={(field) => {
|
Allgemeine Infos über das Projekt
|
||||||
const isInvalid =
|
</FieldDescription>
|
||||||
field.state.meta.isTouched && !field.state.meta.isValid
|
<FieldGroup>
|
||||||
return (
|
<form.Field
|
||||||
<Field data-invalid={isInvalid}>
|
name="title"
|
||||||
<FieldLabel htmlFor={field.name}>Projektname</FieldLabel>
|
children={(field) => {
|
||||||
<Input
|
const isInvalid =
|
||||||
id={field.name}
|
field.state.meta.isTouched && !field.state.meta.isValid
|
||||||
name={field.name}
|
return (
|
||||||
value={field.state.value}
|
<Field data-invalid={isInvalid}>
|
||||||
onBlur={field.handleBlur}
|
<FieldLabel htmlFor={field.name}>Projektname</FieldLabel>
|
||||||
onChange={(e) => field.handleChange(e.target.value)}
|
<Input
|
||||||
aria-invalid={isInvalid}
|
|
||||||
placeholder="Login button not working on mobile"
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
{isInvalid && (
|
|
||||||
<FieldError errors={field.state.meta.errors} />
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<form.Field
|
|
||||||
name="description"
|
|
||||||
children={(field) => {
|
|
||||||
const isInvalid =
|
|
||||||
field.state.meta.isTouched && !field.state.meta.isValid
|
|
||||||
return (
|
|
||||||
<Field data-invalid={isInvalid}>
|
|
||||||
<FieldLabel htmlFor={field.name}>Beschreibung</FieldLabel>
|
|
||||||
<InputGroup>
|
|
||||||
<InputGroupTextarea
|
|
||||||
id={field.name}
|
id={field.name}
|
||||||
name={field.name}
|
name={field.name}
|
||||||
value={field.state.value}
|
value={field.state.value}
|
||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
onChange={(e) => field.handleChange(e.target.value)}
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
placeholder="I'm having an issue with the login button on mobile."
|
|
||||||
rows={6}
|
|
||||||
className="min-h-24 resize-none"
|
|
||||||
aria-invalid={isInvalid}
|
aria-invalid={isInvalid}
|
||||||
|
placeholder="Login button not working on mobile"
|
||||||
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
<InputGroupAddon align="block-end">
|
{isInvalid && (
|
||||||
<InputGroupText className="tabular-nums">
|
<FieldError errors={field.state.meta.errors} />
|
||||||
{field.state.value.length}/100 characters
|
)}
|
||||||
</InputGroupText>
|
</Field>
|
||||||
</InputGroupAddon>
|
)
|
||||||
</InputGroup>
|
}}
|
||||||
<FieldDescription>
|
/>
|
||||||
Allgemeine Infos zum Projekt
|
<form.Field
|
||||||
</FieldDescription>
|
name="description"
|
||||||
{isInvalid && (
|
children={(field) => {
|
||||||
<FieldError errors={field.state.meta.errors} />
|
const isInvalid =
|
||||||
)}
|
field.state.meta.isTouched && !field.state.meta.isValid
|
||||||
</Field>
|
return (
|
||||||
)
|
<Field data-invalid={isInvalid}>
|
||||||
}}
|
<FieldLabel htmlFor={field.name}>Beschreibung</FieldLabel>
|
||||||
/>
|
<InputGroup>
|
||||||
|
<InputGroupTextarea
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
value={field.state.value}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
placeholder="I'm having an issue with the login button on mobile."
|
||||||
|
rows={6}
|
||||||
|
className="min-h-24 resize-none"
|
||||||
|
aria-invalid={isInvalid}
|
||||||
|
/>
|
||||||
|
<InputGroupAddon align="block-end">
|
||||||
|
<InputGroupText className="tabular-nums">
|
||||||
|
{field.state.value.length}/100 characters
|
||||||
|
</InputGroupText>
|
||||||
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
|
<FieldDescription>
|
||||||
|
Allgemeine Infos zum Projekt
|
||||||
|
</FieldDescription>
|
||||||
|
{isInvalid && (
|
||||||
|
<FieldError errors={field.state.meta.errors} />
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<form.Field
|
||||||
|
name="manager"
|
||||||
|
children={(field) => {
|
||||||
|
const isInvalid =
|
||||||
|
field.state.meta.isTouched && !field.state.meta.isValid
|
||||||
|
return (
|
||||||
|
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||||
|
<FieldContent>
|
||||||
|
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||||
|
Manager
|
||||||
|
</FieldLabel>
|
||||||
|
<FieldDescription>
|
||||||
|
Zuständiger für dieses Projekt
|
||||||
|
</FieldDescription>
|
||||||
|
{isInvalid && (
|
||||||
|
<FieldError errors={field.state.meta.errors} />
|
||||||
|
)}
|
||||||
|
</FieldContent>
|
||||||
|
<Select
|
||||||
|
name={field.name}
|
||||||
|
value={field.state.value.toString()}
|
||||||
|
onValueChange={(v) => field.handleChange(Number(v))}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
id="form-tanstack-select-language"
|
||||||
|
aria-invalid={isInvalid}
|
||||||
|
className="min-w-[120px]"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Select" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent position="item-aligned">
|
||||||
|
{managers.map((manager) => (
|
||||||
|
<SelectItem
|
||||||
|
key={manager.id}
|
||||||
|
value={manager.id.toString()}
|
||||||
|
>
|
||||||
|
{manager.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<form.Field
|
||||||
|
name="type"
|
||||||
|
children={(field) => {
|
||||||
|
const isInvalid =
|
||||||
|
field.state.meta.isTouched && !field.state.meta.isValid
|
||||||
|
return (
|
||||||
|
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||||
|
<FieldContent>
|
||||||
|
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||||
|
Projekttyp
|
||||||
|
</FieldLabel>
|
||||||
|
<FieldDescription>
|
||||||
|
Art des Projektes (Gibt voreinstellungen für Felder
|
||||||
|
wie zum Beispiel: Zahlungskonditionen vor)
|
||||||
|
</FieldDescription>
|
||||||
|
{isInvalid && (
|
||||||
|
<FieldError errors={field.state.meta.errors} />
|
||||||
|
)}
|
||||||
|
</FieldContent>
|
||||||
|
<Select
|
||||||
|
name={field.name}
|
||||||
|
value={field.state.value}
|
||||||
|
onValueChange={field.handleChange}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
id="form-tanstack-select-language"
|
||||||
|
aria-invalid={isInvalid}
|
||||||
|
className="min-w-[120px]"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Select" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent position="item-aligned">
|
||||||
|
{projectTypes.map((prj) => (
|
||||||
|
<SelectItem key={prj.name} value={prj.name}>
|
||||||
|
{prj.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<form.Field
|
||||||
|
name="status"
|
||||||
|
children={(field) => {
|
||||||
|
const isInvalid =
|
||||||
|
field.state.meta.isTouched && !field.state.meta.isValid
|
||||||
|
return (
|
||||||
|
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||||
|
<FieldContent>
|
||||||
|
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||||
|
Status
|
||||||
|
</FieldLabel>
|
||||||
|
{isInvalid && (
|
||||||
|
<FieldError errors={field.state.meta.errors} />
|
||||||
|
)}
|
||||||
|
</FieldContent>
|
||||||
|
<Select
|
||||||
|
name={field.name}
|
||||||
|
value={field.state.value}
|
||||||
|
onValueChange={field.handleChange}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
id="form-tanstack-select-language"
|
||||||
|
aria-invalid={isInvalid}
|
||||||
|
className="min-w-[120px]"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Select" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent position="item-aligned">
|
||||||
|
{statusse.map((stat) => (
|
||||||
|
<SelectItem key={stat.name} value={stat.name}>
|
||||||
|
{stat.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</Field>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FieldGroup>
|
||||||
|
</FieldGroup>
|
||||||
|
<FieldSeparator />
|
||||||
|
<FieldGroup>
|
||||||
<form.Field
|
<form.Field
|
||||||
name="manager"
|
name="client"
|
||||||
children={(field) => {
|
children={(field) => {
|
||||||
const isInvalid =
|
const isInvalid =
|
||||||
field.state.meta.isTouched && !field.state.meta.isValid
|
field.state.meta.isTouched && !field.state.meta.isValid
|
||||||
@@ -230,11 +388,8 @@ function RouteComponent() {
|
|||||||
<Field orientation="responsive" data-invalid={isInvalid}>
|
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||||
<FieldContent>
|
<FieldContent>
|
||||||
<FieldLabel htmlFor="form-tanstack-select-language">
|
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||||
Manager
|
Client
|
||||||
</FieldLabel>
|
</FieldLabel>
|
||||||
<FieldDescription>
|
|
||||||
Zuständiger für dieses Projekt
|
|
||||||
</FieldDescription>
|
|
||||||
{isInvalid && (
|
{isInvalid && (
|
||||||
<FieldError errors={field.state.meta.errors} />
|
<FieldError errors={field.state.meta.errors} />
|
||||||
)}
|
)}
|
||||||
@@ -252,12 +407,9 @@ function RouteComponent() {
|
|||||||
<SelectValue placeholder="Select" />
|
<SelectValue placeholder="Select" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent position="item-aligned">
|
<SelectContent position="item-aligned">
|
||||||
{managers.map((manager) => (
|
{clients.map((stat) => (
|
||||||
<SelectItem
|
<SelectItem key={stat.id} value={stat.id.toString()}>
|
||||||
key={manager.id}
|
{stat.name}
|
||||||
value={manager.id.toString()}
|
|
||||||
>
|
|
||||||
{manager.name}
|
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -266,8 +418,10 @@ function RouteComponent() {
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</FieldGroup>
|
||||||
|
<FieldGroup>
|
||||||
<form.Field
|
<form.Field
|
||||||
name="type"
|
name="location"
|
||||||
children={(field) => {
|
children={(field) => {
|
||||||
const isInvalid =
|
const isInvalid =
|
||||||
field.state.meta.isTouched && !field.state.meta.isValid
|
field.state.meta.isTouched && !field.state.meta.isValid
|
||||||
@@ -275,20 +429,16 @@ function RouteComponent() {
|
|||||||
<Field orientation="responsive" data-invalid={isInvalid}>
|
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||||
<FieldContent>
|
<FieldContent>
|
||||||
<FieldLabel htmlFor="form-tanstack-select-language">
|
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||||
Projekttyp
|
Location
|
||||||
</FieldLabel>
|
</FieldLabel>
|
||||||
<FieldDescription>
|
|
||||||
Art des Projektes (Gibt voreinstellungen für Felder
|
|
||||||
wie zum Beispiel: Zahlungskonditionen vor)
|
|
||||||
</FieldDescription>
|
|
||||||
{isInvalid && (
|
{isInvalid && (
|
||||||
<FieldError errors={field.state.meta.errors} />
|
<FieldError errors={field.state.meta.errors} />
|
||||||
)}
|
)}
|
||||||
</FieldContent>
|
</FieldContent>
|
||||||
<Select
|
<Select
|
||||||
name={field.name}
|
name={field.name}
|
||||||
value={field.state.value}
|
value={field.state.value.toString()}
|
||||||
onValueChange={field.handleChange}
|
onValueChange={(v) => field.handleChange(Number(v))}
|
||||||
>
|
>
|
||||||
<SelectTrigger
|
<SelectTrigger
|
||||||
id="form-tanstack-select-language"
|
id="form-tanstack-select-language"
|
||||||
@@ -298,47 +448,8 @@ function RouteComponent() {
|
|||||||
<SelectValue placeholder="Select" />
|
<SelectValue placeholder="Select" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent position="item-aligned">
|
<SelectContent position="item-aligned">
|
||||||
{projectTypes.map((prj) => (
|
{locations.map((stat) => (
|
||||||
<SelectItem key={prj.name} value={prj.name}>
|
<SelectItem key={stat.id} value={stat.id.toString()}>
|
||||||
{prj.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</Field>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<form.Field
|
|
||||||
name="status"
|
|
||||||
children={(field) => {
|
|
||||||
const isInvalid =
|
|
||||||
field.state.meta.isTouched && !field.state.meta.isValid
|
|
||||||
return (
|
|
||||||
<Field orientation="responsive" data-invalid={isInvalid}>
|
|
||||||
<FieldContent>
|
|
||||||
<FieldLabel htmlFor="form-tanstack-select-language">
|
|
||||||
Status
|
|
||||||
</FieldLabel>
|
|
||||||
{isInvalid && (
|
|
||||||
<FieldError errors={field.state.meta.errors} />
|
|
||||||
)}
|
|
||||||
</FieldContent>
|
|
||||||
<Select
|
|
||||||
name={field.name}
|
|
||||||
value={field.state.value}
|
|
||||||
onValueChange={field.handleChange}
|
|
||||||
>
|
|
||||||
<SelectTrigger
|
|
||||||
id="form-tanstack-select-language"
|
|
||||||
aria-invalid={isInvalid}
|
|
||||||
className="min-w-[120px]"
|
|
||||||
>
|
|
||||||
<SelectValue placeholder="Select" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent position="item-aligned">
|
|
||||||
{statusse.map((stat) => (
|
|
||||||
<SelectItem key={stat.name} value={stat.name}>
|
|
||||||
{stat.name}
|
{stat.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
@@ -350,58 +461,66 @@ function RouteComponent() {
|
|||||||
/>
|
/>
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
</FieldGroup>
|
</FieldGroup>
|
||||||
<FieldSeparator />
|
</form>
|
||||||
<FieldGroup>
|
<Field orientation="horizontal" className="mt-4">
|
||||||
<form.Field
|
<Button type="button" variant="outline" onClick={() => form.reset()}>
|
||||||
name="client"
|
Reset
|
||||||
children={(field) => {
|
</Button>
|
||||||
const isInvalid =
|
<Button type="submit" form="project-esther-graf-general-form">
|
||||||
field.state.meta.isTouched && !field.state.meta.isValid
|
Submit
|
||||||
return (
|
</Button>
|
||||||
<Field orientation="responsive" data-invalid={isInvalid}>
|
</Field>
|
||||||
<FieldContent>
|
</div>
|
||||||
<FieldLabel htmlFor="form-tanstack-select-language">
|
<div className="col-span-6 rounded-md overflow-hidden h-128 max-h-128 bg-sidebar flex flex-col p-3">
|
||||||
Client
|
<ShowEventLocation lat={48.202373} lon={16.332889} label='Wiener Stadthalle D'/>
|
||||||
</FieldLabel>
|
</div>
|
||||||
{isInvalid && (
|
</div>
|
||||||
<FieldError errors={field.state.meta.errors} />
|
|
||||||
)}
|
|
||||||
</FieldContent>
|
|
||||||
<Select
|
|
||||||
name={field.name}
|
|
||||||
value={field.state.value.toString()}
|
|
||||||
onValueChange={(v) => field.handleChange(Number(v))}
|
|
||||||
>
|
|
||||||
<SelectTrigger
|
|
||||||
id="form-tanstack-select-language"
|
|
||||||
aria-invalid={isInvalid}
|
|
||||||
className="min-w-[120px]"
|
|
||||||
>
|
|
||||||
<SelectValue placeholder="Select" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent position="item-aligned">
|
|
||||||
{clients.map((stat) => (
|
|
||||||
<SelectItem key={stat.id} value={stat.id.toString()}>
|
|
||||||
{stat.name}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</Field>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FieldGroup>
|
|
||||||
</FieldGroup>
|
|
||||||
</form>
|
|
||||||
<Field orientation="horizontal" className="mt-4">
|
|
||||||
<Button type="button" variant="outline" onClick={() => form.reset()}>
|
|
||||||
Reset
|
|
||||||
</Button>
|
|
||||||
<Button type="submit" form="project-esther-graf-general-form">
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</Field>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ShowEventLocation({lat, lon, label}: {lat: number, lon: number, label: string}) {
|
||||||
|
const map = useRef<MapRef>(null);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h2 className='text-xl mb-1'>Location</h2>
|
||||||
|
<div className="flex-grow rounded-md overflow-hidden">
|
||||||
|
<Map
|
||||||
|
center={{
|
||||||
|
lat,
|
||||||
|
lon,
|
||||||
|
}}
|
||||||
|
zoom={15}
|
||||||
|
ref={map}
|
||||||
|
>
|
||||||
|
<MapControls
|
||||||
|
position='bottom-right'
|
||||||
|
showFullscreen
|
||||||
|
showLocate
|
||||||
|
showCompass
|
||||||
|
showZoom
|
||||||
|
/>
|
||||||
|
<MapMarker
|
||||||
|
latitude={lat}
|
||||||
|
longitude={lon}
|
||||||
|
>
|
||||||
|
<MarkerContent>
|
||||||
|
<div className="size-4 rounded-full bg-emerald-500 border-2 border-white shadow-lg" />
|
||||||
|
</MarkerContent>
|
||||||
|
<MarkerTooltip>{label}</MarkerTooltip>
|
||||||
|
<MarkerPopup>
|
||||||
|
<div className="space-y-1">
|
||||||
|
<p className="font-medium text-foreground">{label}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{lat.toFixed(4)}, {lon.toFixed(4)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</MarkerPopup>
|
||||||
|
</MapMarker>
|
||||||
|
</Map>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -2,6 +2,11 @@ import { createFileRoute } from '@tanstack/react-router'
|
|||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/personal')({
|
export const Route = createFileRoute('/_sidebar/projects/view/$id/personal')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'Personal',
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
|
|||||||
@@ -1,9 +1,34 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import TimelineTable from '@/features/Projects/components/timelinetable'
|
||||||
|
import { Scheduler } from "@bitnoi.se/react-scheduler"
|
||||||
|
|
||||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/timeline')({
|
export const Route = createFileRoute('/_sidebar/projects/view/$id/timeline')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'Timeline',
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <div>Hello "/_sidebar/projects/view/$id/timeline"!</div>
|
return <div className='h-full max-h-full relative'>
|
||||||
|
<Scheduler data={[{
|
||||||
|
id: '1',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
startDate: new Date(2026, 0, 11, 16, 0, 0),
|
||||||
|
endDate: new Date(2026, 0, 11, 18, 0, 0),
|
||||||
|
title: "TEST",
|
||||||
|
occupancy: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
label: {
|
||||||
|
title: "Hello World",
|
||||||
|
icon: "NZLL",
|
||||||
|
subtitle: "TEST"
|
||||||
|
}
|
||||||
|
}]}/>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,43 @@
|
|||||||
|
import TodoTable from '@/features/Projects/components/todotable'
|
||||||
|
import { getTodo, listTodos } from '@/gen/todo/v1/todo-TodoService_connectquery'
|
||||||
|
import {
|
||||||
|
createInfiniteQueryOptions,
|
||||||
|
createQueryOptions,
|
||||||
|
} from '@connectrpc/connect-query'
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
|
||||||
|
const fetchSize = 25
|
||||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/todos')({
|
export const Route = createFileRoute('/_sidebar/projects/view/$id/todos')({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
return {
|
||||||
|
breadcrumb: 'Todos',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loader: async ({ params: { id }, context }) => {
|
||||||
|
await context.queryClient.ensureInfiniteQueryData(
|
||||||
|
createInfiniteQueryOptions(
|
||||||
|
listTodos,
|
||||||
|
{
|
||||||
|
page: 0,
|
||||||
|
perPage: fetchSize,
|
||||||
|
orberBy: 'id',
|
||||||
|
asc: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
transport: context.transport,
|
||||||
|
getNextPageParam: (_, p) => {
|
||||||
|
console.log(p.length)
|
||||||
|
return p.length * fetchSize
|
||||||
|
// return ()
|
||||||
|
},
|
||||||
|
pageParamKey: 'page',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <div>Hello "/_sidebar/projects/view/$id/todos"!</div>
|
return <TodoTable />
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/routes/image[.]png.ts
Normal file
12
src/routes/image[.]png.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { generateSeoImageForContent } from '@/lib/ogimage'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/image.png')({
|
||||||
|
server: {
|
||||||
|
handlers: {
|
||||||
|
GET: async ({ request }) => {
|
||||||
|
return await generateSeoImageForContent()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -1,19 +1,23 @@
|
|||||||
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
import { LoginForm } from '@/components/login-form'
|
import { LoginForm } from '@/components/login-form'
|
||||||
import { useProfile } from '@/features/Auth/queries'
|
import { createServerFn } from '@tanstack/react-start'
|
||||||
|
|
||||||
|
export const getBackendURI = createServerFn({ method: 'GET' }).handler(() => {
|
||||||
|
return process.env.BACKEND_URI
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getRuntimeRPCURI = createServerFn({ method: 'GET' }).handler(
|
||||||
|
() => {
|
||||||
|
// return process.env.RPC_URI
|
||||||
|
return process.env.RPC_URI
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
export const Route = createFileRoute('/')({
|
export const Route = createFileRoute('/')({
|
||||||
component: App,
|
component: App,
|
||||||
})
|
})
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const { data, isLoading } = useProfile()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
if (data?.ID != null) {
|
|
||||||
navigate({ href: '/dashboard' })
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-muted flex min-h-svh flex-col items-center justify-center p-6 md:p-10">
|
<div className="bg-muted flex min-h-svh flex-col items-center justify-center p-6 md:p-10">
|
||||||
<div className="w-full max-w-sm md:max-w-4xl">
|
<div className="w-full max-w-sm md:max-w-4xl">
|
||||||
|
|||||||
5
src/start.tsx
Normal file
5
src/start.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { createStart } from '@tanstack/react-start'
|
||||||
|
|
||||||
|
export const startInstance = createStart(() => ({
|
||||||
|
defaultSsr: false,
|
||||||
|
}))
|
||||||
@@ -135,6 +135,16 @@ code {
|
|||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
.maplibregl-popup-content {
|
||||||
|
@apply bg-transparent! shadow-none! p-0! rounded-none!;
|
||||||
|
@apply bg-transparent! shadow-none! p-0! rounded-none!;
|
||||||
|
@apply bg-transparent! shadow-none! p-0! rounded-none!;
|
||||||
|
}
|
||||||
|
.maplibregl-popup-tip {
|
||||||
|
@apply hidden!;
|
||||||
|
@apply hidden!;
|
||||||
|
@apply hidden!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-width {
|
.sidebar-width {
|
||||||
@@ -155,9 +165,13 @@ code {
|
|||||||
|
|
||||||
.full-h {
|
.full-h {
|
||||||
/* max-height: calc(100% - var(--spacing) * 16); */
|
/* max-height: calc(100% - var(--spacing) * 16); */
|
||||||
height: calc(100% - var(--spacing) * 16);
|
max-height: calc(100% - var(--spacing) * 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-height {
|
.table-height {
|
||||||
height: calc(100% - calc(var(--spacing) * 30));
|
height: calc(100% - calc(var(--spacing) * 30));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.left-timeline-line {
|
||||||
|
left: calc((var(--spacing) * 6) - 1px);
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
exit status 1exit status 1exit status 1exit status 1exit status 1
|
exit status 1exit status 1
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"include": ["**/*.ts", "**/*.tsx", "eslint.config.js", "prettier.config.js", "vite.config.js"],
|
"include": ["**/*.ts", "**/*.tsx", "eslint.config.js", "prettier.config.js", "vite.config.js", "server.js", "src/fetch-polyfill.js"],
|
||||||
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2022",
|
"target": "ES2022",
|
||||||
|
|||||||
@@ -3,22 +3,37 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|||||||
import viteReact from '@vitejs/plugin-react'
|
import viteReact from '@vitejs/plugin-react'
|
||||||
import viteTsConfigPaths from 'vite-tsconfig-paths'
|
import viteTsConfigPaths from 'vite-tsconfig-paths'
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
import tailwindcss from '@tailwindcss/vite'
|
||||||
|
import { oidcSpa } from 'oidc-spa/vite-plugin'
|
||||||
|
import { nitroV2Plugin } from '@tanstack/nitro-v2-vite-plugin'
|
||||||
|
|
||||||
const config = defineConfig({
|
// import { nitro } from 'nitro/vite'
|
||||||
plugins: [
|
|
||||||
// this is the plugin that enables path aliases
|
const config = defineConfig(() => {
|
||||||
viteTsConfigPaths({
|
return {
|
||||||
projects: ['./tsconfig.json'],
|
plugins: [
|
||||||
}),
|
nitroV2Plugin(),
|
||||||
tailwindcss(),
|
viteTsConfigPaths({
|
||||||
tanstackStart({
|
projects: ['./tsconfig.json'],
|
||||||
customViteReactPlugin: true,
|
}),
|
||||||
}),
|
tailwindcss(),
|
||||||
viteReact(),
|
// nitro({ preset: 'node-server' }),
|
||||||
],
|
tanstackStart({
|
||||||
server: {
|
// spa: {
|
||||||
port: 3001,
|
// enabled: true,
|
||||||
},
|
// },
|
||||||
|
}),
|
||||||
|
oidcSpa({
|
||||||
|
freezeFetch: true,
|
||||||
|
freezeXMLHttpRequest: true,
|
||||||
|
freezeWebSocket: true,
|
||||||
|
}),
|
||||||
|
viteReact(),
|
||||||
|
],
|
||||||
|
server: {
|
||||||
|
port: 3001,
|
||||||
|
},
|
||||||
|
// build: isSsrBuild ? ssrBuildConfig : clientBuildConfig,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
|
|||||||
Reference in New Issue
Block a user