diff --git a/package-lock.json b/package-lock.json index a58ae447..89b82dbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,7 +89,7 @@ "version": "5.1.11", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@asamuzakjp/generational-cache": "^1.0.1", @@ -106,7 +106,7 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@asamuzakjp/generational-cache": "^1.0.1", @@ -123,7 +123,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" @@ -133,7 +133,7 @@ "version": "2.3.9", "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@atlaskit/pragmatic-drag-and-drop": { @@ -445,7 +445,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "css-tree": "^3.0.0" @@ -627,7 +627,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -647,7 +647,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -671,7 +671,7 @@ "version": "4.1.10", "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -699,7 +699,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -722,7 +722,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -747,7 +747,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -934,7 +934,7 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": "^20.19.0 || ^22.12.0 || >=24.0.0" @@ -5337,7 +5337,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5354,7 +5353,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5371,7 +5369,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5388,7 +5385,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5405,7 +5401,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5422,7 +5417,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5439,7 +5433,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5456,7 +5449,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5473,7 +5465,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5490,7 +5481,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5507,7 +5497,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5524,7 +5513,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5541,7 +5529,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -5558,7 +5545,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6028,7 +6014,7 @@ }, "node_modules/@types/node": { "version": "22.19.11", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -7301,7 +7287,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "require-from-string": "^2.0.2" @@ -7741,7 +7727,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "mdn-data": "2.27.1", @@ -8267,7 +8253,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "whatwg-mimetype": "^5.0.0", @@ -8281,7 +8267,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "punycode": "^2.3.1" @@ -8294,7 +8280,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=20" @@ -8304,7 +8290,7 @@ "version": "16.0.1", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@exodus/bytes": "^1.11.0", @@ -10165,7 +10151,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@exodus/bytes": "^1.6.0" @@ -10644,7 +10630,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/is-regex": { @@ -10847,7 +10833,7 @@ "version": "29.1.1", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@asamuzakjp/css-color": "^5.1.11", @@ -10888,7 +10874,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=20.19.0" @@ -10901,7 +10887,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "entities": "^8.0.0" @@ -10914,7 +10900,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "punycode": "^2.3.1" @@ -10927,7 +10913,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=20" @@ -10937,7 +10923,7 @@ "version": "16.0.1", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@exodus/bytes": "^1.11.0", @@ -11132,7 +11118,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11153,7 +11138,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11174,7 +11158,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11195,7 +11178,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11216,7 +11198,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11237,7 +11218,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11258,7 +11238,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11279,7 +11258,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11300,7 +11278,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11321,7 +11298,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11342,7 +11318,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -11432,7 +11407,7 @@ "version": "11.5.2", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "dev": true, + "devOptional": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -11738,7 +11713,7 @@ "version": "2.27.1", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "dev": true, + "devOptional": true, "license": "CC0-1.0" }, "node_modules/media-chrome": { @@ -14143,7 +14118,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14337,7 +14312,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "xmlchars": "^2.2.0" @@ -14877,7 +14852,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/tabbable": { @@ -15052,7 +15027,7 @@ "version": "7.4.10", "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "tldts-core": "^7.4.10" @@ -15065,7 +15040,7 @@ "version": "7.4.10", "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/to-regex-range": { @@ -15110,7 +15085,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "tldts": "^7.0.5" @@ -15379,7 +15354,7 @@ "version": "7.29.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=20.18.1" @@ -15387,7 +15362,7 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/unified": { @@ -15956,7 +15931,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "xml-name-validator": "^5.0.0" @@ -16019,7 +15994,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=20" @@ -16181,7 +16156,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18" @@ -16191,7 +16166,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/yallist": { diff --git a/src/app/(app)/tickets/components/create-edit/ticket-form-fields.tsx b/src/app/(app)/tickets/components/create-edit/ticket-form-fields.tsx index 726e6b9d..85895e6c 100644 --- a/src/app/(app)/tickets/components/create-edit/ticket-form-fields.tsx +++ b/src/app/(app)/tickets/components/create-edit/ticket-form-fields.tsx @@ -1,7 +1,16 @@ 'use client'; 'use no memo'; -import { Autocomplete, FileUpload, Input, Label } from '@flamingo-stack/openframe-frontend-core/components/ui'; +import { + Autocomplete, + FileUpload, + Input, + Label, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@flamingo-stack/openframe-frontend-core/components/ui'; import { useDebounce } from '@flamingo-stack/openframe-frontend-core/hooks'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { Controller, type UseFormReturn, useWatch } from 'react-hook-form'; @@ -21,6 +30,7 @@ import { useTicketStatusesQuery } from '../../statuses/hooks/use-ticket-statuses import type { CreateTicketFormData } from '../../types/create-ticket.types'; import type { Ticket } from '../../types/ticket.types'; import { resolveCurrentStatus } from '../../utils/resolve-current-status'; +import { isStatusLockedByPendingApproval, STATUS_LOCKED_BY_APPROVAL_REASON } from '../../utils/status-lock'; import { getTicketDeviceName } from '../../utils/ticket-device-name'; import { TICKET_STATUS_KIND } from '../../utils/ticket-statistics'; import { avatarStartAdornment, renderAvatarOption } from '../avatar-autocomplete'; @@ -143,6 +153,15 @@ export function TicketFormFields({ .map(s => ({ label: s.name, value: s.id, color: s.color })); }, [isEditMode, ticket, statusesQuery.data]); + // Tech Required + pending approval: the server rejects any transition, so + // the status field is locked with the reason in a tooltip. + const isStatusLocked = + isEditMode && + isStatusLockedByPendingApproval({ + statusKind: ticket?.statusDefinition?.kind, + pendingApproval: ticket?.pendingApproval, + }); + const selectedStatusId = useWatch({ control, name: 'statusId' }); // New ticket: pre-select the first CUSTOM status once options load (Tech Required is // selectable but must not become the default). @@ -288,7 +307,7 @@ export function TicketFormFields({ control={control} render={({ field, fieldState }) => { const selectedStatus = statusOptions.find(o => o.value === field.value); - return ( + const statusField = ( field.onChange(val)} placeholder="Select Status" loading={!isEditMode && statusesQuery.isLoading} - disabled={isFaeForm} + disabled={isFaeForm || isStatusLocked} error={fieldState.error?.message} invalid={!!fieldState.error} startAdornment={statusStartAdornment(selectedStatus)} renderOption={renderStatusOption} /> ); + if (!isStatusLocked) return statusField; + return ( + + + +
{statusField}
+
+ {STATUS_LOCKED_BY_APPROVAL_REASON} +
+
+ ); }} /> diff --git a/src/app/(app)/tickets/components/ticket-details-view.tsx b/src/app/(app)/tickets/components/ticket-details-view.tsx index c4489a6a..81a40ef5 100644 --- a/src/app/(app)/tickets/components/ticket-details-view.tsx +++ b/src/app/(app)/tickets/components/ticket-details-view.tsx @@ -81,6 +81,7 @@ import { lastClientMessageId } from '../utils/client-chat-read'; import { isResolvedStatusId } from '../utils/is-resolved-status'; import { latestAssistantModel } from '../utils/latest-assistant-model'; import { ticketsQueryKeys } from '../utils/query-keys'; +import { isStatusLockedByPendingApproval, STATUS_LOCKED_BY_APPROVAL_REASON } from '../utils/status-lock'; import { getTicketDeviceName } from '../utils/ticket-device-name'; import { formatTicketRef } from '../utils/ticket-ref'; import { TICKET_STATUS_KIND } from '../utils/ticket-statistics'; @@ -418,6 +419,9 @@ export function TicketDetailsView({ ticketId }: TicketDetailsViewProps) { const handleTransition = useCallback( (toStatusId: string) => { if (!dialog || transitionTicket.isPending) return; + // Server-enforced lock (Tech Required + pending approval): the dropdown + // is disabled in this state, but guard the programmatic path too. + if (isStatusLockedByPendingApproval(dialog)) return; // Leaving a terminal status for a WORKING one is a REOPEN, not a plain // move: it goes through the confirmation modal (target status + assignee // + reason) instead of firing the transition directly. Gated on @@ -466,6 +470,9 @@ export function TicketDetailsView({ ticketId }: TicketDetailsViewProps) { updateApprovalStatusInMessages('client', requestId, status); try { await mutate(requestId); + // Resolving the approval releases the status lock and changes the + // available transitions - refresh the cached ticket right away. + refetchDialog(); } catch (error) { toast({ title: approving ? 'Approval Failed' : 'Rejection Failed', @@ -480,7 +487,7 @@ export function TicketDetailsView({ ticketId }: TicketDetailsViewProps) { }); } }, - [handleApproveRequest, handleRejectRequest, toast, updateApprovalStatusInMessages], + [handleApproveRequest, handleRejectRequest, toast, updateApprovalStatusInMessages, refetchDialog], ); const handleApprove = useCallback( @@ -619,6 +626,12 @@ export function TicketDetailsView({ ticketId }: TicketDetailsViewProps) { statusName: dialog.statusName, statusColor: dialog.statusColor, }); + // Tech Required + pending approval: the server rejects any transition, so + // the inline changer renders as a locked tag with the reason in a tooltip. + // Starting a direct chat stays available - it changes no status, and the + // handoff cancels the pending approval on the backend, which unlocks. + const isStatusLocked = isStatusLockedByPendingApproval(dialog); + const hasClientChat = !isAdminOwner; const hasDescription = !!dialog.description?.trim(); const hasAssignedItems = !!( @@ -704,6 +717,8 @@ export function TicketDetailsView({ ticketId }: TicketDetailsViewProps) { options: dialog.availableTransitions, onSelect: handleTransition, isPending: transitionTicket.isPending, + disabled: isStatusLocked, + disabledReason: isStatusLocked ? STATUS_LOCKED_BY_APPROVAL_REASON : undefined, }, }, ]; diff --git a/src/app/(app)/tickets/components/tickets-board.tsx b/src/app/(app)/tickets/components/tickets-board.tsx index acf8cce4..d74a6ce4 100644 --- a/src/app/(app)/tickets/components/tickets-board.tsx +++ b/src/app/(app)/tickets/components/tickets-board.tsx @@ -40,6 +40,7 @@ import type { Dialog, TicketActivityFilter } from '../types/dialog.types'; import { hasActiveAiDialog } from '../utils/ai-dialog'; import { resolveBoardActivity } from '../utils/board-activity'; import { dialogsQueryKeys, ticketsQueryKeys } from '../utils/query-keys'; +import { isStatusLockedByPendingApproval, STATUS_LOCKED_BY_APPROVAL_REASON } from '../utils/status-lock'; import { getTicketDeviceName } from '../utils/ticket-device-name'; import { ActivityFilter } from './activity-filter'; import { AssigneeFilter } from './assignee-filter'; @@ -608,6 +609,19 @@ export function TicketsBoard({ const handleChange = useCallback( (change: BoardChange) => { if (change.fromColumnId !== change.toColumnId) { + // Tech Required + pending approval: the server rejects the transition, + // so block the drop up front - toast the reason and snap the card back. + const sourceKind = statuses.find(s => s.id === change.fromColumnId)?.kind; + if (isStatusLockedByPendingApproval(dialogById.get(change.ticketId))) { + toast({ + title: 'Status Locked', + description: STATUS_LOCKED_BY_APPROVAL_REASON, + variant: 'destructive', + duration: 5000, + }); + setBoardResetNonce(nonce => nonce + 1); + return; + } // Dragging OUT of the Resolved lane is a REOPEN, not a plain move: it // goes through the confirmation modal (target status + assignee + // reason) instead of committing the drop. The card is HELD at the drop @@ -615,7 +629,6 @@ export function TicketsBoard({ // confirming keeps it there, cancelling releases it back. Gated on // `ai-resolution` — with the flag off the drop commits directly (legacy). if (featureFlags.aiResolution.enabled()) { - const sourceKind = statuses.find(s => s.id === change.fromColumnId)?.kind; if (sourceKind === 'RESOLVED') { setHeldMove(change); setReopenTarget({ ticketId: change.ticketId, initialStatusId: change.toColumnId }); @@ -641,7 +654,7 @@ export function TicketsBoard({ beforeTicketId: change.beforeTicketId, }); }, - [moveTicket, statuses, dialogById, setHeldMove], + [moveTicket, statuses, dialogById, toast, setHeldMove], ); const showEmptyState = diff --git a/src/app/(app)/tickets/queries/ticket-queries.ts b/src/app/(app)/tickets/queries/ticket-queries.ts index 86a4c790..05b94166 100644 --- a/src/app/(app)/tickets/queries/ticket-queries.ts +++ b/src/app/(app)/tickets/queries/ticket-queries.ts @@ -110,6 +110,10 @@ export const GET_TICKET_QUERY = ` name color } + pendingApproval { + id + approvalType + } creationSource owner { ... on ClientTicketOwner { diff --git a/src/app/(app)/tickets/types/ticket.types.ts b/src/app/(app)/tickets/types/ticket.types.ts index e609313f..aa59e793 100644 --- a/src/app/(app)/tickets/types/ticket.types.ts +++ b/src/app/(app)/tickets/types/ticket.types.ts @@ -78,6 +78,8 @@ export interface Ticket { status: TicketStatus; statusDefinition?: TicketStatusRef & { kind: string }; availableTransitions?: TicketStatusRef[]; + // Latest pending tool-approval request (locks Tech Required status changes). + pendingApproval?: { id: string; approvalType?: string } | null; creationSource?: string; owner: TicketOwner; deviceId?: string; diff --git a/src/app/(app)/tickets/utils/status-lock.ts b/src/app/(app)/tickets/utils/status-lock.ts new file mode 100644 index 00000000..b8b516de --- /dev/null +++ b/src/app/(app)/tickets/utils/status-lock.ts @@ -0,0 +1,13 @@ +import { TICKET_STATUS_KIND } from './ticket-statistics'; + +// Mirrors the server rule (TicketTransitionPolicyValidator): a Tech Required +// ticket with a pending tool-approval request cannot change status until the +// approval is resolved. `pendingApproval` already excludes escalate-to-human +// offers on the backend, so its presence matches the lock semantics exactly. +export const STATUS_LOCKED_BY_APPROVAL_REASON = 'Status is locked while an approval request is pending.'; + +export function isStatusLockedByPendingApproval( + ticket: { statusKind?: string; pendingApproval?: unknown } | null | undefined, +): boolean { + return !!ticket && ticket.statusKind === TICKET_STATUS_KIND.TECH_REQUIRED && !!ticket.pendingApproval; +}