Tech Table & History components completely rewritten & cleaned up. Just need to implement the realtime subscription now.

This commit is contained in:
2025-06-13 11:38:33 -05:00
parent c96bdab91b
commit b80bf9cd3f
20 changed files with 639 additions and 545 deletions

View File

@ -27,7 +27,7 @@
"@radix-ui/react-scroll-area": "^1.2.9", "@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@sentry/nextjs": "^9.28.1", "@sentry/nextjs": "^9.29.0",
"@supabase/ssr": "^0.6.1", "@supabase/ssr": "^0.6.1",
"@supabase/supabase-js": "^2.50.0", "@supabase/supabase-js": "^2.50.0",
"@t3-oss/env-nextjs": "^0.12.0", "@t3-oss/env-nextjs": "^0.12.0",
@ -43,7 +43,7 @@
"require-in-the-middle": "^7.5.2", "require-in-the-middle": "^7.5.2",
"sonner": "^2.0.5", "sonner": "^2.0.5",
"vaul": "^1.1.2", "vaul": "^1.1.2",
"zod": "^3.25.63" "zod": "^3.25.64"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
@ -57,7 +57,7 @@
"eslint-config-next": "^15.3.3", "eslint-config-next": "^15.3.3",
"eslint-config-prettier": "^10.1.5", "eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.4.1", "eslint-plugin-prettier": "^5.4.1",
"import-in-the-middle": "^1.14.0", "import-in-the-middle": "^1.14.2",
"postcss": "^8.5.5", "postcss": "^8.5.5",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.12", "prettier-plugin-tailwindcss": "^0.6.12",

170
pnpm-lock.yaml generated
View File

