Update README.md with additional SonarCloud badges and improve CI workflow for coverage reporting
Enhanced the README.md file by adding new SonarCloud badges for coverage, bugs, code smells, security rating, and maintainability rating. Updated the CI workflow to remove coverage collection from the test step, as it is now handled by SonarCloud, streamlining the process and ensuring accurate badge representation.
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
"@hookform/resolvers": "^5.4.0",
|
||||
"@radix-ui/react-dialog": "^1.1.18",
|
||||
"@radix-ui/react-label": "^2.1.11",
|
||||
"@radix-ui/react-popover": "1.1.14",
|
||||
"@radix-ui/react-select": "^2.3.2",
|
||||
"@radix-ui/react-slot": "^1.3.0",
|
||||
"@tanstack/react-query": "^5.101.2",
|
||||
|
||||
Generated
+408
@@ -17,6 +17,9 @@ importers:
|
||||
'@radix-ui/react-label':
|
||||
specifier: ^2.1.11
|
||||
version: 2.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-popover':
|
||||
specifier: 1.1.14
|
||||
version: 1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-select':
|
||||
specifier: ^2.3.2
|
||||
version: 2.3.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
@@ -352,6 +355,9 @@ packages:
|
||||
'@radix-ui/number@1.1.3':
|
||||
resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==}
|
||||
|
||||
'@radix-ui/primitive@1.1.2':
|
||||
resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==}
|
||||
|
||||
'@radix-ui/primitive@1.1.7':
|
||||
resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==}
|
||||
|
||||
@@ -368,6 +374,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7':
|
||||
resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-collection@1.1.13':
|
||||
resolution: {integrity: sha512-Q8xYqNRFObXPmi45bFSkGdM2JA5hjBeYGFqzg6lZR3wp6b+cciraVq8DdneTabtws+uOYqjmsvmKOufk/M9Cyg==}
|
||||
peerDependencies:
|
||||
@@ -381,6 +400,15 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.2':
|
||||
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.4':
|
||||
resolution: {integrity: sha512-pWJo6lQAfR6uy1n7ii7PaCc9dLPwTXDYbQpORZU5B548Aqvl2pP1SM1vJGKyxIFqZMHRopRO4CQYX2iXAIB5jA==}
|
||||
peerDependencies:
|
||||
@@ -390,6 +418,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-context@1.1.2':
|
||||
resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-context@1.2.1':
|
||||
resolution: {integrity: sha512-EraVbFjiIjibpLr6EjvEDmSCYJU2SlKDMiO+qEK/D9GOWnQoAQlpQo2occGYC1UM9MBeEx5Bek3UtW/Qi57vAg==}
|
||||
peerDependencies:
|
||||
@@ -421,6 +458,19 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.10':
|
||||
resolution: {integrity: sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.17':
|
||||
resolution: {integrity: sha512-QAXwa38pG0xNAYh1pjdSaf86NrkqsMoDNmget/Y7X8O8E/C3Iqlj9GAPE4DfX9BPLXc7WH2TWSzMRnIoCdcjzQ==}
|
||||
peerDependencies:
|
||||
@@ -434,6 +484,15 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-focus-guards@1.1.2':
|
||||
resolution: {integrity: sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-focus-guards@1.1.5':
|
||||
resolution: {integrity: sha512-UQvlB7L/BYh3P8MLvwZnQkH521EDos40Rwnbt5+Qpg4Vbk0z3xJjRUmR6+aka4aT1IQQXFdO5bNPoE7cvFl5xQ==}
|
||||
peerDependencies:
|
||||
@@ -456,6 +515,28 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-focus-scope@1.1.7':
|
||||
resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-id@1.1.1':
|
||||
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-id@1.1.3':
|
||||
resolution: {integrity: sha512-f/Wxm0ctyMymUJK0fqTSQlm85rbzdAkoNbPXJQ5+6caowVO8Yx+NWGjGz/oGhs/D+WIbbQpOrU0hU2Li2/42xQ==}
|
||||
peerDependencies:
|
||||
@@ -478,6 +559,32 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popover@1.1.14':
|
||||
resolution: {integrity: sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popper@1.2.7':
|
||||
resolution: {integrity: sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popper@1.3.5':
|
||||
resolution: {integrity: sha512-6hLng2Rs45IZcvZ31BNvMeaFEMMEbtsog4xPcb8LZrGfyoV9fdAXqB9UKhLpTHtL0+E2DhSr6VW54Tehd6ksAQ==}
|
||||
peerDependencies:
|
||||
@@ -504,6 +611,32 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-portal@1.1.9':
|
||||
resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-presence@1.1.4':
|
||||
resolution: {integrity: sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-presence@1.1.9':
|
||||
resolution: {integrity: sha512-LTi1v05bprIb8/GSY/GWusI0jfsYjQ3CD3Nin8o7jVxnpHzVQfzjOQJoJTQkE9bdmOnsS7SFdhkXiBv8PrYnxw==}
|
||||
peerDependencies:
|
||||
@@ -517,6 +650,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-primitive@2.1.3':
|
||||
resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-primitive@2.1.8':
|
||||
resolution: {integrity: sha512-DOlK1BdcIeYYUcFkSYFka4v1h95XTov93b0jCgW1EEiZuIhdwHY2NlE1teLIh+p0uBsuZI5A+voay+iVWpprfA==}
|
||||
peerDependencies:
|
||||
@@ -543,6 +689,15 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.2.3':
|
||||
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.3.1':
|
||||
resolution: {integrity: sha512-Bu/aAQHFFh6/QAvXAeUMurJ9fbW0JUIqlojU/yBXZ7cAVqy75Y7JYYyuCr9zLNF0p4WWoJYV54CTUIf4l7FzTw==}
|
||||
peerDependencies:
|
||||
@@ -552,6 +707,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1':
|
||||
resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.3':
|
||||
resolution: {integrity: sha512-AUS7HoBBAncIsGMLNG+CcpLuJ+JIBbZzmyM8Qdb1eIThX0AlhSSC6wn40xfBlPE+ypx/vSSiRWnklUAjy3U3UA==}
|
||||
peerDependencies:
|
||||
@@ -561,6 +725,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-controllable-state@1.2.2':
|
||||
resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-controllable-state@1.2.5':
|
||||
resolution: {integrity: sha512-UB1dXpxvHjR48poyKdKdTm7jT0kp3elkUKdKQiOkirlbYumqXinSJtrjDsr9maXNPvL12bKI4CDSmydms/9Aeg==}
|
||||
peerDependencies:
|
||||
@@ -570,6 +743,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-effect-event@0.0.2':
|
||||
resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-effect-event@0.0.4':
|
||||
resolution: {integrity: sha512-XYcfa6wlXDCwQtePuEiPmXLSAhGL4DWtedSyRgGbG3y10mw+OnrLp6SyeY1gJFMiYF0Dx0nMAX9InylKbLEFQQ==}
|
||||
peerDependencies:
|
||||
@@ -579,6 +761,24 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-escape-keydown@1.1.1':
|
||||
resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.1':
|
||||
resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.3':
|
||||
resolution: {integrity: sha512-rDiah9wvtqihWtWz02XreeRKIxt2EJF8y5D9rtY9l5A2zxePAtcPiOMpDugNRw5bFHz+1/8viVoc7ZVKiJknCw==}
|
||||
peerDependencies:
|
||||
@@ -597,6 +797,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-rect@1.1.1':
|
||||
resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-rect@1.1.3':
|
||||
resolution: {integrity: sha512-W0GSYZFKEfi6raMiMEfJSngvVbFDIyxtW5JuVg5NoQBY59l0dVQVGLbhog/tOdwq0qtZ+TXuX1ikSNan4/IZXA==}
|
||||
peerDependencies:
|
||||
@@ -606,6 +815,15 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-size@1.1.1':
|
||||
resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-size@1.1.3':
|
||||
resolution: {integrity: sha512-jJq6tQQLvO/z4uWbztjwPV+1a/+H4rCaWypasLB/ac8DEdd6+p7dIm7o3F6P2KZPGkPMqD4s5424EdAdoU+GLA==}
|
||||
peerDependencies:
|
||||
@@ -628,6 +846,9 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/rect@1.1.1':
|
||||
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||
|
||||
'@radix-ui/rect@1.1.3':
|
||||
resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==}
|
||||
|
||||
@@ -1735,6 +1956,8 @@ snapshots:
|
||||
|
||||
'@radix-ui/number@1.1.3': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.2': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.7': {}
|
||||
|
||||
'@radix-ui/react-arrow@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
@@ -1746,6 +1969,15 @@ snapshots:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
react: 19.2.8
|
||||
react-dom: 19.2.8(react@19.2.8)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-collection@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.4(@types/react@19.2.17)(react@19.2.8)
|
||||
@@ -1758,12 +1990,24 @@ snapshots:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.4(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-context@1.1.2(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-context@1.2.1(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
react: 19.2.8
|
||||
@@ -1799,6 +2043,19 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
react: 19.2.8
|
||||
react-dom: 19.2.8(react@19.2.8)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
@@ -1812,6 +2069,12 @@ snapshots:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-focus-guards@1.1.2(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-focus-guards@1.1.5(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
react: 19.2.8
|
||||
@@ -1829,6 +2092,24 @@ snapshots:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
react: 19.2.8
|
||||
react-dom: 19.2.8(react@19.2.8)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-id@1.1.1(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-id@1.1.3(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.8)
|
||||
@@ -1845,6 +2126,47 @@ snapshots:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-popover@1.1.14(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-focus-guards': 1.1.2(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-id': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-popper': 1.2.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-presence': 1.1.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.17)(react@19.2.8)
|
||||
aria-hidden: 1.2.6
|
||||
react: 19.2.8
|
||||
react-dom: 19.2.8(react@19.2.8)
|
||||
react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.8)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-popper@1.2.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/rect': 1.1.1
|
||||
react: 19.2.8
|
||||
react-dom: 19.2.8(react@19.2.8)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-popper@1.3.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.9(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
@@ -1873,6 +2195,26 @@ snapshots:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
react: 19.2.8
|
||||
react-dom: 19.2.8(react@19.2.8)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-presence@1.1.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
react: 19.2.8
|
||||
react-dom: 19.2.8(react@19.2.8)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-presence@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.8)
|
||||
@@ -1882,6 +2224,15 @@ snapshots:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.17)(react@19.2.8)
|
||||
react: 19.2.8
|
||||
react-dom: 19.2.8(react@19.2.8)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-primitive@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.3.1(@types/react@19.2.17)(react@19.2.8)
|
||||
@@ -1921,6 +2272,13 @@ snapshots:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/react-slot@1.2.3(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.8)
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-slot@1.3.1(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
@@ -1929,12 +2287,26 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.3(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.17)(react@19.2.8)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-controllable-state@1.2.5(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.7
|
||||
@@ -1944,6 +2316,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-effect-event@0.0.4(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.8)
|
||||
@@ -1951,6 +2330,19 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.3(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
react: 19.2.8
|
||||
@@ -1963,6 +2355,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-rect@1.1.1(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/rect': 1.1.1
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-rect@1.1.3(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/rect': 1.1.3
|
||||
@@ -1970,6 +2369,13 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-size@1.1.1(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.17)(react@19.2.8)
|
||||
react: 19.2.8
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-size@1.1.3(@types/react@19.2.17)(react@19.2.8)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.3(@types/react@19.2.17)(react@19.2.8)
|
||||
@@ -1986,6 +2392,8 @@ snapshots:
|
||||
'@types/react': 19.2.17
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.17)
|
||||
|
||||
'@radix-ui/rect@1.1.1': {}
|
||||
|
||||
'@radix-ui/rect@1.1.3': {}
|
||||
|
||||
'@rolldown/binding-android-arm64@1.1.5':
|
||||
|
||||
@@ -6,13 +6,13 @@ import { useTranslation } from 'react-i18next'
|
||||
import { imageUrl } from '@/features/admin/images/api'
|
||||
import { ImageGallery } from '@/features/admin/images/ImageGallery'
|
||||
import { listShows } from '@/features/admin/shows/api'
|
||||
import { ShowPicker } from '@/features/admin/shows/ShowPicker'
|
||||
import { qk } from '@/shared/api/query-keys'
|
||||
import { useApiError } from '@/shared/lib/use-api-error'
|
||||
import { Badge } from '@/shared/ui/badge'
|
||||
import { Button } from '@/shared/ui/button'
|
||||
import { Input } from '@/shared/ui/input'
|
||||
import { Label } from '@/shared/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
|
||||
import {
|
||||
addCollectionShow,
|
||||
getCollection,
|
||||
@@ -165,18 +165,14 @@ export function CollectionDetail({ collectionId }: Readonly<{ collectionId: stri
|
||||
<h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
{t('admin.collections.parts')}
|
||||
</h3>
|
||||
<Select value={pendingShow} onValueChange={setPendingShow}>
|
||||
<SelectTrigger className="w-64">
|
||||
<SelectValue placeholder={t('admin.collections.addShow')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{available.map((show) => (
|
||||
<SelectItem key={show.id} value={show.id}>
|
||||
{show.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{/* Только те, кого в коллекции ещё нет: повторно добавлять некого. */}
|
||||
<ShowPicker
|
||||
value={pendingShow}
|
||||
shows={available}
|
||||
placeholder={t('admin.collections.pickShow')}
|
||||
className="h-9 w-64"
|
||||
onChange={setPendingShow}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={!pendingShow || addMutation.isPending}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ChevronDown, ChevronRight, Folder } from 'lucide-react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { listShows } from '@/features/admin/shows/api'
|
||||
import { ShowPicker } from '@/features/admin/shows/ShowPicker'
|
||||
import { qk } from '@/shared/api/query-keys'
|
||||
import type { ManualInboxFileDto } from '@/shared/api/types'
|
||||
import { useApiError } from '@/shared/lib/use-api-error'
|
||||
@@ -18,7 +19,6 @@ import {
|
||||
} from '@/shared/ui/dialog'
|
||||
import { Input } from '@/shared/ui/input'
|
||||
import { Label } from '@/shared/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
|
||||
import { toast } from '@/shared/ui/toast-store'
|
||||
import { importManualInbox, listManualInbox } from './api'
|
||||
import { compareParsed, formatSeasonEpisode, isValidRegex, parseEpisodeName } from './episode-parse'
|
||||
@@ -221,24 +221,15 @@ export function ManualInboxDialog({ onClose }: Readonly<{ onClose: () => void }>
|
||||
{/* Шоу — первое решение в этом диалоге: остальные поля лишь помогают разложить файлы. */}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.media.manualShow')}</Label>
|
||||
<Select
|
||||
<ShowPicker
|
||||
value={showId}
|
||||
onValueChange={(value) => {
|
||||
shows={shows}
|
||||
placeholder={t('admin.media.manualPickShow')}
|
||||
onChange={(value) => {
|
||||
setShowPicked(true)
|
||||
setShowId(value)
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('admin.media.manualPickShow')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(shows ?? []).map((show) => (
|
||||
<SelectItem key={show.id} value={show.id}>
|
||||
{show.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
/>
|
||||
{autoDetected && (
|
||||
<p className="text-xs text-muted-foreground">{t('admin.media.manualDetected')}</p>
|
||||
)}
|
||||
|
||||
@@ -14,15 +14,12 @@ import {
|
||||
} from '@/shared/ui/dialog'
|
||||
import { Input } from '@/shared/ui/input'
|
||||
import { Label } from '@/shared/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
|
||||
import { listShows } from '@/features/admin/shows/api'
|
||||
import { ShowPicker } from '@/features/admin/shows/ShowPicker'
|
||||
import { compareParsed, formatSeasonEpisode, isValidRegex, parseEpisodeName } from './episode-parse'
|
||||
import { matchShowByName } from './match-show'
|
||||
import { useUploadStore } from './upload-store'
|
||||
|
||||
/** Radix Select запрещает пустое значение — под «в библиотеку» используем спец-значение. */
|
||||
const LIBRARY_VALUE = '__library__'
|
||||
|
||||
export function UploadToShowDialog({
|
||||
files,
|
||||
onClose,
|
||||
@@ -119,19 +116,16 @@ export function UploadToShowDialog({
|
||||
<span className="text-muted-foreground">
|
||||
{t('admin.media.toShowMatched', { matched: matchedCount, total: files.length })}
|
||||
</span>
|
||||
<Select value="" onValueChange={(v) => applyToAll(v === LIBRARY_VALUE ? '' : v)}>
|
||||
<SelectTrigger className="h-8 w-52">
|
||||
<SelectValue placeholder={t('admin.media.applyToAll')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={LIBRARY_VALUE}>{t('admin.media.toShowLibrary')}</SelectItem>
|
||||
{shows?.map((s) => (
|
||||
<SelectItem key={s.id} value={s.id}>
|
||||
{s.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{/* Значение не храним: выбор здесь — это действие «проставить всем», а не состояние,
|
||||
поэтому на кнопке всегда её собственная подпись. */}
|
||||
<ShowPicker
|
||||
value=""
|
||||
shows={shows}
|
||||
noneLabel={t('admin.media.toShowLibrary')}
|
||||
placeholder={t('admin.media.applyToAll')}
|
||||
className="h-8 w-52"
|
||||
onChange={applyToAll}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="crt-panel max-h-72 divide-y divide-border overflow-y-auto rounded-md text-sm">
|
||||
@@ -148,26 +142,13 @@ export function UploadToShowDialog({
|
||||
<span className="min-w-0 flex-1 truncate" title={p.name}>
|
||||
{p.name}
|
||||
</span>
|
||||
<Select
|
||||
value={current || LIBRARY_VALUE}
|
||||
onValueChange={(v) =>
|
||||
setOverrides((o) => ({ ...o, [p.name]: v === LIBRARY_VALUE ? '' : v }))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="h-8 w-56 shrink-0 whitespace-nowrap [&>span]:truncate">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={LIBRARY_VALUE}>
|
||||
{t('admin.media.toShowLibrary')}
|
||||
</SelectItem>
|
||||
{shows?.map((s) => (
|
||||
<SelectItem key={s.id} value={s.id}>
|
||||
{s.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<ShowPicker
|
||||
value={current}
|
||||
shows={shows}
|
||||
noneLabel={t('admin.media.toShowLibrary')}
|
||||
className="h-8 w-56 shrink-0"
|
||||
onChange={(showId) => setOverrides((o) => ({ ...o, [p.name]: showId }))}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { Check, ChevronDown, Plus } from 'lucide-react'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { qk } from '@/shared/api/query-keys'
|
||||
import type { ShowKind, ShowSummaryDto } from '@/shared/api/types'
|
||||
import { cn } from '@/shared/lib/cn'
|
||||
import { useApiError } from '@/shared/lib/use-api-error'
|
||||
import { Input } from '@/shared/ui/input'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/shared/ui/popover'
|
||||
import { toast } from '@/shared/ui/toast-store'
|
||||
import { createShow } from './api'
|
||||
|
||||
/** Сколько шоу показывать за раз: длинный список всё равно не читают — его сужают поиском. */
|
||||
const MAX_VISIBLE = 50
|
||||
|
||||
/**
|
||||
* Выбор шоу с поиском и созданием на месте. Шоу в библиотеке бывают сотни, поэтому обычный
|
||||
* `Select` не годится: здесь строка поиска, а недостающее шоу заводится прямо из выпадашки —
|
||||
* не покидая разбора файлов. Кнопка создания появляется, только когда набранное имя ни с чем не
|
||||
* совпало, так что в обычном случае (шоу есть) она на глаза не попадается.
|
||||
*/
|
||||
export function ShowPicker({
|
||||
value,
|
||||
onChange,
|
||||
shows,
|
||||
noneLabel,
|
||||
placeholder,
|
||||
className,
|
||||
}: Readonly<{
|
||||
value: string
|
||||
onChange: (showId: string) => void
|
||||
shows: ShowSummaryDto[] | undefined
|
||||
/** Подпись пункта «без шоу» (в загрузке — «в библиотеку»). Не задан — шоу обязательно. */
|
||||
noneLabel?: string
|
||||
/** Что показывать на кнопке, когда ничего не выбрано. */
|
||||
placeholder?: string
|
||||
className?: string
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const onError = useApiError()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [query, setQuery] = useState('')
|
||||
const [active, setActive] = useState(0)
|
||||
const listRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const trimmed = query.trim()
|
||||
const matches = useMemo(() => {
|
||||
const q = trimmed.toLowerCase()
|
||||
const all = shows ?? []
|
||||
if (!q) return all.slice(0, MAX_VISIBLE)
|
||||
return all
|
||||
.filter(
|
||||
(s) => s.name.toLowerCase().includes(q) || (s.originalName ?? '').toLowerCase().includes(q),
|
||||
)
|
||||
.slice(0, MAX_VISIBLE)
|
||||
}, [shows, trimmed])
|
||||
|
||||
// Точное совпадение — создавать нечего: шоу уже есть, его надо просто выбрать.
|
||||
const exists = (shows ?? []).some((s) => s.name.toLowerCase() === trimmed.toLowerCase())
|
||||
const canCreate = trimmed.length > 0 && !exists
|
||||
|
||||
const selected = shows?.find((s) => s.id === value)
|
||||
const label = selected?.name ?? placeholder ?? noneLabel ?? t('admin.shows.pickerPlaceholder')
|
||||
|
||||
const close = () => {
|
||||
setOpen(false)
|
||||
setQuery('')
|
||||
setActive(0)
|
||||
}
|
||||
|
||||
const pick = (showId: string) => {
|
||||
onChange(showId)
|
||||
close()
|
||||
}
|
||||
|
||||
const create = useMutation({
|
||||
mutationFn: (kind: ShowKind) => createShow({ name: trimmed, kind }),
|
||||
onSuccess: async (created) => {
|
||||
// Список шоу перечитываем до выбора: иначе кнопка покажет плейсхолдер вместо нового имени.
|
||||
await queryClient.invalidateQueries({ queryKey: qk.shows.all })
|
||||
toast.success(t('admin.shows.pickerCreated'))
|
||||
pick(created.id)
|
||||
},
|
||||
onError,
|
||||
})
|
||||
|
||||
/** Стрелки/Enter по списку — иначе после поиска пришлось бы тянуться к мыши. */
|
||||
const onKeyDown = (event: React.KeyboardEvent) => {
|
||||
const options = noneLabel ? matches.length + 1 : matches.length
|
||||
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
||||
event.preventDefault()
|
||||
if (options === 0) return
|
||||
const next = event.key === 'ArrowDown' ? active + 1 : active - 1
|
||||
setActive((next + options) % options)
|
||||
requestAnimationFrame(() =>
|
||||
listRef.current
|
||||
?.querySelector('[data-active="true"]')
|
||||
?.scrollIntoView({ block: 'nearest' }),
|
||||
)
|
||||
return
|
||||
}
|
||||
if (event.key !== 'Enter') return
|
||||
event.preventDefault()
|
||||
if (options === 0) {
|
||||
if (canCreate && !create.isPending) create.mutate('Series')
|
||||
return
|
||||
}
|
||||
if (noneLabel && active === 0) {
|
||||
pick('')
|
||||
return
|
||||
}
|
||||
const show = matches[noneLabel ? active - 1 : active]
|
||||
if (show) pick(show.id)
|
||||
}
|
||||
|
||||
return (
|
||||
// modal — потому что пикер живёт внутри диалогов: те держат фокус-ловушку, и без своего
|
||||
// слоя поповер отдавал бы фокус обратно диалогу, а в строку поиска нельзя было бы печатать.
|
||||
<Popover modal open={open} onOpenChange={(next) => (next ? setOpen(true) : close())}>
|
||||
<PopoverTrigger
|
||||
className={cn(
|
||||
'flex h-10 w-full items-center justify-between gap-2 rounded-sm border border-border bg-transparent px-3 py-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span className={cn('min-w-0 truncate', !selected && 'text-muted-foreground')}>
|
||||
{label}
|
||||
</span>
|
||||
<ChevronDown className="h-4 w-4 shrink-0 opacity-60" />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-[var(--radix-popover-trigger-width)] min-w-64 p-1"
|
||||
onKeyDown={onKeyDown}
|
||||
>
|
||||
<Input
|
||||
autoFocus
|
||||
className="h-8"
|
||||
placeholder={t('admin.shows.pickerSearch')}
|
||||
value={query}
|
||||
onChange={(e) => {
|
||||
setQuery(e.target.value)
|
||||
setActive(0)
|
||||
}}
|
||||
/>
|
||||
|
||||
<div ref={listRef} className="mt-1 max-h-64 overflow-y-auto">
|
||||
{noneLabel && (
|
||||
<Option
|
||||
active={active === 0}
|
||||
selected={!value}
|
||||
label={noneLabel}
|
||||
onSelect={() => pick('')}
|
||||
/>
|
||||
)}
|
||||
{matches.map((show, index) => (
|
||||
<Option
|
||||
key={show.id}
|
||||
active={active === (noneLabel ? index + 1 : index)}
|
||||
selected={show.id === value}
|
||||
label={show.name}
|
||||
hint={show.originalName ?? undefined}
|
||||
onSelect={() => pick(show.id)}
|
||||
/>
|
||||
))}
|
||||
{matches.length === 0 && !canCreate && (
|
||||
<p className="px-2 py-1.5 text-sm text-muted-foreground">
|
||||
{t('admin.shows.pickerEmpty')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{canCreate && (
|
||||
// Сериал — основной случай (в шоу заливают сезоны), полнометражка рядом отдельной кнопкой:
|
||||
// так создание остаётся в один клик и при этом не навязывает тип.
|
||||
<div className="mt-1 flex items-center gap-1 border-t border-border pt-1">
|
||||
<button
|
||||
type="button"
|
||||
disabled={create.isPending}
|
||||
className="flex min-w-0 flex-1 items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-primary hover:bg-muted disabled:opacity-50"
|
||||
onClick={() => create.mutate('Series')}
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5 shrink-0" />
|
||||
<span className="truncate">{t('admin.shows.pickerCreate', { name: trimmed })}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={create.isPending}
|
||||
className="shrink-0 rounded-sm border border-border px-1.5 py-0.5 text-xs text-muted-foreground hover:border-primary hover:text-primary disabled:opacity-50"
|
||||
onClick={() => create.mutate('Single')}
|
||||
title={t('admin.shows.pickerCreateSingleHint')}
|
||||
>
|
||||
{t('admin.shows.kinds.Single')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
function Option({
|
||||
active,
|
||||
selected,
|
||||
label,
|
||||
hint,
|
||||
onSelect,
|
||||
}: Readonly<{
|
||||
active: boolean
|
||||
selected: boolean
|
||||
label: string
|
||||
hint?: string
|
||||
onSelect: () => void
|
||||
}>) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
data-active={active}
|
||||
className={cn(
|
||||
'flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm hover:bg-muted',
|
||||
active && 'bg-muted',
|
||||
)}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<Check className={cn('h-3.5 w-3.5 shrink-0', !selected && 'opacity-0')} />
|
||||
<span className="min-w-0 truncate">{label}</span>
|
||||
{hint && hint !== label && (
|
||||
<span className="min-w-0 shrink truncate text-xs text-muted-foreground">{hint}</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -129,6 +129,7 @@ export const en = {
|
||||
parts: 'Parts',
|
||||
units: 'Units',
|
||||
addShow: 'Add show',
|
||||
pickShow: 'Pick a show',
|
||||
empty: 'Collection is empty',
|
||||
orderHint: 'Drag to set the order of the parts — that is the order they air in.',
|
||||
},
|
||||
@@ -295,6 +296,12 @@ export const en = {
|
||||
originalName: 'Original name (eng)',
|
||||
kind: 'Kind',
|
||||
kinds: { Series: 'Series', Single: 'Movie', Interstitial: 'Clip' },
|
||||
pickerPlaceholder: 'Pick a show',
|
||||
pickerSearch: 'Search by name…',
|
||||
pickerEmpty: 'Nothing found',
|
||||
pickerCreate: 'Create series “{{name}}”',
|
||||
pickerCreateSingleHint: 'Create as a movie',
|
||||
pickerCreated: 'Show created',
|
||||
audience: 'Rating',
|
||||
audienceUnset: 'Not rated',
|
||||
audiences: {
|
||||
|
||||
@@ -129,6 +129,7 @@ export const ru = {
|
||||
parts: 'Части',
|
||||
units: 'Единиц',
|
||||
addShow: 'Добавить шоу',
|
||||
pickShow: 'Выберите шоу',
|
||||
empty: 'Коллекция пуста',
|
||||
orderHint: 'Порядок частей задаётся перетаскиванием — в нём они и пойдут в эфир.',
|
||||
},
|
||||
@@ -296,6 +297,12 @@ export const ru = {
|
||||
originalName: 'Оригинальное название (eng)',
|
||||
kind: 'Тип',
|
||||
kinds: { Series: 'Сериал', Single: 'Полнометражка', Interstitial: 'Ролик' },
|
||||
pickerPlaceholder: 'Выберите шоу',
|
||||
pickerSearch: 'Поиск по названию…',
|
||||
pickerEmpty: 'Ничего не найдено',
|
||||
pickerCreate: 'Создать сериал «{{name}}»',
|
||||
pickerCreateSingleHint: 'Создать как полнометражку',
|
||||
pickerCreated: 'Шоу создано',
|
||||
audience: 'Рейтинг',
|
||||
audienceUnset: 'Не проставлен',
|
||||
audiences: {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
||||
import { type ComponentPropsWithoutRef, type ElementRef, forwardRef } from 'react'
|
||||
import { cn } from '@/shared/lib/cn'
|
||||
|
||||
export const Popover = PopoverPrimitive.Root
|
||||
export const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
export const PopoverAnchor = PopoverPrimitive.Anchor
|
||||
|
||||
export const PopoverContent = forwardRef<
|
||||
ElementRef<typeof PopoverPrimitive.Content>,
|
||||
ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = 'start', sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
// z-50 — как у диалога: поповер живёт и внутри диалогов, и должен быть над ними.
|
||||
className={cn('crt-panel z-50 rounded-sm outline-none', className)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
))
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
Reference in New Issue
Block a user