@ -39,8 +39,8 @@ importers:
specifier: ^1.2.3 specifier: ^1.2.3
version: 1.2.3(@types/react@19.1.8)(react@19.1.0) version: 1.2.3(@types/react@19.1.8)(react@19.1.0)
'@sentry/nextjs': '@sentry/nextjs':
specifier: ^9.28.1 specifier: ^9.29.0
version: 9.28.1(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9) version: 9.29.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9)
'@supabase/ssr': '@supabase/ssr':
specifier: ^0.6.1 specifier: ^0.6.1
version: 0.6.1(@supabase/supabase-js@2.50.0) version: 0.6.1(@supabase/supabase-js@2.50.0)
@ -49,7 +49,7 @@ importers:
version: 2.50.0 version: 2.50.0
'@t3-oss/env-nextjs': '@t3-oss/env-nextjs':
specifier: ^0.12.0 specifier: ^0.12.0
version: 0.12.0(typescript@5.8.3)(zod@3.25.63) version: 0.12.0(typescript@5.8.3)(zod@3.25.64)
class-variance-authority: class-variance-authority:
specifier: ^0.7.1 specifier: ^0.7.1
version: 0.7.1 version: 0.7.1
@ -87,8 +87,8 @@ importers:
specifier: ^1.1.2 specifier: ^1.1.2
version: 1.1.2(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 1.1.2(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
zod: zod:
specifier: ^3.25.63 specifier: ^3.25.64
version: 3.25.63 version: 3.25.64
devDependencies: devDependencies:
'@eslint/eslintrc': '@eslint/eslintrc':
specifier: ^3.3.1 specifier: ^3.3.1
@ -124,8 +124,8 @@ importers:
specifier: ^5.4.1 specifier: ^5.4.1
version: 5.4.1(@types/eslint@9.6.1)(eslint-config-prettier@10.1.5(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2))(prettier@3.5.3) version: 5.4.1(@types/eslint@9.6.1)(eslint-config-prettier@10.1.5(eslint@9.28.0(jiti@2.4.2)))(eslint@9.28.0(jiti@2.4.2))(prettier@3.5.3)
import-in-the-middle: import-in-the-middle:
specifier: ^1.14.0 specifier: ^1.14.2
version: 1.14.0 version: 1.14.2
postcss: postcss:
specifier: ^8.5.5 specifier: ^8.5.5
version: 8.5.5 version: 8.5.5
@ -1226,28 +1226,28 @@ packages:
'@rushstack/eslint-patch@1.11.0': '@rushstack/eslint-patch@1.11.0':
resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==} resolution: {integrity: sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==}
'@sentry-internal/browser-utils@9.28.1': '@sentry-internal/browser-utils@9.29.0':
resolution: {integrity: sha512-P/FEZkT7UqTw9P/2n/Y4Aa1OtGP6dnCvyqzPPkjiRdVa7Ep7S5ElBJloGv7077TLLBtAfCsEUVRlM1F6/jQoaA==} resolution: {integrity: sha512-Wp6UJCDVV2KVK+TG8GwdLZyDy4GtUYDmVhGMpHKPS3G/Qgpf36cY/XHwChwaHZ5P9Bk1sjS9Ok698J59S8L2nw==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@sentry-internal/feedback@9.28.1': '@sentry-internal/feedback@9.29.0':
resolution: {integrity: sha512-HOk/c26D3nlClO/xEefev8fIJzRA621PFQvNFPu/y0Z5HujEqSmIsrff0cXszPPYD95h4Mwk63E0ZYdspdeXcw==} resolution: {integrity: sha512-ADvetGrtr+RfYcQKrQxah4fHs/xDJ/VjbStVMSuaNllzwWPYNkWIGFE6YjQ7wZszj0DQIu5/H+B6lZKsFYk4xw==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@sentry-internal/replay-canvas@9.28.1': '@sentry-internal/replay-canvas@9.29.0':
resolution: {integrity: sha512-RtkogfcIpXLFCyV8CTnXmVTH2QauT/KwmUAXBbeOz3rRWsM19yjN1moHrsjxn7OdjTv+D4qWSCA8Ka1aKSpr7g==} resolution: {integrity: sha512-TrQYhSAVPhyenvu0fNkon7BznFibu1mzS5bCudxhgOWajZluUVrXcbp8Q3WZ3R+AogrcgA3Vy6aumP/+fMKdwg==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@sentry-internal/replay@9.28.1': '@sentry-internal/replay@9.29.0':
resolution: {integrity: sha512-Tv9pkfAX+1bmhxF42TL0c4uTiK2+rp5LMYEPdz6JBfpfvG/Z1unPGsuB7fQmHYKyfHBQJmi92DZV+smljm7w/g==} resolution: {integrity: sha512-we/1JPRje8sNowQCyogOV1OYWuDOP/3XmDi48XoFG2HB0XMl2HfL5LI8AvgAvC/5nrqVAAo4ktbjoVLm1fb7rg==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@sentry/babel-plugin-component-annotate@3.5.0': '@sentry/babel-plugin-component-annotate@3.5.0':
resolution: {integrity: sha512-s2go8w03CDHbF9luFGtBHKJp4cSpsQzNVqgIa9Pfa4wnjipvrK6CxVT4icpLA3YO6kg5u622Yoa5GF3cJdippw==} resolution: {integrity: sha512-s2go8w03CDHbF9luFGtBHKJp4cSpsQzNVqgIa9Pfa4wnjipvrK6CxVT4icpLA3YO6kg5u622Yoa5GF3cJdippw==}
engines: {node: '>= 14'} engines: {node: '>= 14'}
'@sentry/browser@9.28.1': '@sentry/browser@9.29.0':
resolution: {integrity: sha512-XAS46iQSq8lXTnv9udQP025JTf3PwSVRE9ePJVQhx25QBWxedqGhEOv5qqX9b1Ijf8KiZYXXhBWMQxBBXVzUaw==} resolution: {integrity: sha512-+GFX/yb+rh6V1fSgTYM6ttAgledl2aUR3T3Rg86HNuegbdX8ym6lOtUOIZ0j9jPK015HR47KIPyIZVZZJ7Rj9g==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@sentry/bundler-plugin-core@3.5.0': '@sentry/bundler-plugin-core@3.5.0':
@ -1300,22 +1300,22 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
hasBin: true hasBin: true
'@sentry/core@9.28.1': '@sentry/core@9.29.0':
resolution: {integrity: sha512-6q59r/71MeE+4StkvwdKAAyhBBNpWcii0HeiWBZ3l1gaFYQlb6bChjZJRZmxSzF5dnvkdF4duQbAC3JmjeIbPA==} resolution: {integrity: sha512-wDyNe45PM+RCGtUn1tK7LzJ08ksv8i8KRUHrst7lsinEfRm83YH+wbWrPmwkVNEngUZvYkHwGLbNXM7xgFUuDQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@sentry/nextjs@9.28.1': '@sentry/nextjs@9.29.0':
resolution: {integrity: sha512-3ryNddo0S+Ni2pcUx9u3hxhXvbGpjU8dLO8lemH3chlDMG787URD/IpO9o+mN/WXJcA/UnNFgDMebQ4bXSRj1g==} resolution: {integrity: sha512-chMSvo/CWsUw3bkGnURiOejW2hI95sofvFQQL2W98KGRhznfkfXhIh6U60fDpO2KaAbXDbbUCgcvBALdmILu9g==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
next: ^13.2.0 || ^14.0 || ^15.0.0-rc.0 next: ^13.2.0 || ^14.0 || ^15.0.0-rc.0
'@sentry/node@9.28.1': '@sentry/node@9.29.0':
resolution: {integrity: sha512-1bC8ywFPs1EIKzRlUhLoreTR/fUU45O+ezVNvOu0I6JURMwjyC8uT23f0Xkiolx4WjKZXER+d0el3HojPaYkYw==} resolution: {integrity: sha512-oABipgC/fClRuvyMeK43rigv9F+OAaoR84UaMKB7aPXN6iz634wBRVsaoZAwiR3xLL+R7MafEPPA/s9XqlG7ag==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@sentry/opentelemetry@9.28.1': '@sentry/opentelemetry@9.29.0':
resolution: {integrity: sha512-1xxriB2diNSlNpkMosdgIKzPOPwAiyFg7XgEWKpLtDYWj7WVbLNyLkUWriFr4JgU40uZp3LW+sGgnshA48/zQQ==} resolution: {integrity: sha512-QTUmre8i5+832RjzQW+g8IQ3UmBe5fbQXGbCF5hQ0UNuHle9r3Z8UZcIff5W8tm5AXMxPqvptTnDEZUUXHgBiA==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
'@opentelemetry/api': ^1.9.0 '@opentelemetry/api': ^1.9.0
@ -1325,14 +1325,14 @@ packages:
'@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.0.0 '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.0.0
'@opentelemetry/semantic-conventions': ^1.34.0 '@opentelemetry/semantic-conventions': ^1.34.0
'@sentry/react@9.28.1': '@sentry/react@9.29.0':
resolution: {integrity: sha512-XnEURhb2wG7FFCGMuW/IJc8YeDMb9LM7cZSBwBDcplR11mCRsLpf4AGm6K1nWmAn5ZvWrXRO/TBAf9DNRAfnfg==} resolution: {integrity: sha512-Wb8oKkIr/1yZ0GRz1UH4CRcjIU48iQLSDLXFKZ8YwPpPdC5qq7l4ALraxlcdB+uWq0JIgEjN5FSLamdt/NHX/w==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
react: ^16.14.0 || 17.x || 18.x || 19.x react: ^16.14.0 || 17.x || 18.x || 19.x
'@sentry/vercel-edge@9.28.1': '@sentry/vercel-edge@9.29.0':
resolution: {integrity: sha512-fX4Zc3uzpnJxcallZrWCvXFaMDBy710KlnPll3ja82QJVIhyzCf2ot109jGW8mKlpMXXBbDSKGZlY0v4pQW+lg==} resolution: {integrity: sha512-Op31XnkkLwWImIXPVpX2ErEPGmXaWt5YvVxiikyCgH0OzfWUnBKXDl9lcPC2Kn02JCQOop8o9tmQUyOagfJrog==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@sentry/webpack-plugin@3.5.0': '@sentry/webpack-plugin@3.5.0':
@ -1937,8 +1937,8 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
caniuse-lite@1.0.30001722: caniuse-lite@1.0.30001723:
resolution: {integrity: sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==} resolution: {integrity: sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==}
chalk@3.0.0: chalk@3.0.0:
resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==}
@ -2072,8 +2072,8 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
electron-to-chromium@1.5.166: electron-to-chromium@1.5.167:
resolution: {integrity: sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==} resolution: {integrity: sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==}
emoji-regex@9.2.2: emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
@ -2460,8 +2460,8 @@ packages:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
import-in-the-middle@1.14.0: import-in-the-middle@1.14.2:
resolution: {integrity: sha512-g5zLT0HaztRJWysayWYiUq/7E5H825QIiecMD2pI5QO7Wzr847l6GDvPvmZaDIdrDtS2w7qRczywxiK6SL5vRw==} resolution: {integrity: sha512-5tCuY9BV8ujfOpwtAGgsTx9CGUapcFMEEyByLv1B+v2+6DhAcw+Zr0nhQT7uwaZ7DiourxFEscghOR8e1aPLQw==}
imurmurhash@0.1.4: imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
@ -3594,8 +3594,8 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
zod@3.25.63: zod@3.25.64:
resolution: {integrity: sha512-3ttCkqhtpncYXfP0f6dsyabbYV/nEUW+Xlu89jiXbTBifUfjaSqXOG6JnQPLtqt87n7KAmnMqcjay6c0Wq0Vbw==} resolution: {integrity: sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==}
snapshots: snapshots:
@ -4172,7 +4172,7 @@ snapshots:
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
'@opentelemetry/api-logs': 0.57.2 '@opentelemetry/api-logs': 0.57.2
'@types/shimmer': 1.2.0 '@types/shimmer': 1.2.0
import-in-the-middle: 1.14.0 import-in-the-middle: 1.14.2
require-in-the-middle: 7.5.2 require-in-the-middle: 7.5.2
semver: 7.7.2 semver: 7.7.2
shimmer: 1.2.1 shimmer: 1.2.1
@ -4644,33 +4644,33 @@ snapshots:
'@rushstack/eslint-patch@1.11.0': {} '@rushstack/eslint-patch@1.11.0': {}
'@sentry-internal/browser-utils@9.28.1': '@sentry-internal/browser-utils@9.29.0':
dependencies: dependencies:
'@sentry/core': 9.28.1 '@sentry/core': 9.29.0
'@sentry-internal/feedback@9.28.1': '@sentry-internal/feedback@9.29.0':
dependencies: dependencies:
'@sentry/core': 9.28.1 '@sentry/core': 9.29.0
'@sentry-internal/replay-canvas@9.28.1': '@sentry-internal/replay-canvas@9.29.0':
dependencies: dependencies:
'@sentry-internal/replay': 9.28.1 '@sentry-internal/replay': 9.29.0
'@sentry/core': 9.28.1 '@sentry/core': 9.29.0
'@sentry-internal/replay@9.28.1': '@sentry-internal/replay@9.29.0':
dependencies: dependencies:
'@sentry-internal/browser-utils': 9.28.1 '@sentry-internal/browser-utils': 9.29.0
'@sentry/core': 9.28.1 '@sentry/core': 9.29.0
'@sentry/babel-plugin-component-annotate@3.5.0': {} '@sentry/babel-plugin-component-annotate@3.5.0': {}
'@sentry/browser@9.28.1': '@sentry/browser@9.29.0':
dependencies: dependencies:
'@sentry-internal/browser-utils': 9.28.1 '@sentry-internal/browser-utils': 9.29.0
'@sentry-internal/feedback': 9.28.1 '@sentry-internal/feedback': 9.29.0
'@sentry-internal/replay': 9.28.1 '@sentry-internal/replay': 9.29.0
'@sentry-internal/replay-canvas': 9.28.1 '@sentry-internal/replay-canvas': 9.29.0
'@sentry/core': 9.28.1 '@sentry/core': 9.29.0
'@sentry/bundler-plugin-core@3.5.0': '@sentry/bundler-plugin-core@3.5.0':
dependencies: dependencies:
@ -4726,19 +4726,19 @@ snapshots:
- encoding - encoding
- supports-color - supports-color
'@sentry/core@9.28.1': {} '@sentry/core@9.29.0': {}
'@sentry/nextjs@9.28.1(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9)': '@sentry/nextjs@9.29.0(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(next@15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)(webpack@5.99.9)':
dependencies: dependencies:
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
'@opentelemetry/semantic-conventions': 1.34.0 '@opentelemetry/semantic-conventions': 1.34.0
'@rollup/plugin-commonjs': 28.0.1(rollup@4.35.0) '@rollup/plugin-commonjs': 28.0.1(rollup@4.35.0)
'@sentry-internal/browser-utils': 9.28.1 '@sentry-internal/browser-utils': 9.29.0
'@sentry/core': 9.28.1 '@sentry/core': 9.29.0
'@sentry/node': 9.28.1 '@sentry/node': 9.29.0
'@sentry/opentelemetry': 9.28.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.34.0) '@sentry/opentelemetry': 9.29.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.34.0)
'@sentry/react': 9.28.1(react@19.1.0) '@sentry/react': 9.29.0(react@19.1.0)
'@sentry/vercel-edge': 9.28.1 '@sentry/vercel-edge': 9.29.0
'@sentry/webpack-plugin': 3.5.0(webpack@5.99.9) '@sentry/webpack-plugin': 3.5.0(webpack@5.99.9)
chalk: 3.0.0 chalk: 3.0.0
next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next: 15.3.3(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@ -4755,7 +4755,7 @@ snapshots:
- supports-color - supports-color
- webpack - webpack
'@sentry/node@9.28.1': '@sentry/node@9.29.0':
dependencies: dependencies:
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
'@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0)
@ -4787,14 +4787,14 @@ snapshots:
'@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.34.0 '@opentelemetry/semantic-conventions': 1.34.0
'@prisma/instrumentation': 6.8.2(@opentelemetry/api@1.9.0) '@prisma/instrumentation': 6.8.2(@opentelemetry/api@1.9.0)
'@sentry/core': 9.28.1 '@sentry/core': 9.29.0
'@sentry/opentelemetry': 9.28.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.34.0) '@sentry/opentelemetry': 9.29.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.34.0)
import-in-the-middle: 1.14.0 import-in-the-middle: 1.14.2
minimatch: 9.0.5 minimatch: 9.0.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@sentry/opentelemetry@9.28.1(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.34.0)': '@sentry/opentelemetry@9.29.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.57.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.34.0)':
dependencies: dependencies:
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
'@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0)
@ -4802,19 +4802,19 @@ snapshots:
'@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0) '@opentelemetry/instrumentation': 0.57.2(@opentelemetry/api@1.9.0)
'@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0)
'@opentelemetry/semantic-conventions': 1.34.0 '@opentelemetry/semantic-conventions': 1.34.0
'@sentry/core': 9.28.1 '@sentry/core': 9.29.0
'@sentry/react@9.28.1(react@19.1.0)': '@sentry/react@9.29.0(react@19.1.0)':
dependencies: dependencies:
'@sentry/browser': 9.28.1 '@sentry/browser': 9.29.0
'@sentry/core': 9.28.1 '@sentry/core': 9.29.0
hoist-non-react-statics: 3.3.2 hoist-non-react-statics: 3.3.2
react: 19.1.0 react: 19.1.0
'@sentry/vercel-edge@9.28.1': '@sentry/vercel-edge@9.29.0':
dependencies: dependencies:
'@opentelemetry/api': 1.9.0 '@opentelemetry/api': 1.9.0
'@sentry/core': 9.28.1 '@sentry/core': 9.29.0
'@sentry/webpack-plugin@3.5.0(webpack@5.99.9)': '@sentry/webpack-plugin@3.5.0(webpack@5.99.9)':
dependencies: dependencies:
@ -4881,17 +4881,17 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
'@t3-oss/env-core@0.12.0(typescript@5.8.3)(zod@3.25.63)': '@t3-oss/env-core@0.12.0(typescript@5.8.3)(zod@3.25.64)':
optionalDependencies: optionalDependencies:
typescript: 5.8.3 typescript: 5.8.3
zod: 3.25.63 zod: 3.25.64
'@t3-oss/env-nextjs@0.12.0(typescript@5.8.3)(zod@3.25.63)': '@t3-oss/env-nextjs@0.12.0(typescript@5.8.3)(zod@3.25.64)':
dependencies: dependencies:
'@t3-oss/env-core': 0.12.0(typescript@5.8.3)(zod@3.25.63) '@t3-oss/env-core': 0.12.0(typescript@5.8.3)(zod@3.25.64)
optionalDependencies: optionalDependencies:
typescript: 5.8.3 typescript: 5.8.3
zod: 3.25.63 zod: 3.25.64
'@tailwindcss/node@4.1.10': '@tailwindcss/node@4.1.10':
dependencies: dependencies:
@ -5456,8 +5456,8 @@ snapshots:
browserslist@4.25.0: browserslist@4.25.0:
dependencies: dependencies:
caniuse-lite: 1.0.30001722 caniuse-lite: 1.0.30001723
electron-to-chromium: 1.5.166 electron-to-chromium: 1.5.167
node-releases: 2.0.19 node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.25.0) update-browserslist-db: 1.1.3(browserslist@4.25.0)
@ -5486,7 +5486,7 @@ snapshots:
callsites@3.1.0: {} callsites@3.1.0: {}
caniuse-lite@1.0.30001722: {} caniuse-lite@1.0.30001723: {}
chalk@3.0.0: chalk@3.0.0:
dependencies: dependencies:
@ -5618,7 +5618,7 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
gopd: 1.2.0 gopd: 1.2.0
electron-to-chromium@1.5.166: {} electron-to-chromium@1.5.167: {}
emoji-regex@9.2.2: {} emoji-regex@9.2.2: {}
@ -6140,7 +6140,7 @@ snapshots:
parent-module: 1.0.1 parent-module: 1.0.1
resolve-from: 4.0.0 resolve-from: 4.0.0
import-in-the-middle@1.14.0: import-in-the-middle@1.14.2:
dependencies: dependencies:
acorn: 8.15.0 acorn: 8.15.0
acorn-import-attributes: 1.9.5(acorn@8.15.0) acorn-import-attributes: 1.9.5(acorn@8.15.0)
@ -6489,7 +6489,7 @@ snapshots:
'@swc/counter': 0.1.3 '@swc/counter': 0.1.3
'@swc/helpers': 0.5.15 '@swc/helpers': 0.5.15
busboy: 1.6.0 busboy: 1.6.0
caniuse-lite: 1.0.30001722 caniuse-lite: 1.0.30001723
postcss: 8.4.31 postcss: 8.4.31
react: 19.1.0 react: 19.1.0
react-dom: 19.1.0(react@19.1.0) react-dom: 19.1.0(react@19.1.0)
@ -7324,4 +7324,4 @@ snapshots:
yocto-queue@0.1.0: {} yocto-queue@0.1.0: {}
zod@3.25.63: {} zod@3.25.64: {}

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { ProfileCard } from "@/components/default/auth"; import { ProfileCard } from '@/components/default/auth';
const ProfilePage = () => { const ProfilePage = () => {
return ( return (

View File

@ -1,6 +1,6 @@
'use client'; 'use client';
import { SignUpCard } from "@/components/default/auth"; import { SignUpCard } from '@/components/default/auth';
const SignUp = () => { const SignUp = () => {
return ( return (

View File

@ -10,7 +10,7 @@ const Home = async () => {
} else if (userResponse.data) { } else if (userResponse.data) {
redirect('/status'); redirect('/status');
} else return <div />; } else return <div />;
} };
export default Home; export default Home;
//'use client'; //'use client';

View File

@ -62,7 +62,9 @@ const AvatarDropdown = () => {
</Avatar> </Avatar>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent> <DropdownMenuContent>
<DropdownMenuLabel className='font-bold'>{profile?.full_name}</DropdownMenuLabel> <DropdownMenuLabel className='font-bold'>
{profile?.full_name}
</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<button <button

View File

@ -22,62 +22,63 @@ import {
TableRow, TableRow,
Button, Button,
} from '@/components/ui'; } from '@/components/ui';
import { import { getUserHistory, getAllHistory } from '@/lib/hooks';
getUserHistory, import type { Profile } from '@/utils/supabase';
getAllHistory, import type { UserWithStatus } from '@/lib/hooks';
type HistoryEntry,
} from '@/lib/hooks';
import { toast } from 'sonner'; import { toast } from 'sonner';
type HistoryDrawerProps = { type HistoryDrawerProps = {
user_id: string; user?: Profile | null;
className?: string;
}; };
export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => { export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({
const [history, setHistory] = useState<HistoryEntry[]>([]); user = null,
className = 'max-w-4xl mx-auto',
}) => {
const [history, setHistory] = useState<UserWithStatus[]>([]);
const [page, setPage] = useState<number>(1); const [page, setPage] = useState<number>(1);
const [totalPages, setTotalPages] = useState<number>(1); const [totalPages, setTotalPages] = useState<number>(1);
const [totalCount, setTotalCount] = useState<number>(0); const [totalCount, setTotalCount] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);
const perPage = 50; const perPage = 50;
const fetchHistory = useCallback(async (currentPage: number) => { const fetchHistory = useCallback(
async (currentPage: number) => {
setLoading(true); setLoading(true);
try { try {
let response; let response;
if (user && user.id !== '') {
if (user_id && user_id !== '') { response = await getUserHistory(user.id, currentPage, perPage);
response = await getUserHistory(user_id, currentPage, perPage);
} else { } else {
response = await getAllHistory(currentPage, perPage); response = await getAllHistory(currentPage, perPage);
} }
if (!response.success) { if (!response.success) {
throw new Error(response.error); throw new Error(response.error);
} }
setHistory(response.data.statuses);
setHistory(response.data.data);
setTotalPages(response.data.meta.total_pages); setTotalPages(response.data.meta.total_pages);
setTotalCount(response.data.meta.total_count); setTotalCount(response.data.meta.total_count);
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error); toast.error(`Error fetching history: ${error as Error}`);
toast.error(`Error fetching history: ${errorMessage}`);
setHistory([]); setHistory([]);
setTotalPages(1); setTotalPages(1);
setTotalCount(0); setTotalCount(0);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [user_id, perPage]); },
[user, perPage],
);
useEffect(() => { useEffect(() => {
void fetchHistory(page); fetchHistory(page).catch((error) => {
toast.error(`Error fetching history: ${error as Error}`);
});
}, [page, fetchHistory]); }, [page, fetchHistory]);
const handlePageChange = (newPage: number) => { const handlePageChange = (newPage: number) => {
if (newPage >= 1 && newPage <= totalPages) { if (newPage >= 1 && newPage <= totalPages) setPage(newPage);
setPage(newPage);
}
}; };
const formatTime = (timestamp: string) => { const formatTime = (timestamp: string) => {
@ -92,17 +93,8 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
}); });
}; };
const getDisplayName = (entry: HistoryEntry) => {
return entry.user_profile?.full_name || 'Unknown User';
};
const getUpdatedByName = (entry: HistoryEntry) => {
if (!entry.updated_by) return 'Self';
return entry.updated_by.full_name || 'Unknown';
};
return ( return (
<DrawerContent className='max-w-4xl mx-auto'> <DrawerContent className={className}>
<DrawerHeader> <DrawerHeader>
<DrawerTitle> <DrawerTitle>
<div className='flex flex-row items-center justify-center py-4'> <div className='flex flex-row items-center justify-center py-4'>
@ -114,7 +106,7 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
className='w-8 h-8 md:w-12 md:h-12' className='w-8 h-8 md:w-12 md:h-12'
/> />
<h1 className='text-lg md:text-2xl lg:text-4xl font-bold pl-2 md:pl-4'> <h1 className='text-lg md:text-2xl lg:text-4xl font-bold pl-2 md:pl-4'>
{user_id && user_id !== '' ? 'User History' : 'All History'} {user && user.id !== '' ? 'User History' : 'All History'}
</h1> </h1>
</div> </div>
{totalCount > 0 && ( {totalCount > 0 && (
@ -124,7 +116,6 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
)} )}
</DrawerTitle> </DrawerTitle>
</DrawerHeader> </DrawerHeader>
<div className='px-4'> <div className='px-4'>
<ScrollArea className='h-96 w-full'> <ScrollArea className='h-96 w-full'>
{loading ? ( {loading ? (
@ -142,14 +133,16 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
<TableHead className='font-semibold'>Name</TableHead> <TableHead className='font-semibold'>Name</TableHead>
<TableHead className='font-semibold'>Status</TableHead> <TableHead className='font-semibold'>Status</TableHead>
<TableHead className='font-semibold'>Updated By</TableHead> <TableHead className='font-semibold'>Updated By</TableHead>
<TableHead className='font-semibold text-right'>Date & Time</TableHead> <TableHead className='font-semibold text-right'>
Date & Time
</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{history.map((entry) => ( {history.map((entry) => (
<TableRow key={entry.id}> <TableRow key={entry.id}>
<TableCell className='font-medium'> <TableCell className='font-medium'>
{getDisplayName(entry)} {entry.user.full_name}
</TableCell> </TableCell>
<TableCell className='max-w-xs'> <TableCell className='max-w-xs'>
<div className='truncate' title={entry.status}> <div className='truncate' title={entry.status}>
@ -157,7 +150,7 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
</div> </div>
</TableCell> </TableCell>
<TableCell className='text-sm text-muted-foreground'> <TableCell className='text-sm text-muted-foreground'>
{getUpdatedByName(entry)} {entry.updated_by?.full_name ?? ''}
</TableCell> </TableCell>
<TableCell className='text-right text-sm'> <TableCell className='text-right text-sm'>
{formatTime(entry.created_at)} {formatTime(entry.created_at)}
@ -169,7 +162,6 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
)} )}
</ScrollArea> </ScrollArea>
</div> </div>
<DrawerFooter> <DrawerFooter>
{totalPages > 1 && ( {totalPages > 1 && (
<Pagination> <Pagination>
@ -191,7 +183,9 @@ export const HistoryDrawer: React.FC<HistoryDrawerProps> = ({ user_id }) => {
<span>Page</span> <span>Page</span>
<span className='font-bold text-foreground'>{page}</span> <span className='font-bold text-foreground'>{page}</span>
<span>of</span> <span>of</span>
<span className='font-semibold text-foreground'>{totalPages}</span> <span className='font-semibold text-foreground'>
{totalPages}
</span>
</div> </div>
) : ( ) : (
Array.from({ length: totalPages }).map((_, idx) => ( Array.from({ length: totalPages }).map((_, idx) => (

View File

@ -9,13 +9,10 @@ import {
updateUserStatus, updateUserStatus,
type UserWithStatus, type UserWithStatus,
} from '@/lib/hooks/status'; } from '@/lib/hooks/status';
import { import { Drawer, DrawerTrigger, Progress } from '@/components/ui';
Drawer,
DrawerTrigger,
Progress,
} from '@/components/ui';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { HistoryDrawer } from '@/components/status'; import { HistoryDrawer } from '@/components/status';
import type { Profile } from '@/utils/supabase';
import type { RealtimeChannel } from '@supabase/supabase-js'; import type { RealtimeChannel } from '@supabase/supabase-js';
type TechTableProps = { type TechTableProps = {
@ -33,10 +30,10 @@ export const TechTable = ({
const [selectedIds, setSelectedIds] = useState<string[]>([]); const [selectedIds, setSelectedIds] = useState<string[]>([]);
const [selectAll, setSelectAll] = useState(false); const [selectAll, setSelectAll] = useState(false);
const [statusInput, setStatusInput] = useState(''); const [statusInput, setStatusInput] = useState('');
const [usersWithStatuses, setUsersWithStatuses] = useState<UserWithStatus[]>(initialStatuses); const [usersWithStatuses, setUsersWithStatuses] =
const [selectedHistoryUserId, setSelectedHistoryUserId] = useState(''); useState<UserWithStatus[]>(initialStatuses);
const [selectedHistoryUser, setSelectedHistoryUser] =
const supabase = createClient(); useState<Profile | null>(null);
const fetchRecentUsersWithStatuses = useCallback(async () => { const fetchRecentUsersWithStatuses = useCallback(async () => {
try { try {
@ -44,7 +41,7 @@ export const TechTable = ({
if (!response.success) throw new Error(response.error); if (!response.success) throw new Error(response.error);
return response.data; return response.data;
} catch (error) { } catch (error) {
toast.error(`Error fetching technicians: ${error as Error}`) toast.error(`Error fetching technicians: ${error as Error}`);
return []; return [];
} }
}, []); }, []);
@ -78,19 +75,24 @@ export const TechTable = ({
} else { } else {
const result = await updateStatuses(selectedIds, statusInput); const result = await updateStatuses(selectedIds, statusInput);
if (!result.success) throw new Error(result.error); if (!result.success) throw new Error(result.error);
toast.success(`Status updated for ${selectedIds.length} selected users.`); toast.success(
`Status updated for ${selectedIds.length} selected users.`,
);
} }
setSelectedIds([]); setSelectedIds([]);
setStatusInput(''); setStatusInput('');
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(`Failed to update status: ${errorMessage}`); toast.error(`Failed to update status: ${errorMessage}`);
} }
}, [isAuthenticated, statusInput, selectedIds, usersWithStatuses, profile]); }, [isAuthenticated, statusInput, selectedIds]);
const handleCheckboxChange = (id: string) => { const handleCheckboxChange = (id: string) => {
setSelectedIds((prev) => setSelectedIds((prev) =>
prev.includes(id) ? prev.filter(prevId => prevId !== id) : [...prev, id] prev.includes(id)
? prev.filter((prevId) => prevId !== id)
: [...prev, id],
); );
}; };
@ -98,7 +100,7 @@ export const TechTable = ({
if (selectAll) { if (selectAll) {
setSelectedIds([]); setSelectedIds([]);
} else { } else {
setSelectedIds(usersWithStatuses.map(tech => tech.user.id)); setSelectedIds(usersWithStatuses.map((tech) => tech.user.id));
} }
setSelectAll(!selectAll); setSelectAll(!selectAll);
}; };
@ -106,7 +108,7 @@ export const TechTable = ({
useEffect(() => { useEffect(() => {
setSelectAll( setSelectAll(
selectedIds.length === usersWithStatuses.length && selectedIds.length === usersWithStatuses.length &&
usersWithStatuses.length > 0 usersWithStatuses.length > 0,
); );
}, [selectedIds.length, usersWithStatuses.length]); }, [selectedIds.length, usersWithStatuses.length]);
@ -116,9 +118,9 @@ export const TechTable = ({
hour: 'numeric', hour: 'numeric',
minute: 'numeric', minute: 'numeric',
}); });
const day = date.getDate() const day = date.getDate();
const month = date.toLocaleString('default', { month: 'long' }); const month = date.toLocaleString('default', { month: 'long' });
return `${time} = ${month} ${day}`; return `${time} - ${month} ${day}`;
}; };
if (loading) { if (loading) {
@ -131,7 +133,10 @@ export const TechTable = ({
return ( return (
<div className={className}> <div className={className}>
<table className={`w-full text-center border-collapse ${tvMode ? 'text-4xl lg:text-5xl' : 'text-base lg:text-lg'}`}> <table
className={`w-full text-center border-collapse \
${tvMode ? 'text-4xl lg:text-5xl' : 'text-base lg:text-lg'}`}
>
<thead> <thead>
<tr className='bg-muted'> <tr className='bg-muted'>
{!tvMode && ( {!tvMode && (
@ -150,7 +155,7 @@ export const TechTable = ({
<DrawerTrigger className='hover:underline'> <DrawerTrigger className='hover:underline'>
Status Status
</DrawerTrigger> </DrawerTrigger>
<HistoryDrawer user_id='' /> <HistoryDrawer />
</Drawer> </Drawer>
</th> </th>
<th className='py-3 px-4 border font-semibold'>Updated At</th> <th className='py-3 px-4 border font-semibold'>Updated At</th>
@ -171,7 +176,9 @@ export const TechTable = ({
type='checkbox' type='checkbox'
className='scale-125 cursor-pointer' className='scale-125 cursor-pointer'
checked={selectedIds.includes(userWithStatus.user.id)} checked={selectedIds.includes(userWithStatus.user.id)}
onChange={() => handleCheckboxChange(userWithStatus.user.id)} onChange={() =>
handleCheckboxChange(userWithStatus.user.id)
}
/> />
</td> </td>
)} )}
@ -182,15 +189,12 @@ export const TechTable = ({
<Drawer> <Drawer>
<DrawerTrigger <DrawerTrigger
className='text-left w-full p-2 rounded hover:bg-muted transition-colors' className='text-left w-full p-2 rounded hover:bg-muted transition-colors'
onClick={() => setSelectedHistoryUserId(userWithStatus.user.id)} onClick={() => setSelectedHistoryUser(userWithStatus.user)}
> >
{userWithStatus.status} {userWithStatus.status}
</DrawerTrigger> </DrawerTrigger>
{selectedHistoryUserId === userWithStatus.user.id && ( {selectedHistoryUser === userWithStatus.user && (
<HistoryDrawer <HistoryDrawer user={selectedHistoryUser} />
key={selectedHistoryUserId}
user_id={selectedHistoryUserId}
/>
)} )}
</Drawer> </Drawer>
</td> </td>
@ -201,7 +205,43 @@ export const TechTable = ({
))} ))}
</tbody> </tbody>
</table> </table>
{!tvMode && (
<div className='mx-auto flex flex-row items-center justify-center py-5 gap-4'>
<input
autoFocus
type='text'
placeholder='New Status'
className={
'min-w-[120px] lg:min-w-[400px] py-2 px-3 rounded-xl \
border bg-background lg:text-2xl focus:outline-none \
focus:ring-2 focus:ring-primary'
}
value={statusInput}
onChange={(e) => setStatusInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
updateStatus().catch((error) => {
toast.error(`Failed to update status: ${error as Error}`);
});
}
}}
/>
<button
type='submit'
className={
'min-w-[100px] lg:min-w-[160px] py-2 px-4 rounded-xl \
text-center font-semibold lg:text-2xl bg-primary \
text-primary-foreground hover:bg-primary/90 \
transition-colors disabled:opacity-50 \
disabled:cursor-not-allowed'
}
onClick={() => void updateStatus()}
disabled={!statusInput.trim()}
>
Update
</button>
</div>
)}
</div> </div>
); );
}; };

View File

@ -1,32 +1,32 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import { Drawer as DrawerPrimitive } from "vaul" import { Drawer as DrawerPrimitive } from 'vaul';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
function Drawer({ function Drawer({
...props ...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) { }: React.ComponentProps<typeof DrawerPrimitive.Root>) {
return <DrawerPrimitive.Root data-slot="drawer" {...props} /> return <DrawerPrimitive.Root data-slot='drawer' {...props} />;
} }
function DrawerTrigger({ function DrawerTrigger({
...props ...props
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) { }: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} /> return <DrawerPrimitive.Trigger data-slot='drawer-trigger' {...props} />;
} }
function DrawerPortal({ function DrawerPortal({
...props ...props
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) { }: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} /> return <DrawerPrimitive.Portal data-slot='drawer-portal' {...props} />;
} }
function DrawerClose({ function DrawerClose({
...props ...props
}: React.ComponentProps<typeof DrawerPrimitive.Close>) { }: React.ComponentProps<typeof DrawerPrimitive.Close>) {
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} /> return <DrawerPrimitive.Close data-slot='drawer-close' {...props} />;
} }
function DrawerOverlay({ function DrawerOverlay({
@ -35,14 +35,14 @@ function DrawerOverlay({
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) { }: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
return ( return (
<DrawerPrimitive.Overlay <DrawerPrimitive.Overlay
data-slot="drawer-overlay" data-slot='drawer-overlay'
className={cn( className={cn(
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50", 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function DrawerContent({ function DrawerContent({
@ -51,48 +51,48 @@ function DrawerContent({
...props ...props
}: React.ComponentProps<typeof DrawerPrimitive.Content>) { }: React.ComponentProps<typeof DrawerPrimitive.Content>) {
return ( return (
<DrawerPortal data-slot="drawer-portal"> <DrawerPortal data-slot='drawer-portal'>
<DrawerOverlay /> <DrawerOverlay />
<DrawerPrimitive.Content <DrawerPrimitive.Content
data-slot="drawer-content" data-slot='drawer-content'
className={cn( className={cn(
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col", 'group/drawer-content bg-background fixed z-50 flex h-auto flex-col',
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b", 'data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b',
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t", 'data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t',
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm", 'data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm',
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm", 'data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm',
className className,
)} )}
{...props} {...props}
> >
<div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" /> <div className='bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block' />
{children} {children}
</DrawerPrimitive.Content> </DrawerPrimitive.Content>
</DrawerPortal> </DrawerPortal>
) );
} }
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) { function DrawerHeader({ className, ...props }: React.ComponentProps<'div'>) {
return ( return (
<div <div
data-slot="drawer-header" data-slot='drawer-header'
className={cn( className={cn(
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left", 'flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left',
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) { function DrawerFooter({ className, ...props }: React.ComponentProps<'div'>) {
return ( return (
<div <div
data-slot="drawer-footer" data-slot='drawer-footer'
className={cn("mt-auto flex flex-col gap-2 p-4", className)} className={cn('mt-auto flex flex-col gap-2 p-4', className)}
{...props} {...props}
/> />
) );
} }
function DrawerTitle({ function DrawerTitle({
@ -101,11 +101,11 @@ function DrawerTitle({
}: React.ComponentProps<typeof DrawerPrimitive.Title>) { }: React.ComponentProps<typeof DrawerPrimitive.Title>) {
return ( return (
<DrawerPrimitive.Title <DrawerPrimitive.Title
data-slot="drawer-title" data-slot='drawer-title'
className={cn("text-foreground font-semibold", className)} className={cn('text-foreground font-semibold', className)}
{...props} {...props}
/> />
) );
} }
function DrawerDescription({ function DrawerDescription({
@ -114,11 +114,11 @@ function DrawerDescription({
}: React.ComponentProps<typeof DrawerPrimitive.Description>) { }: React.ComponentProps<typeof DrawerPrimitive.Description>) {
return ( return (
<DrawerPrimitive.Description <DrawerPrimitive.Description
data-slot="drawer-description" data-slot='drawer-description'
className={cn("text-muted-foreground text-sm", className)} className={cn('text-muted-foreground text-sm', className)}
{...props} {...props}
/> />
) );
} }
export { export {
@ -132,4 +132,4 @@ export {
DrawerFooter, DrawerFooter,
DrawerTitle, DrawerTitle,
DrawerDescription, DrawerDescription,
} };

View File

@ -1,68 +1,68 @@
import * as React from "react" import * as React from 'react';
import { import {
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
MoreHorizontalIcon, MoreHorizontalIcon,
} from "lucide-react" } from 'lucide-react';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
import { Button, buttonVariants } from "@/components/ui/button" import { Button, buttonVariants } from '@/components/ui/button';
function Pagination({ className, ...props }: React.ComponentProps<"nav">) { function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
return ( return (
<nav <nav
role="navigation" role='navigation'
aria-label="pagination" aria-label='pagination'
data-slot="pagination" data-slot='pagination'
className={cn("mx-auto flex w-full justify-center", className)} className={cn('mx-auto flex w-full justify-center', className)}
{...props} {...props}
/> />
) );
} }
function PaginationContent({ function PaginationContent({
className, className,
...props ...props
}: React.ComponentProps<"ul">) { }: React.ComponentProps<'ul'>) {
return ( return (
<ul <ul
data-slot="pagination-content" data-slot='pagination-content'
className={cn("flex flex-row items-center gap-1", className)} className={cn('flex flex-row items-center gap-1', className)}
{...props} {...props}
/> />
) );
} }
function PaginationItem({ ...props }: React.ComponentProps<"li">) { function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
return <li data-slot="pagination-item" {...props} /> return <li data-slot='pagination-item' {...props} />;
} }
type PaginationLinkProps = { type PaginationLinkProps = {
isActive?: boolean isActive?: boolean;
} & Pick<React.ComponentProps<typeof Button>, "size"> & } & Pick<React.ComponentProps<typeof Button>, 'size'> &
React.ComponentProps<"a"> React.ComponentProps<'a'>;
function PaginationLink({ function PaginationLink({
className, className,
isActive, isActive,
size = "icon", size = 'icon',
...props ...props
}: PaginationLinkProps) { }: PaginationLinkProps) {
return ( return (
<a <a
aria-current={isActive ? "page" : undefined} aria-current={isActive ? 'page' : undefined}
data-slot="pagination-link" data-slot='pagination-link'
data-active={isActive} data-active={isActive}
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: isActive ? "outline" : "ghost", variant: isActive ? 'outline' : 'ghost',
size, size,
}), }),
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function PaginationPrevious({ function PaginationPrevious({
@ -71,15 +71,15 @@ function PaginationPrevious({
}: React.ComponentProps<typeof PaginationLink>) { }: React.ComponentProps<typeof PaginationLink>) {
return ( return (
<PaginationLink <PaginationLink
aria-label="Go to previous page" aria-label='Go to previous page'
size="default" size='default'
className={cn("gap-1 px-2.5 sm:pl-2.5", className)} className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
{...props} {...props}
> >
<ChevronLeftIcon /> <ChevronLeftIcon />
<span className="hidden sm:block">Previous</span> <span className='hidden sm:block'>Previous</span>
</PaginationLink> </PaginationLink>
) );
} }
function PaginationNext({ function PaginationNext({
@ -88,32 +88,32 @@ function PaginationNext({
}: React.ComponentProps<typeof PaginationLink>) { }: React.ComponentProps<typeof PaginationLink>) {
return ( return (
<PaginationLink <PaginationLink
aria-label="Go to next page" aria-label='Go to next page'
size="default" size='default'
className={cn("gap-1 px-2.5 sm:pr-2.5", className)} className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
{...props} {...props}
> >
<span className="hidden sm:block">Next</span> <span className='hidden sm:block'>Next</span>
<ChevronRightIcon /> <ChevronRightIcon />
</PaginationLink> </PaginationLink>
) );
} }
function PaginationEllipsis({ function PaginationEllipsis({
className, className,
...props ...props
}: React.ComponentProps<"span">) { }: React.ComponentProps<'span'>) {
return ( return (
<span <span
aria-hidden aria-hidden
data-slot="pagination-ellipsis" data-slot='pagination-ellipsis'
className={cn("flex size-9 items-center justify-center", className)} className={cn('flex size-9 items-center justify-center', className)}
{...props} {...props}
> >
<MoreHorizontalIcon className="size-4" /> <MoreHorizontalIcon className='size-4' />
<span className="sr-only">More pages</span> <span className='sr-only'>More pages</span>
</span> </span>
) );
} }
export { export {
@ -124,4 +124,4 @@ export {
PaginationPrevious, PaginationPrevious,
PaginationNext, PaginationNext,
PaginationEllipsis, PaginationEllipsis,
} };

View File

@ -1,9 +1,9 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as ProgressPrimitive from "@radix-ui/react-progress" import * as ProgressPrimitive from '@radix-ui/react-progress';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
function Progress({ function Progress({
className, className,
@ -12,20 +12,20 @@ function Progress({
}: React.ComponentProps<typeof ProgressPrimitive.Root>) { }: React.ComponentProps<typeof ProgressPrimitive.Root>) {
return ( return (
<ProgressPrimitive.Root <ProgressPrimitive.Root
data-slot="progress" data-slot='progress'
className={cn( className={cn(
"bg-primary/20 relative h-2 w-full overflow-hidden rounded-full", 'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full',
className className,
)} )}
{...props} {...props}
> >
<ProgressPrimitive.Indicator <ProgressPrimitive.Indicator
data-slot="progress-indicator" data-slot='progress-indicator'
className="bg-primary h-full w-full flex-1 transition-all" className='bg-primary h-full w-full flex-1 transition-all'
style={{ transform: `translateX(-${100 - (value || 0)}%)` }} style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/> />
</ProgressPrimitive.Root> </ProgressPrimitive.Root>
) );
} }
export { Progress } export { Progress };

View File

@ -1,9 +1,9 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
function ScrollArea({ function ScrollArea({
className, className,
@ -12,47 +12,47 @@ function ScrollArea({
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) { }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return ( return (
<ScrollAreaPrimitive.Root <ScrollAreaPrimitive.Root
data-slot="scroll-area" data-slot='scroll-area'
className={cn("relative", className)} className={cn('relative', className)}
{...props} {...props}
> >
<ScrollAreaPrimitive.Viewport <ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport" data-slot='scroll-area-viewport'
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1" className='focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1'
> >
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>
<ScrollBar /> <ScrollBar />
<ScrollAreaPrimitive.Corner /> <ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root> </ScrollAreaPrimitive.Root>
) );
} }
function ScrollBar({ function ScrollBar({
className, className,
orientation = "vertical", orientation = 'vertical',
...props ...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) { }: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return ( return (
<ScrollAreaPrimitive.ScrollAreaScrollbar <ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar" data-slot='scroll-area-scrollbar'
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"flex touch-none p-px transition-colors select-none", 'flex touch-none p-px transition-colors select-none',
orientation === "vertical" && orientation === 'vertical' &&
"h-full w-2.5 border-l border-l-transparent", 'h-full w-2.5 border-l border-l-transparent',
orientation === "horizontal" && orientation === 'horizontal' &&
"h-2.5 flex-col border-t border-t-transparent", 'h-2.5 flex-col border-t border-t-transparent',
className className,
)} )}
{...props} {...props}
> >
<ScrollAreaPrimitive.ScrollAreaThumb <ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb" data-slot='scroll-area-thumb'
className="bg-border relative flex-1 rounded-full" className='bg-border relative flex-1 rounded-full'
/> />
</ScrollAreaPrimitive.ScrollAreaScrollbar> </ScrollAreaPrimitive.ScrollAreaScrollbar>
) );
} }
export { ScrollArea, ScrollBar } export { ScrollArea, ScrollBar };

View File

@ -1,107 +1,107 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
function Table({ className, ...props }: React.ComponentProps<"table">) { function Table({ className, ...props }: React.ComponentProps<'table'>) {
return ( return (
<div <div
data-slot="table-container" data-slot='table-container'
className="relative w-full overflow-x-auto" className='relative w-full overflow-x-auto'
> >
<table <table
data-slot="table" data-slot='table'
className={cn("w-full caption-bottom text-sm", className)} className={cn('w-full caption-bottom text-sm', className)}
{...props} {...props}
/> />
</div> </div>
) );
} }
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
return ( return (
<thead <thead
data-slot="table-header" data-slot='table-header'
className={cn("[&_tr]:border-b", className)} className={cn('[&_tr]:border-b', className)}
{...props} {...props}
/> />
) );
} }
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {
return ( return (
<tbody <tbody
data-slot="table-body" data-slot='table-body'
className={cn("[&_tr:last-child]:border-0", className)} className={cn('[&_tr:last-child]:border-0', className)}
{...props} {...props}
/> />
) );
} }
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {
return ( return (
<tfoot <tfoot
data-slot="table-footer" data-slot='table-footer'
className={cn( className={cn(
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", 'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function TableRow({ className, ...props }: React.ComponentProps<"tr">) { function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
return ( return (
<tr <tr
data-slot="table-row" data-slot='table-row'
className={cn( className={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", 'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function TableHead({ className, ...props }: React.ComponentProps<"th">) { function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
return ( return (
<th <th
data-slot="table-head" data-slot='table-head'
className={cn( className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", 'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function TableCell({ className, ...props }: React.ComponentProps<"td">) { function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
return ( return (
<td <td
data-slot="table-cell" data-slot='table-cell'
className={cn( className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", 'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function TableCaption({ function TableCaption({
className, className,
...props ...props
}: React.ComponentProps<"caption">) { }: React.ComponentProps<'caption'>) {
return ( return (
<caption <caption
data-slot="table-caption" data-slot='table-caption'
className={cn("text-muted-foreground mt-4 text-sm", className)} className={cn('text-muted-foreground mt-4 text-sm', className)}
{...props} {...props}
/> />
) );
} }
export { export {
@ -113,4 +113,4 @@ export {
TableRow, TableRow,
TableCell, TableCell,
TableCaption, TableCaption,
} };

View File

@ -1,9 +1,10 @@
'use server'; 'use server';
import { createServerClient } from '@/utils/supabase'; import { createServerClient } from '@/utils/supabase';
import type { Profile, Result, Status } from '@/utils/supabase'; import type { Profile, Result } from '@/utils/supabase';
import { getUser, getProfile } from '@/lib/hooks' import { getUser, getProfile } from '@/lib/hooks';
type UserWithStatus = { export type UserWithStatus = {
id?: string;
user: Profile; user: Profile;
status: string; status: string;
created_at: string; created_at: string;
@ -11,8 +12,7 @@ type UserWithStatus = {
}; };
type PaginatedHistory = { type PaginatedHistory = {
profile?: Profile; statuses: UserWithStatus[];
statuses: Status[];
meta: { meta: {
current_page: number; current_page: number;
per_page: number; per_page: number;
@ -28,16 +28,21 @@ export const getRecentUsersWithStatuses = async (): Promise<
const supabase = await createServerClient(); const supabase = await createServerClient();
const oneDayAgo = new Date(Date.now() - 1000 * 60 * 60 * 24); const oneDayAgo = new Date(Date.now() - 1000 * 60 * 60 * 24);
const { data, error } = await supabase const { data, error } = (await supabase
.from('statuses') .from('statuses')
.select(` .select(
`
user:profiles!user_id(*), user:profiles!user_id(*),
status, status,
created_at, created_at,
updated_by:profiles!updated_by_id(*) updated_by:profiles!updated_by_id(*)
`) `,
)
.gte('created_at', oneDayAgo.toISOString()) .gte('created_at', oneDayAgo.toISOString())
.order('created_at', { ascending: false }) as { data: UserWithStatus[], error: unknown }; .order('created_at', { ascending: false })) as {
data: UserWithStatus[];
error: unknown;
};
if (error) throw error as Error; if (error) throw error as Error;
if (!data?.length) return { success: true, data: [] }; if (!data?.length) return { success: true, data: [] };
@ -69,10 +74,12 @@ export const broadcastStatusUpdates = async (
payload: { payload: {
user_status: userStatus, user_status: userStatus,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
} },
}); });
if (broadcast === 'error' || broadcast === 'timed out') if (broadcast === 'error' || broadcast === 'timed out')
throw new Error('Failed to broadcast status update. Timed out or errored.'); throw new Error(
'Failed to broadcast status update. Timed out or errored.',
);
} }
return { success: true, data: undefined }; return { success: true, data: undefined };
} catch (error) { } catch (error) {
@ -96,16 +103,14 @@ export const updateStatuses = async (
const user = userResponse.data; const user = userResponse.data;
const userProfile = profileResponse.data; const userProfile = profileResponse.data;
const inserts = userIds.map(usersId => ({ const inserts = userIds.map((usersId) => ({
user_id: usersId, user_id: usersId,
status, status,
updated_by_id: user.id, updated_by_id: user.id,
})); }));
const { data: insertedStatuses, error: insertedStatusesError } = await supabase const { data: insertedStatuses, error: insertedStatusesError } =
.from('statuses') await supabase.from('statuses').insert(inserts).select();
.insert(inserts)
.select();
if (insertedStatusesError) throw insertedStatusesError as Error; if (insertedStatusesError) throw insertedStatusesError as Error;
if (insertedStatuses) { if (insertedStatuses) {
@ -130,7 +135,6 @@ export const updateStatuses = async (
await broadcastStatusUpdates(broadcastArray); await broadcastStatusUpdates(broadcastArray);
} }
return { success: true, data: undefined }; return { success: true, data: undefined };
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
@ -139,13 +143,17 @@ export const updateStatuses = async (
} }
}; };
export const updateUserStatus = async (status: string): Promise<Result<void>> => { export const updateUserStatus = async (
status: string,
): Promise<Result<void>> => {
try { try {
const supabase = await createServerClient(); const supabase = await createServerClient();
const userResponse = await getUser(); const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`); if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const profileResponse = await getProfile(); const profileResponse = await getProfile();
if (!profileResponse.success) throw new Error(`Could not get profile! ${profileResponse.error}`); if (!profileResponse.success)
throw new Error(`Could not get profile! ${profileResponse.error}`);
const user = userResponse.data; const user = userResponse.data;
const userProfile = profileResponse.data; const userProfile = profileResponse.data;
@ -168,12 +176,11 @@ export const updateUserStatus = async (status: string): Promise<Result<void>> =>
await broadcastStatusUpdates([userStatus]); await broadcastStatusUpdates([userStatus]);
return { success: true, data: undefined }; return { success: true, data: undefined };
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
error: `Error updating user's status: ${error as Error}`, error: `Error updating user's status: ${error as Error}`,
} };
} }
}; };
@ -185,7 +192,8 @@ export const getUserHistory = async (
try { try {
const supabase = await createServerClient(); const supabase = await createServerClient();
const userResponse = await getUser(); const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`); if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const offset = (page - 1) * perPage; const offset = (page - 1) * perPage;
const { count } = await supabase const { count } = await supabase
@ -193,19 +201,30 @@ export const getUserHistory = async (
.select('*', { count: 'exact', head: true }) .select('*', { count: 'exact', head: true })
.eq('user_id', userId); .eq('user_id', userId);
const { data: statuses, error: statusesError } = await supabase const { data: statuses, error: statusesError } = (await supabase
.from('statuses') .from('statuses')
.select('*') .select(
`
id,
user:profiles!user_id(*),
status,
created_at,
updated_by:profiles!updated_by_id(*)
`,
)
.eq('user_id', userId) .eq('user_id', userId)
.order('created_at', { ascending: false }) .order('created_at', { ascending: false })
.range(offset, offset + perPage - 1) as {data: Status[], error: unknown}; .range(offset, offset + perPage - 1)) as {
data: UserWithStatus[];
error: unknown;
};
if (statusesError) throw statusesError as Error; if (statusesError) throw statusesError as Error;
const { data: profile, error: profileError } = await supabase const { data: profile, error: profileError } = (await supabase
.from('profiles') .from('profiles')
.select('*') .select('*')
.eq('id', userId) .eq('id', userId)
.single() as { data: Profile, error: unknown }; .single()) as { data: Profile; error: unknown };
if (profileError) throw profileError as Error; if (profileError) throw profileError as Error;
if (!profile) throw new Error('User profile not found!'); if (!profile) throw new Error('User profile not found!');
@ -215,7 +234,6 @@ export const getUserHistory = async (
return { return {
success: true, success: true,
data: { data: {
profile,
statuses, statuses,
meta: { meta: {
current_page: page, current_page: page,
@ -225,7 +243,6 @@ export const getUserHistory = async (
}, },
}, },
}; };
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
@ -241,18 +258,30 @@ export const getAllHistory = async (
try { try {
const supabase = await createServerClient(); const supabase = await createServerClient();
const userResponse = await getUser(); const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`); if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const offset = (page - 1) * perPage; const offset = (page - 1) * perPage;
const { count } = await supabase const { count } = await supabase
.from('statuses') .from('statuses')
.select('*', { count: 'exact', head: true }); .select('*', { count: 'exact', head: true });
const { data: statuses, error: statusesError } = await supabase const { data: statuses, error: statusesError } = (await supabase
.from('statuses') .from('statuses')
.select('*') .select(
`
id,
user:profiles!user_id(*),
status,
created_at,
updated_by:profiles!updated_by_id(*)
`,
)
.order('created_at', { ascending: false }) .order('created_at', { ascending: false })
.range(offset, offset + perPage - 1) as {data: Status[], error: unknown}; .range(offset, offset + perPage - 1)) as {
data: UserWithStatus[];
error: unknown;
};
if (statusesError) throw statusesError as Error; if (statusesError) throw statusesError as Error;
const totalCount = count ?? 0; const totalCount = count ?? 0;
@ -270,7 +299,6 @@ export const getAllHistory = async (
}, },
}, },
}; };
} catch (error) { } catch (error) {
return { return {
success: false, success: false,

View File

@ -146,6 +146,9 @@ export const getUser = async (): Promise<Result<User>> => {
if (error) throw error; if (error) throw error;
return { success: true, data: data.user }; return { success: true, data: data.user };
} catch (error) { } catch (error) {
return { success: false, error: `Could not get user! Error: ${error as Error}` }; return {
success: false,
error: `Could not get user! Error: ${error as Error}`,
};
} }
}; };

View File

@ -1,9 +1,10 @@
'use client'; 'use client';
import { createClient } from '@/utils/supabase'; import { createClient } from '@/utils/supabase';
import type { Profile, Result, Status } from '@/utils/supabase'; import type { Profile, Result } from '@/utils/supabase';
import { getUser, getProfile } from '@/lib/hooks' import { getUser, getProfile } from '@/lib/hooks';
export type UserWithStatus = { export type UserWithStatus = {
id?: string;
user: Profile; user: Profile;
status: string; status: string;
created_at: string; created_at: string;
@ -11,8 +12,7 @@ export type UserWithStatus = {
}; };
type PaginatedHistory = { type PaginatedHistory = {
profile?: Profile; statuses: UserWithStatus[];
statuses: Status[];
meta: { meta: {
current_page: number; current_page: number;
per_page: number; per_page: number;
@ -28,16 +28,21 @@ export const getRecentUsersWithStatuses = async (): Promise<
const supabase = createClient(); const supabase = createClient();
const oneDayAgo = new Date(Date.now() - 1000 * 60 * 60 * 24); const oneDayAgo = new Date(Date.now() - 1000 * 60 * 60 * 24);
const { data, error } = await supabase const { data, error } = (await supabase
.from('statuses') .from('statuses')
.select(` .select(
`
user:profiles!user_id(*), user:profiles!user_id(*),
status, status,
created_at, created_at,
updated_by:profiles!updated_by_id(*) updated_by:profiles!updated_by_id(*)
`) `,
)
.gte('created_at', oneDayAgo.toISOString()) .gte('created_at', oneDayAgo.toISOString())
.order('created_at', { ascending: false }) as { data: UserWithStatus[], error: unknown }; .order('created_at', { ascending: false })) as {
data: UserWithStatus[];
error: unknown;
};
if (error) throw error as Error; if (error) throw error as Error;
if (!data?.length) return { success: true, data: [] }; if (!data?.length) return { success: true, data: [] };
@ -69,10 +74,12 @@ export const broadcastStatusUpdates = async (
payload: { payload: {
user_status: userStatus, user_status: userStatus,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
} },
}); });
if (broadcast === 'error' || broadcast === 'timed out') if (broadcast === 'error' || broadcast === 'timed out')
throw new Error('Failed to broadcast status update. Timed out or errored.'); throw new Error(
'Failed to broadcast status update. Timed out or errored.',
);
} }
return { success: true, data: undefined }; return { success: true, data: undefined };
} catch (error) { } catch (error) {
@ -96,16 +103,14 @@ export const updateStatuses = async (
const user = userResponse.data; const user = userResponse.data;
const userProfile = profileResponse.data; const userProfile = profileResponse.data;
const inserts = userIds.map(usersId => ({ const inserts = userIds.map((usersId) => ({
user_id: usersId, user_id: usersId,
status, status,
updated_by_id: user.id, updated_by_id: user.id,
})); }));
const { data: insertedStatuses, error: insertedStatusesError } = await supabase const { data: insertedStatuses, error: insertedStatusesError } =
.from('statuses') await supabase.from('statuses').insert(inserts).select();
.insert(inserts)
.select();
if (insertedStatusesError) throw insertedStatusesError as Error; if (insertedStatusesError) throw insertedStatusesError as Error;
if (insertedStatuses) { if (insertedStatuses) {
@ -130,7 +135,6 @@ export const updateStatuses = async (
await broadcastStatusUpdates(broadcastArray); await broadcastStatusUpdates(broadcastArray);
} }
return { success: true, data: undefined }; return { success: true, data: undefined };
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
@ -139,13 +143,17 @@ export const updateStatuses = async (
} }
}; };
export const updateUserStatus = async (status: string): Promise<Result<void>> => { export const updateUserStatus = async (
status: string,
): Promise<Result<void>> => {
try { try {
const supabase = createClient(); const supabase = createClient();
const userResponse = await getUser(); const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`); if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const profileResponse = await getProfile(); const profileResponse = await getProfile();
if (!profileResponse.success) throw new Error(`Could not get profile! ${profileResponse.error}`); if (!profileResponse.success)
throw new Error(`Could not get profile! ${profileResponse.error}`);
const user = userResponse.data; const user = userResponse.data;
const userProfile = profileResponse.data; const userProfile = profileResponse.data;
@ -168,12 +176,11 @@ export const updateUserStatus = async (status: string): Promise<Result<void>> =>
await broadcastStatusUpdates([userStatus]); await broadcastStatusUpdates([userStatus]);
return { success: true, data: undefined }; return { success: true, data: undefined };
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
error: `Error updating user's status: ${error as Error}`, error: `Error updating user's status: ${error as Error}`,
} };
} }
}; };
@ -185,7 +192,8 @@ export const getUserHistory = async (
try { try {
const supabase = createClient(); const supabase = createClient();
const userResponse = await getUser(); const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`); if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const offset = (page - 1) * perPage; const offset = (page - 1) * perPage;
const { count } = await supabase const { count } = await supabase
@ -193,19 +201,30 @@ export const getUserHistory = async (
.select('*', { count: 'exact', head: true }) .select('*', { count: 'exact', head: true })
.eq('user_id', userId); .eq('user_id', userId);
const { data: statuses, error: statusesError } = await supabase const { data: statuses, error: statusesError } = (await supabase
.from('statuses') .from('statuses')
.select('*') .select(
`
id,
user:profiles!user_id(*),
status,
created_at,
updated_by:profiles!updated_by_id(*)
`,
)
.eq('user_id', userId) .eq('user_id', userId)
.order('created_at', { ascending: false }) .order('created_at', { ascending: false })
.range(offset, offset + perPage - 1) as {data: Status[], error: unknown}; .range(offset, offset + perPage - 1)) as {
data: UserWithStatus[];
error: unknown;
};
if (statusesError) throw statusesError as Error; if (statusesError) throw statusesError as Error;
const { data: profile, error: profileError } = await supabase const { data: profile, error: profileError } = (await supabase
.from('profiles') .from('profiles')
.select('*') .select('*')
.eq('id', userId) .eq('id', userId)
.single() as { data: Profile, error: unknown }; .single()) as { data: Profile; error: unknown };
if (profileError) throw profileError as Error; if (profileError) throw profileError as Error;
if (!profile) throw new Error('User profile not found!'); if (!profile) throw new Error('User profile not found!');
@ -215,7 +234,6 @@ export const getUserHistory = async (
return { return {
success: true, success: true,
data: { data: {
profile,
statuses, statuses,
meta: { meta: {
current_page: page, current_page: page,
@ -225,7 +243,6 @@ export const getUserHistory = async (
}, },
}, },
}; };
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
@ -241,18 +258,30 @@ export const getAllHistory = async (
try { try {
const supabase = createClient(); const supabase = createClient();
const userResponse = await getUser(); const userResponse = await getUser();
if (!userResponse.success) throw new Error(`Not authenticated! ${userResponse.error}`); if (!userResponse.success)
throw new Error(`Not authenticated! ${userResponse.error}`);
const offset = (page - 1) * perPage; const offset = (page - 1) * perPage;
const { count } = await supabase const { count } = await supabase
.from('statuses') .from('statuses')
.select('*', { count: 'exact', head: true }); .select('*', { count: 'exact', head: true });
const { data: statuses, error: statusesError } = await supabase const { data: statuses, error: statusesError } = (await supabase
.from('statuses') .from('statuses')
.select('*') .select(
`
id,
user:profiles!user_id(*),
status,
created_at,
updated_by:profiles!updated_by_id(*)
`,
)
.order('created_at', { ascending: false }) .order('created_at', { ascending: false })
.range(offset, offset + perPage - 1) as {data: Status[], error: unknown}; .range(offset, offset + perPage - 1)) as {
data: UserWithStatus[];
error: unknown;
};
if (statusesError) throw statusesError as Error; if (statusesError) throw statusesError as Error;
const totalCount = count ?? 0; const totalCount = count ?? 0;
@ -270,12 +299,10 @@ export const getAllHistory = async (
}, },
}, },
}; };
} catch (error) { } catch (error) {
return { return {
success: false, success: false,
error: `Error getting all history: ${error as Error}`, error: `Error getting all history: ${error as Error}`,
} };
} }
}; };

View File

@ -4,200 +4,200 @@ export type Json =
| boolean | boolean
| null | null
| { [key: string]: Json | undefined } | { [key: string]: Json | undefined }
| Json[] | Json[];
export type Database = { export type Database = {
public: { public: {
Tables: { Tables: {
profiles: { profiles: {
Row: { Row: {
avatar_url: string | null avatar_url: string | null;
email: string | null email: string | null;
full_name: string | null full_name: string | null;
id: string id: string;
provider: string | null provider: string | null;
updated_at: string | null updated_at: string | null;
} };
Insert: { Insert: {
avatar_url?: string | null avatar_url?: string | null;
email?: string | null email?: string | null;
full_name?: string | null full_name?: string | null;
id: string id: string;
provider?: string | null provider?: string | null;
updated_at?: string | null updated_at?: string | null;
} };
Update: { Update: {
avatar_url?: string | null avatar_url?: string | null;
email?: string | null email?: string | null;
full_name?: string | null full_name?: string | null;
id?: string id?: string;
provider?: string | null provider?: string | null;
updated_at?: string | null updated_at?: string | null;
} };
Relationships: [] Relationships: [];
} };
statuses: { statuses: {
Row: { Row: {
created_at: string created_at: string;
id: string id: string;
status: string status: string;
updated_by_id: string | null updated_by_id: string | null;
user_id: string user_id: string;
} };
Insert: { Insert: {
created_at?: string created_at?: string;
id?: string id?: string;
status: string status: string;
updated_by_id?: string | null updated_by_id?: string | null;
user_id: string user_id: string;
} };
Update: { Update: {
created_at?: string created_at?: string;
id?: string id?: string;
status?: string status?: string;
updated_by_id?: string | null updated_by_id?: string | null;
user_id?: string user_id?: string;
} };
Relationships: [ Relationships: [
{ {
foreignKeyName: "statuses_updated_by_id_fkey" foreignKeyName: 'statuses_updated_by_id_fkey';
columns: ["updated_by_id"] columns: ['updated_by_id'];
isOneToOne: false isOneToOne: false;
referencedRelation: "profiles" referencedRelation: 'profiles';
referencedColumns: ["id"] referencedColumns: ['id'];
}, },
{ {
foreignKeyName: "statuses_user_id_fkey" foreignKeyName: 'statuses_user_id_fkey';
columns: ["user_id"] columns: ['user_id'];
isOneToOne: false isOneToOne: false;
referencedRelation: "profiles" referencedRelation: 'profiles';
referencedColumns: ["id"] referencedColumns: ['id'];
}, },
] ];
} };
} };
Views: { Views: {
[_ in never]: never [_ in never]: never;
} };
Functions: { Functions: {
[_ in never]: never [_ in never]: never;
} };
Enums: { Enums: {
[_ in never]: never [_ in never]: never;
} };
CompositeTypes: { CompositeTypes: {
[_ in never]: never [_ in never]: never;
} };
} };
} };
type DefaultSchema = Database[Extract<keyof Database, "public">] type DefaultSchema = Database[Extract<keyof Database, 'public'>];
export type Tables< export type Tables<
DefaultSchemaTableNameOrOptions extends DefaultSchemaTableNameOrOptions extends
| keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) | keyof (DefaultSchema['Tables'] & DefaultSchema['Views'])
| { schema: keyof Database }, | { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends { TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database schema: keyof Database;
} }
? keyof (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & ? keyof (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] &
Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) Database[DefaultSchemaTableNameOrOptions['schema']]['Views'])
: never = never, : never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } > = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & ? (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] &
Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { Database[DefaultSchemaTableNameOrOptions['schema']]['Views'])[TableName] extends {
Row: infer R Row: infer R;
} }
? R ? R
: never : never
: DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema['Tables'] &
DefaultSchema["Views"]) DefaultSchema['Views'])
? (DefaultSchema["Tables"] & ? (DefaultSchema['Tables'] &
DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { DefaultSchema['Views'])[DefaultSchemaTableNameOrOptions] extends {
Row: infer R Row: infer R;
} }
? R ? R
: never : never
: never : never;
export type TablesInsert< export type TablesInsert<
DefaultSchemaTableNameOrOptions extends DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema["Tables"] | keyof DefaultSchema['Tables']
| { schema: keyof Database }, | { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends { TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database schema: keyof Database;
} }
? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] ? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables']
: never = never, : never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } > = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { ? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends {
Insert: infer I Insert: infer I;
} }
? I ? I
: never : never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables']
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends {
Insert: infer I Insert: infer I;
} }
? I ? I
: never : never
: never : never;
export type TablesUpdate< export type TablesUpdate<
DefaultSchemaTableNameOrOptions extends DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema["Tables"] | keyof DefaultSchema['Tables']
| { schema: keyof Database }, | { schema: keyof Database },
TableName extends DefaultSchemaTableNameOrOptions extends { TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof Database schema: keyof Database;
} }
? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] ? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables']
: never = never, : never = never,
> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } > = DefaultSchemaTableNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { ? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends {
Update: infer U Update: infer U;
} }
? U ? U
: never : never
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables']
? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends {
Update: infer U Update: infer U;
} }
? U ? U
: never : never
: never : never;
export type Enums< export type Enums<
DefaultSchemaEnumNameOrOptions extends DefaultSchemaEnumNameOrOptions extends
| keyof DefaultSchema["Enums"] | keyof DefaultSchema['Enums']
| { schema: keyof Database }, | { schema: keyof Database },
EnumName extends DefaultSchemaEnumNameOrOptions extends { EnumName extends DefaultSchemaEnumNameOrOptions extends {
schema: keyof Database schema: keyof Database;
} }
? keyof Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] ? keyof Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums']
: never = never, : never = never,
> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database } > = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database }
? Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] ? Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums'][EnumName]
: DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums']
? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] ? DefaultSchema['Enums'][DefaultSchemaEnumNameOrOptions]
: never : never;
export type CompositeTypes< export type CompositeTypes<
PublicCompositeTypeNameOrOptions extends PublicCompositeTypeNameOrOptions extends
| keyof DefaultSchema["CompositeTypes"] | keyof DefaultSchema['CompositeTypes']
| { schema: keyof Database }, | { schema: keyof Database },
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
schema: keyof Database schema: keyof Database;
} }
? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] ? keyof Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes']
: never = never, : never = never,
> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } > = PublicCompositeTypeNameOrOptions extends { schema: keyof Database }
? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] ? Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName]
: PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema['CompositeTypes']
? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] ? DefaultSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions]
: never : never;
export const Constants = { export const Constants = {
public: { public: {
Enums: {}, Enums: {},
}, },
} as const } as const;