Compare commits
110
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
587d7ceead | ||
|
|
ac8ef8caa2 | ||
|
|
7d0fb265d4 | ||
|
|
743db57bb3 | ||
|
|
d7d2e36573 | ||
|
|
8b157b8e87 | ||
|
|
0935e8cbc7 | ||
|
|
f04ce348ae | ||
|
|
508e0d182c | ||
|
|
dbd5d43d65 | ||
|
|
7d8a725e34 | ||
|
|
491adb4090 | ||
|
|
1a3f5277d9 | ||
|
|
ffcd5fc535 | ||
|
|
65e0c2ed62 | ||
|
|
36b4a3b481 | ||
|
|
e0455b200a | ||
|
|
348a5398d0 | ||
|
|
af87bfc7b6 | ||
|
|
beefe86ffd | ||
|
|
3a7c609588 | ||
|
|
aeeee04166 | ||
|
|
288d6782a4 | ||
|
|
55915af9df | ||
|
|
bfc457470c | ||
|
|
210145f5ca | ||
|
|
d0793a281e | ||
|
|
6994edd216 | ||
|
|
3a0ae93846 | ||
|
|
13a5db6313 | ||
|
|
9c011b6416 | ||
|
|
294e16e1a6 | ||
|
|
71f7120239 | ||
|
|
0e28986dc7 | ||
|
|
51a3ed7d18 | ||
|
|
c15867a41c | ||
|
|
c4e4d775f5 | ||
|
|
cd07502128 | ||
|
|
1ef2cc227a | ||
|
|
7226285746 | ||
|
|
dd93cd302c | ||
|
|
37b7ccb52e | ||
|
|
b8a76bd6a9 | ||
|
|
07e1237f05 | ||
|
|
29546740c7 | ||
|
|
42cc96a28a | ||
|
|
84b07af246 | ||
|
|
8a72dbaf68 | ||
|
|
af6b997ceb | ||
|
|
e97dd392c0 | ||
|
|
ae0526a58a | ||
|
|
d419c1c2cb | ||
|
|
48e03b4237 | ||
|
|
2fad519b7e | ||
|
|
84b73b912f | ||
|
|
a3cbaaa2e7 | ||
|
|
881f3295c0 | ||
|
|
0cc9230672 | ||
|
|
010a8f3a5a | ||
|
|
4952e0daa1 | ||
|
|
455b39bb1e | ||
|
|
34772bd3b4 | ||
|
|
2296044280 | ||
|
|
2d201bfcde | ||
|
|
17931191f5 | ||
|
|
4e8289e976 | ||
|
|
41c56e026f | ||
|
|
1e8bf782ca | ||
|
|
e4eedf464b | ||
|
|
c274cb134d | ||
|
|
c0fe6cce48 | ||
|
|
9e89a37b4d | ||
|
|
a704e410c5 | ||
|
|
d24884f7e6 | ||
|
|
2cada9ba27 | ||
|
|
a7c203d7a4 | ||
|
|
abc07a5e2f | ||
|
|
904c615665 | ||
|
|
e9af59769d | ||
|
|
e933d64790 | ||
|
|
c07fe860e5 | ||
|
|
1b6dd3c40b | ||
|
|
7c82f940fc | ||
|
|
a69636d7e3 | ||
|
|
83f535c2e9 | ||
|
|
47bf0b3e77 | ||
|
|
e09cd3a77c | ||
|
|
5d521358f7 | ||
|
|
cfb2f42e96 | ||
|
|
cbbfb1c6ad | ||
|
|
86263bd235 | ||
|
|
fc9dc836e7 | ||
|
|
a508ede373 | ||
|
|
fd4da24b1d | ||
|
|
2f0acfc07f | ||
|
|
f8cf3618f7 | ||
|
|
0ad1ce98ad | ||
|
|
04c27a9b7f | ||
|
|
12fb7104a1 | ||
|
|
f5548f39da | ||
|
|
b16d0949a4 | ||
|
|
5dfc3573e5 | ||
|
|
7800499c72 | ||
|
|
045eef24c0 | ||
|
|
3a8a1f9c08 | ||
|
|
f55f4cedfe | ||
|
|
85b77aeb91 | ||
|
|
1b4ad64ef6 | ||
|
|
c7939b8923 | ||
|
|
a155a90fba |
Generated
+265
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "no-copy-frontend",
|
"name": "no-copy-frontend",
|
||||||
"version": "0.58.0",
|
"version": "0.104.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "no-copy-frontend",
|
"name": "no-copy-frontend",
|
||||||
"version": "0.58.0",
|
"version": "0.104.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@a2seven/yoo-checkout": "^1.1.4",
|
"@a2seven/yoo-checkout": "^1.1.4",
|
||||||
"@react-input/mask": "^2.0.4",
|
"@react-input/mask": "^2.0.4",
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
"next": "^16.0.7",
|
"next": "^16.0.7",
|
||||||
"next-auth": "5.0.0-beta.30",
|
"next-auth": "5.0.0-beta.30",
|
||||||
"next-intl": "^4.5.8",
|
"next-intl": "^4.5.8",
|
||||||
|
"pdfjs-dist": "^5.7.284",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-slick": "^0.31.0",
|
"react-slick": "^0.31.0",
|
||||||
@@ -1129,6 +1130,256 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@napi-rs/canvas": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"workspaces": [
|
||||||
|
"e2e/*"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@napi-rs/canvas-android-arm64": "0.1.100",
|
||||||
|
"@napi-rs/canvas-darwin-arm64": "0.1.100",
|
||||||
|
"@napi-rs/canvas-darwin-x64": "0.1.100",
|
||||||
|
"@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100",
|
||||||
|
"@napi-rs/canvas-linux-arm64-gnu": "0.1.100",
|
||||||
|
"@napi-rs/canvas-linux-arm64-musl": "0.1.100",
|
||||||
|
"@napi-rs/canvas-linux-riscv64-gnu": "0.1.100",
|
||||||
|
"@napi-rs/canvas-linux-x64-gnu": "0.1.100",
|
||||||
|
"@napi-rs/canvas-linux-x64-musl": "0.1.100",
|
||||||
|
"@napi-rs/canvas-win32-arm64-msvc": "0.1.100",
|
||||||
|
"@napi-rs/canvas-win32-x64-msvc": "0.1.100"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-android-arm64": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-darwin-arm64": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-darwin-x64": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-linux-x64-musl": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-win32-arm64-msvc": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
|
||||||
|
"version": "0.1.100",
|
||||||
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.100.tgz",
|
||||||
|
"integrity": "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@napi-rs/wasm-runtime": {
|
"node_modules/@napi-rs/wasm-runtime": {
|
||||||
"version": "0.2.12",
|
"version": "0.2.12",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
|
||||||
@@ -6573,6 +6824,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/pdfjs-dist": {
|
||||||
|
"version": "5.7.284",
|
||||||
|
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.7.284.tgz",
|
||||||
|
"integrity": "sha512-h4EdYQczmGhbOlqc3PPZwxevn7ApdWPbovAuWXOB/DjIyigSnwfy2oze7c6mRcSr9XgLp3eN3EeL4DyySTPMFw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.13.0 || >=24"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@napi-rs/canvas": "^0.1.100"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "no-copy-frontend",
|
"name": "no-copy-frontend",
|
||||||
"version": "0.90.0",
|
"version": "0.120.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev -p 2999",
|
"dev": "next dev -p 2999",
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"next": "^16.0.7",
|
"next": "^16.0.7",
|
||||||
"next-auth": "5.0.0-beta.30",
|
"next-auth": "5.0.0-beta.30",
|
||||||
"next-intl": "^4.5.8",
|
"next-intl": "^4.5.8",
|
||||||
|
"pdfjs-dist": "^5.7.284",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-slick": "^0.31.0",
|
"react-slick": "^0.31.0",
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import Providers from '@/app/providers/getQueryServer'
|
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { NextIntlClientProvider, hasLocale } from 'next-intl';
|
import { NextIntlClientProvider, hasLocale } from 'next-intl';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { routing } from '@/i18n/routing';
|
import { routing } from '@/i18n/routing';
|
||||||
import { ToastProvider } from '@/app/providers/ToastProvider';
|
import { ToastProvider } from '@/app/providers/ToastProvider';
|
||||||
|
import Providers from '@/app/providers/getQueryServer';
|
||||||
|
import { Suspense } from 'react';
|
||||||
|
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
import "../styles/global-styles.scss";
|
import "../styles/global-styles.scss";
|
||||||
@@ -30,9 +31,9 @@ export default async function RootLayout({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={locale}>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<NextIntlClientProvider>
|
<NextIntlClientProvider locale={locale}>
|
||||||
<Providers>
|
<Providers>
|
||||||
{children}
|
{children}
|
||||||
<ToastProvider />
|
<ToastProvider />
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import DashboardFilesInfo from '@/app/ui/dashboard/new/dashboard-files-info';
|
|||||||
import DahboardLimitsSection from '@/app/ui/dashboard/new/dashborad-limits-section';
|
import DahboardLimitsSection from '@/app/ui/dashboard/new/dashborad-limits-section';
|
||||||
import DahboardGeographySection from '@/app/ui/dashboard/new/dashboard-geography-section';
|
import DahboardGeographySection from '@/app/ui/dashboard/new/dashboard-geography-section';
|
||||||
import DashboardLastCheck from '@/app/ui/dashboard/new/dashboard-last-check';
|
import DashboardLastCheck from '@/app/ui/dashboard/new/dashboard-last-check';
|
||||||
import ViolationsCarousel from '@/app/ui/dashboard/new/violations-carousel';
|
|
||||||
import DashboardUserViolations from '@/app/ui/dashboard/new/dashboard-user-violations';
|
import DashboardUserViolations from '@/app/ui/dashboard/new/dashboard-user-violations';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export interface FileDetails {
|
|||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params: Promise<{ id: string }>;
|
params: Promise<{ id: string }>;
|
||||||
searchParams: Promise<{ page?: string, status?: string }>;
|
searchParams: Promise<{ page?: string, status?: string, violationId?: string, caseType?: 'complaint' | 'claim', startDate: string, endDate: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({
|
export default async function Page({
|
||||||
@@ -56,7 +56,7 @@ export default async function Page({
|
|||||||
searchParams
|
searchParams
|
||||||
}: PageProps) {
|
}: PageProps) {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
const { page, status } = await searchParams;
|
const { page, status, violationId, caseType, startDate, endDate } = await searchParams;
|
||||||
const currentPage = Number(page) || 1;
|
const currentPage = Number(page) || 1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -71,15 +71,19 @@ export default async function Page({
|
|||||||
>
|
>
|
||||||
<FilePageFileInfo fileInfo={fileInfo} />
|
<FilePageFileInfo fileInfo={fileInfo} />
|
||||||
{/* <FilePageActions /> */}
|
{/* <FilePageActions /> */}
|
||||||
<ViolationPpageFileStatistic />
|
<ViolationPpageFileStatistic id={id} />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<FilePageViolationsList
|
<FilePageViolationsList
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
status={status}
|
status={status}
|
||||||
fileId={id}
|
fileId={id}
|
||||||
|
violationId={violationId}
|
||||||
|
caseType={caseType}
|
||||||
|
startDate={startDate}
|
||||||
|
endDate={endDate}
|
||||||
/>
|
/>
|
||||||
<FilePageNote />
|
{/* <FilePageNote /> */}
|
||||||
{/* <FilePageViolationInfo /> */}
|
{/* <FilePageViolationInfo /> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
'use server'
|
||||||
|
|
||||||
|
import { TrackingStatistic } from '@/app/ui/tracking-page/tracking-statistic';
|
||||||
|
import FilesTable from '@/app/ui/dashboard/files-table';
|
||||||
|
import { TrackingHistoryView } from '@/app/ui/tracking-page/tracking-history-view';
|
||||||
|
|
||||||
|
export default async function Page() {
|
||||||
|
const FILE_TYPE = "document";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="tracking-page"
|
||||||
|
>
|
||||||
|
<TrackingStatistic />
|
||||||
|
<FilesTable fileType={FILE_TYPE} showFileLink={true} />
|
||||||
|
<TrackingHistoryView />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
import PageTitleColorFrame from '@/app/ui/page-title-color-frame';
|
||||||
import ViolationsMyCases from '@/app/ui/violations/violations-my-cases';
|
import ViolationsMyComplaint from '@/app/ui/violations/violations-my-complaint';
|
||||||
import ViolationsCheckAllSection from '@/app/ui/violations/violations-check-all-section';
|
import ViolationsCheckAllSection from '@/app/ui/violations/violations-check-all-section';
|
||||||
import ViolationsTable from '@/app/ui/violations/violations-table';
|
import ViolationsTable from '@/app/ui/violations/violations-table';
|
||||||
import ViolationsStatistic from '@/app/ui/violations/violations-statistic';
|
import ViolationsStatistic from '@/app/ui/violations/violations-statistic';
|
||||||
import AnalyticsCardGeography from '@/app/ui/reports/analytics-card-geography';
|
import AnalyticsCardGeography from '@/app/ui/reports/analytics-card-geography';
|
||||||
import AnalyticsCardDistribution from '@/app/ui/reports/analytics-card-distribution';
|
import AnalyticsCardDistribution from '@/app/ui/reports/analytics-card-distribution';
|
||||||
|
import ViolationsMyClaims from '@/app/ui/violations/violations-my-claims';
|
||||||
|
import ViolationsLastCombinedCases from '@/app/ui/violations/violations-last-combined-cases';
|
||||||
|
import { ViolationsMyComplaintsTable } from '@/app/ui/violations/tables/violations-my-complaints-table';
|
||||||
|
import { ViolationsMyClaimsTable } from '@/app/ui/violations/tables/violations-my-claims-table';
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
@@ -16,11 +20,17 @@ export default function Page() {
|
|||||||
<ViolationsStatistic />
|
<ViolationsStatistic />
|
||||||
<ViolationsCheckAllSection />
|
<ViolationsCheckAllSection />
|
||||||
<ViolationsTable />
|
<ViolationsTable />
|
||||||
<ViolationsMyCases />
|
<ViolationsLastCombinedCases />
|
||||||
|
{/* <ViolationsMyComplaint />
|
||||||
|
<ViolationsMyClaims /> */}
|
||||||
<div className="analytics-grid">
|
<div className="analytics-grid">
|
||||||
<AnalyticsCardDistribution />
|
<AnalyticsCardDistribution />
|
||||||
<AnalyticsCardGeography />
|
<AnalyticsCardGeography />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="analytics-grid">
|
||||||
|
<ViolationsMyClaimsTable />
|
||||||
|
<ViolationsMyComplaintsTable />
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { PdfProvider } from '@/app/contexts/PdfContext';
|
||||||
|
import { WatchDocProviders } from '@/app/providers/WatchDocProviders';
|
||||||
|
|
||||||
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<WatchDocProviders>
|
||||||
|
<PdfProvider>
|
||||||
|
{children}
|
||||||
|
</PdfProvider>
|
||||||
|
</WatchDocProviders>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import PdfViewer from '@/app/components/PdfViewer';
|
||||||
|
import { DownloadButton } from '@/app/components/document-viewer/DownloadButton';
|
||||||
|
import { CopyLinkButton } from '@/app/components/document-viewer/CopyLinkButton';
|
||||||
|
import { fetchDocumentInfo } from '@/app/actions/trackingActions';
|
||||||
|
import { LoginUserBage } from '@/app/components/document-viewer/LoginUserBage';
|
||||||
|
|
||||||
|
interface PageProps {
|
||||||
|
searchParams: Promise<{ docid: string; title?: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DocumentInfo {
|
||||||
|
checksCount?: number;
|
||||||
|
checksum?: string;
|
||||||
|
createdAt?: string;
|
||||||
|
downloadUrl?: string;
|
||||||
|
existsOnDisk?: boolean;
|
||||||
|
fileExtension?: string;
|
||||||
|
fileFormat?: string | null;
|
||||||
|
fileName?: string;
|
||||||
|
filePath?: string;
|
||||||
|
fileSize?: number;
|
||||||
|
fileUploadDate?: string;
|
||||||
|
formattedSize?: string;
|
||||||
|
id: string;
|
||||||
|
mimeType?: string;
|
||||||
|
monitoring?: string;
|
||||||
|
originalFileName?: string;
|
||||||
|
ownerCompany?: string | null;
|
||||||
|
ownerEmail?: string;
|
||||||
|
ownerName?: string;
|
||||||
|
permissions?: {
|
||||||
|
DOWNLOAD?: boolean;
|
||||||
|
};
|
||||||
|
protectStatus?: string;
|
||||||
|
protectedFilePath?: string;
|
||||||
|
status?: string;
|
||||||
|
storedFileName?: string;
|
||||||
|
supportId?: number;
|
||||||
|
thumbnailFileUrl?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
uploadSessionId?: string;
|
||||||
|
userId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ searchParams }: PageProps) {
|
||||||
|
const { docid, title } = await searchParams;
|
||||||
|
|
||||||
|
let response: DocumentInfo | null = null;
|
||||||
|
let error: string | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await fetchDocumentInfo(docid);
|
||||||
|
} catch (err) {
|
||||||
|
error = 'Не удалось загрузить информацию о документе';
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentTitle = response?.fileName || 'FileName';
|
||||||
|
|
||||||
|
if (error || !response) {
|
||||||
|
return (
|
||||||
|
<div className="watch-doc-wrapper">
|
||||||
|
<div className="watch-doc-page">
|
||||||
|
<header className="watch-doc-header">
|
||||||
|
<Link href="#" className="header-logo">NoCopy</Link>
|
||||||
|
<LoginUserBage />
|
||||||
|
</header>
|
||||||
|
<div className="error-container">
|
||||||
|
<h2>Ошибка загрузки документа</h2>
|
||||||
|
<p>{error || 'Документ не найден'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="watch-doc-wrapper">
|
||||||
|
<div className="watch-doc-page">
|
||||||
|
<header className="watch-doc-header">
|
||||||
|
<Link href="#" className="header-logo">NoCopy</Link>
|
||||||
|
<LoginUserBage />
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="watch-doc-status">
|
||||||
|
<div className="st-title">Защита подтверждена</div>
|
||||||
|
<div className="st-sub">
|
||||||
|
Документ <strong>{response?.fileName ? response?.fileName : '#'}</strong> зарегистрирован и защищён платформой NoCopy.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="watch-doc-grid">
|
||||||
|
<div
|
||||||
|
className="flex flex-col justify-between"
|
||||||
|
>
|
||||||
|
<div className="watch-doc-card">
|
||||||
|
<div className="card-head">Правообладатель</div>
|
||||||
|
<div className="card-body">
|
||||||
|
<div className="owner-row">
|
||||||
|
<div className="owner-av">
|
||||||
|
{response?.fileName ? response?.fileName[0] : ''}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{response?.ownerName && (
|
||||||
|
<div className="owner-name">{response?.ownerName}</div>
|
||||||
|
)}
|
||||||
|
{response?.ownerCompany && (
|
||||||
|
<div className="owner-name">{response?.ownerCompany}</div>
|
||||||
|
)}
|
||||||
|
{response?.ownerName && (
|
||||||
|
<div className="owner-meta">Зарегистрирован в NoCopy</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="watch-doc-actions">
|
||||||
|
{response?.permissions?.DOWNLOAD && (
|
||||||
|
<DownloadButton fileId={docid} fileName={response?.fileName}/>
|
||||||
|
)}
|
||||||
|
<CopyLinkButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div className="watch-doc-card">
|
||||||
|
<div className="card-head">Предпросмотр</div>
|
||||||
|
<div className="card-body">
|
||||||
|
<PdfViewer
|
||||||
|
fileId={docid}
|
||||||
|
fileName={documentTitle}
|
||||||
|
showDownload={response.permissions?.DOWNLOAD}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="watch-doc-footer">
|
||||||
|
<Link href="#">NoCopy</Link>
|
||||||
|
<Link href="/terms-of-use">Условия</Link>
|
||||||
|
<Link href="/privacy-policy">Конфиденциальность</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -44,12 +44,12 @@ export async function getUserFilesInfo() {
|
|||||||
msg_id: 20005,
|
msg_id: 20005,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'user_files_info',
|
action: 'user_files_info',
|
||||||
token: token
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -81,13 +81,13 @@ export async function fetchTariffs(tariffTerm: 'MONTHLY' | 'YEAR') {
|
|||||||
msg_id: 30001,
|
msg_id: 30001,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'get',
|
action: 'get',
|
||||||
user_token: token,
|
|
||||||
tariff_term: tariffTerm
|
tariff_term: tariffTerm
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -108,6 +108,7 @@ export async function fetchTariffs(tariffTerm: 'MONTHLY' | 'YEAR') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchTokensBundle() {
|
export async function fetchTokensBundle() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
@@ -122,7 +123,8 @@ export async function fetchTokensBundle() {
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -143,6 +145,7 @@ export async function fetchTokensBundle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getBuildData() {
|
export async function getBuildData() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/check/api/build`, {
|
const response = await fetch(`${API_BASE_URL}/check/api/build`, {
|
||||||
@@ -150,6 +153,7 @@ export async function getBuildData() {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -165,7 +169,7 @@ export async function getBuildData() {
|
|||||||
|
|
||||||
export async function fetchINN(inn: string) {
|
export async function fetchINN(inn: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/auth`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
|
|||||||
+9
-12
@@ -72,7 +72,7 @@ export async function authorization(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { email, password } = validatedFields.data;
|
const { email, password } = validatedFields.data;
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/auth`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -89,6 +89,7 @@ export async function authorization(
|
|||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let parsed = await response.json();
|
let parsed = await response.json();
|
||||||
|
|
||||||
if (parsed.message_code === 0) {
|
if (parsed.message_code === 0) {
|
||||||
await createSession(parsed.message_body.token, email);
|
await createSession(parsed.message_body.token, email);
|
||||||
} else {
|
} else {
|
||||||
@@ -234,7 +235,7 @@ export async function registration(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { fullName, email, password, phone } = validatedFields.data;
|
const { fullName, email, password, phone } = validatedFields.data;
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/auth`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -400,13 +401,11 @@ export async function tokenLifeExtension() {
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 20008,
|
msg_id: 20008
|
||||||
message_body: {
|
|
||||||
token: token
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -431,14 +430,14 @@ export async function confirmEmail(userId: number, verifyToken: string) {
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
|
'Authorization': `Bearer ${verifyToken}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 20009,
|
msg_id: 20009,
|
||||||
message_body: {
|
message_body: {
|
||||||
resend: 0,
|
resend: 0,
|
||||||
user_id: userId,
|
user_id: userId
|
||||||
verify_token: verifyToken
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -474,7 +473,6 @@ export async function confirmEmail(userId: number, verifyToken: string) {
|
|||||||
export async function resendConfirmEmail(userId: number) {
|
export async function resendConfirmEmail(userId: number) {
|
||||||
console.log('resendConfirmEmail');
|
console.log('resendConfirmEmail');
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -524,10 +522,9 @@ export async function resetPassword(
|
|||||||
formData: FormData,
|
formData: FormData,
|
||||||
) {
|
) {
|
||||||
const email = formData.get('email');
|
const email = formData.get('email');
|
||||||
console.log('resetPassword');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/auth`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -611,6 +608,7 @@ export async function confirmPassword(
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${verifyToken}`
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
@@ -618,7 +616,6 @@ export async function confirmPassword(
|
|||||||
message_body: {
|
message_body: {
|
||||||
email: email,
|
email: email,
|
||||||
password: password,
|
password: password,
|
||||||
verifyToken: verifyToken,
|
|
||||||
action: "confirmVerification"
|
action: "confirmVerification"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ export async function companyUserRegistration(
|
|||||||
phone: phone,
|
phone: phone,
|
||||||
password,
|
password,
|
||||||
accountType: 'b2b',
|
accountType: 'b2b',
|
||||||
authToken: token,
|
|
||||||
mail_verified: false,
|
mail_verified: false,
|
||||||
ip: ip,
|
ip: ip,
|
||||||
user_agent: userAgent,
|
user_agent: userAgent,
|
||||||
@@ -103,7 +102,8 @@ export async function companyUserRegistration(
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -113,14 +113,6 @@ export async function companyUserRegistration(
|
|||||||
message_body: { token: string },
|
message_body: { token: string },
|
||||||
message_code: number
|
message_code: number
|
||||||
};
|
};
|
||||||
console.log({
|
|
||||||
fullName: fullName,
|
|
||||||
email,
|
|
||||||
phone: phone,
|
|
||||||
password,
|
|
||||||
accountType: 'b2b',
|
|
||||||
authToken: token
|
|
||||||
});
|
|
||||||
|
|
||||||
if (parsed.message_code === 0) {
|
if (parsed.message_code === 0) {
|
||||||
console.log(`registration - ${email}`);
|
console.log(`registration - ${email}`);
|
||||||
|
|||||||
@@ -88,19 +88,22 @@ export const createComplaintSchema = z
|
|||||||
.object({
|
.object({
|
||||||
textArea: z
|
textArea: z
|
||||||
.string()
|
.string()
|
||||||
.min(6, { error: 'text-area-error-fill-complaint-text' })
|
.min(1, { error: 'text-area-error-fill-complaint-text' })
|
||||||
|
.max(2000, {error: 'text-area-error-complaint-text-too-long'})
|
||||||
.trim(),
|
.trim(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const createCaseSchema = z
|
export const createClaimSchema = z
|
||||||
.object({
|
.object({
|
||||||
textArea: z
|
textArea: z
|
||||||
.string()
|
.string()
|
||||||
.min(6, { error: 'fill-text-of-the-case' })
|
.min(1, { error: 'fill-text-of-the-case' })
|
||||||
|
.max(2000, {error: 'claim-text-must-not-exceed-2000-characters'})
|
||||||
.trim(),
|
.trim(),
|
||||||
caseTitle: z
|
caseTitle: z
|
||||||
.string()
|
.string()
|
||||||
.min(6, { error: 'fill-title-of-the-case' })
|
.min(1, { error: 'fill-title-of-the-case' })
|
||||||
|
.max(300, {error: 'claim-title-must-not-exceed-300-characters'})
|
||||||
.trim(),
|
.trim(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ export async function getUserFilesData({
|
|||||||
msg_id: 20005,
|
msg_id: 20005,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'search_files',
|
action: 'search_files',
|
||||||
token: token,
|
|
||||||
page: page,
|
page: page,
|
||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
query: query,
|
query: query,
|
||||||
@@ -43,7 +42,8 @@ export async function getUserFilesData({
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -77,12 +77,12 @@ export async function removeUserFile(fileId: string, fullDelete: number) {
|
|||||||
action: 'delete_file',
|
action: 'delete_file',
|
||||||
file_id: fileId,
|
file_id: fileId,
|
||||||
full_delete: fullDelete,
|
full_delete: fullDelete,
|
||||||
token: token
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,13 +114,13 @@ export async function viewFileInfo(fileId: string) {
|
|||||||
msg_id: 20005,
|
msg_id: 20005,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'file_info',
|
action: 'file_info',
|
||||||
file_id: fileId,
|
file_id: fileId
|
||||||
token: token
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -163,3 +163,43 @@ export async function downloadFile(fileId: string, fileName: string) {
|
|||||||
fileName: fileName
|
fileName: fileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function FileAnAppeal(fileId: string, appealReason: string, additionalInfo: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 20005,
|
||||||
|
message_body: {
|
||||||
|
action: 'submit_appeal',
|
||||||
|
file_id: fileId,
|
||||||
|
appeal_reason: appealReason,
|
||||||
|
additional_info: additionalInfo
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed;
|
||||||
|
} else {
|
||||||
|
throw parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
import { API_BASE_URL } from '@/app/actions/definitions';
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
import { getSessionData } from '@/app/actions/session';
|
import { getSessionData } from '@/app/actions/session';
|
||||||
import { FormState } from '@/app/actions/auth';
|
|
||||||
import { unknown } from 'zod';
|
|
||||||
|
|
||||||
interface initMessageBody {
|
interface initMessageBody {
|
||||||
file_name: string,
|
file_name: string,
|
||||||
@@ -22,7 +20,6 @@ export async function fileUpload(messageBody: initMessageBody, convertTo?: strin
|
|||||||
}
|
}
|
||||||
const message = messageBody;
|
const message = messageBody;
|
||||||
message.action = 'init';
|
message.action = 'init';
|
||||||
message.token = token;
|
|
||||||
|
|
||||||
if (convertTo && convertTo !== 'reject') {
|
if (convertTo && convertTo !== 'reject') {
|
||||||
message.convertTo = convertTo;
|
message.convertTo = convertTo;
|
||||||
@@ -38,7 +35,8 @@ export async function fileUpload(messageBody: initMessageBody, convertTo?: strin
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -68,6 +66,8 @@ export async function fileUpload(messageBody: initMessageBody, convertTo?: strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function cancelUpload(udloadId: string) {
|
export async function cancelUpload(udloadId: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -81,7 +81,8 @@ export async function cancelUpload(udloadId: string) {
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -114,7 +115,6 @@ export type ChunkResponse = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function chunkUpload(formData: FormData, itPrivate?: boolean): Promise<ChunkResponse> {
|
export async function chunkUpload(formData: FormData, itPrivate?: boolean): Promise<ChunkResponse> {
|
||||||
console.log('chunkUpload');
|
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -123,19 +123,21 @@ export async function chunkUpload(formData: FormData, itPrivate?: boolean): Prom
|
|||||||
: `${API_BASE_URL}/api/v1/files/chunk`;
|
: `${API_BASE_URL}/api/v1/files/chunk`;
|
||||||
|
|
||||||
|
|
||||||
if (token && itPrivate) {
|
/* if (token && itPrivate) {
|
||||||
formData.append('token', token);
|
formData.append('token', token);
|
||||||
}
|
} */
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
/* ...(token && itPrivate && { 'Authorization': `Bearer ${token}` }) возможно нужно будет условно отправлять токен*/
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let parsed = await response.json();
|
let parsed = await response.json();
|
||||||
console.log(formData);
|
|
||||||
console.log(parsed);
|
|
||||||
|
|
||||||
if (parsed.message_code === 0) {
|
if (parsed.message_code === 0) {
|
||||||
return {
|
return {
|
||||||
@@ -171,6 +173,8 @@ export async function chunkUpload(formData: FormData, itPrivate?: boolean): Prom
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function checkChunkStatus(upload_id: string) {
|
export async function checkChunkStatus(upload_id: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -184,7 +188,8 @@ export async function checkChunkStatus(upload_id: string) {
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -206,6 +211,8 @@ export async function checkChunkStatus(upload_id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllowedFilesExtensions(type: string) {
|
export async function getAllowedFilesExtensions(type: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -219,7 +226,8 @@ export async function getAllowedFilesExtensions(type: string) {
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -250,7 +258,6 @@ export async function checkOperationPrice(action: string, filesCount: number, fi
|
|||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 30008,
|
msg_id: 30008,
|
||||||
message_body: {
|
message_body: {
|
||||||
auth_token: token,
|
|
||||||
action: action,
|
action: action,
|
||||||
file_type: fileType,
|
file_type: fileType,
|
||||||
count_files: filesCount
|
count_files: filesCount
|
||||||
@@ -258,7 +265,8 @@ export async function checkOperationPrice(action: string, filesCount: number, fi
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ export async function setMonitoring(fileId: string, monitoringType: string) {
|
|||||||
msg_id: 30007,
|
msg_id: 30007,
|
||||||
message_body: {
|
message_body: {
|
||||||
monitoring_type: monitoringType,
|
monitoring_type: monitoringType,
|
||||||
file_id: fileId,
|
file_id: fileId
|
||||||
auth_token: token
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -65,14 +65,12 @@ export async function fetchMonitoringStats(): Promise<MonitoringStats | null> {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 30011,
|
msg_id: 30011
|
||||||
message_body: {
|
|
||||||
token: token
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -88,3 +86,66 @@ export async function fetchMonitoringStats(): Promise<MonitoringStats | null> {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchLastMonitoringCheck() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30011,
|
||||||
|
message_body: {
|
||||||
|
limit: 1
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_body?.searches_by_status) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchMonitoringInfo() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30027
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_body) {
|
||||||
|
return parsed.message_body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,8 @@ export interface NotificationBody {
|
|||||||
id: number,
|
id: number,
|
||||||
message: string,
|
message: string,
|
||||||
status: string | null,
|
status: string | null,
|
||||||
type: string
|
type: string,
|
||||||
|
context: Record<string, string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchActiveNotifications() {
|
export async function fetchActiveNotifications() {
|
||||||
@@ -21,13 +22,13 @@ export async function fetchActiveNotifications() {
|
|||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 30015,
|
msg_id: 30015,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'active',
|
action: 'active'
|
||||||
authToken: token,
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -58,7 +59,6 @@ export async function fetchAllNotifications(page: number = 0, size: number = 10)
|
|||||||
msg_id: 30015,
|
msg_id: 30015,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'list',
|
action: 'list',
|
||||||
authToken: token,
|
|
||||||
page: page,
|
page: page,
|
||||||
size: size,
|
size: size,
|
||||||
sortBy: "createdAt",
|
sortBy: "createdAt",
|
||||||
@@ -69,7 +69,8 @@ export async function fetchAllNotifications(page: number = 0, size: number = 10)
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,13 +101,13 @@ export async function notificationsMarkAsRead(ids: number[]) {
|
|||||||
msg_id: 30015,
|
msg_id: 30015,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'markRead',
|
action: 'markRead',
|
||||||
authToken: token,
|
|
||||||
notificationIds: ids
|
notificationIds: ids
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -131,7 +132,6 @@ export async function notificationsMarkAsRead(ids: number[]) {
|
|||||||
|
|
||||||
export async function notificationsDelete(ids: number[]) {
|
export async function notificationsDelete(ids: number[]) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
console.log('notificationsDelete');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
@@ -141,13 +141,13 @@ export async function notificationsDelete(ids: number[]) {
|
|||||||
msg_id: 30015,
|
msg_id: 30015,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'delete',
|
action: 'delete',
|
||||||
authToken: token,
|
|
||||||
notificationIds: ids
|
notificationIds: ids
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ export async function fetchReferralsLevels() {
|
|||||||
msg_id: 30003,
|
msg_id: 30003,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'levels',
|
action: 'levels',
|
||||||
token: token,
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,12 +57,12 @@ export async function fetchReferralUserStats() {
|
|||||||
msg_id: 30003,
|
msg_id: 30003,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'userStats',
|
action: 'userStats',
|
||||||
token: token,
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,14 +96,14 @@ export async function fetchReferralInvitees() {
|
|||||||
msg_id: 30003,
|
msg_id: 30003,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'invitees',
|
action: 'invitees',
|
||||||
token: token,
|
|
||||||
pageSize: 100,
|
pageSize: 100,
|
||||||
pageNumber: 0
|
pageNumber: 0
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -137,25 +137,18 @@ export async function referralRefill() {
|
|||||||
msg_id: 30003,
|
msg_id: 30003,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'refill',
|
action: 'refill',
|
||||||
token: token,
|
|
||||||
amount: 2000,
|
amount: 2000,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
console.log('referralRefill');
|
|
||||||
console.log({
|
|
||||||
action: 'refill',
|
|
||||||
token: token,
|
|
||||||
amount: 2000,
|
|
||||||
})
|
|
||||||
let parsed = await response.json();
|
let parsed = await response.json();
|
||||||
console.log(parsed);
|
|
||||||
|
|
||||||
if (parsed.message_code === 0) {
|
if (parsed.message_code === 0) {
|
||||||
return parsed.message_body;
|
return parsed.message_body;
|
||||||
@@ -366,8 +359,8 @@ export async function createPayoutRequest(
|
|||||||
const response = await fetch(`${API_BASE_URL}/api/payouts/create-request`, {
|
const response = await fetch(`${API_BASE_URL}/api/payouts/create-request`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
"amount": amount,
|
'amount': amount,
|
||||||
"payoutMethodId": payoutMethodId
|
'payoutMethodId': payoutMethodId
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ export async function searchUserFiles(fileId: string) {
|
|||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/files/${fileId}/similar?similarityLevels=DUPLICATE,SIMILARITY&auth_token=${token}`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/files/${fileId}/similar?similarityLevels=DUPLICATE,SIMILARITY`, {
|
||||||
method: 'GET'
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -40,48 +43,6 @@ export async function searchUserFiles(fileId: string) {
|
|||||||
export async function searchGlobalFiles(fileId: string, currentPage: number) {
|
export async function searchGlobalFiles(fileId: string, currentPage: number) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
//удалить когда поиск будет нормально работать
|
|
||||||
/* return {
|
|
||||||
images: [
|
|
||||||
{
|
|
||||||
url: 'string1',
|
|
||||||
pageTitle: `string1 from page ${currentPage}`,
|
|
||||||
height: 100,
|
|
||||||
width: 100,
|
|
||||||
host: 'string1',
|
|
||||||
pageUrl: 'string1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: 'string1',
|
|
||||||
pageTitle: `string2 from page ${currentPage}`,
|
|
||||||
height: 100,
|
|
||||||
width: 100,
|
|
||||||
host: 'string1',
|
|
||||||
pageUrl: 'string1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: 'string1',
|
|
||||||
pageTitle: `string3 from page ${currentPage}`,
|
|
||||||
height: 100,
|
|
||||||
width: 100,
|
|
||||||
host: 'string1',
|
|
||||||
pageUrl: 'string1'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
page: 0,
|
|
||||||
pageSize: 0,
|
|
||||||
totalPages: 11,
|
|
||||||
totalResults: 100,
|
|
||||||
} */
|
|
||||||
|
|
||||||
/* return {
|
|
||||||
images: [],
|
|
||||||
page: 0,
|
|
||||||
pageSize: 0,
|
|
||||||
totalPages: 0,
|
|
||||||
totalResults: 0
|
|
||||||
} */
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -91,11 +52,13 @@ export async function searchGlobalFiles(fileId: string, currentPage: number) {
|
|||||||
message_body: {
|
message_body: {
|
||||||
file_id: fileId,
|
file_id: fileId,
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
|
check_similar_first: true,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -151,6 +114,7 @@ export async function getSearchStats() {
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let parsed = await response.json();
|
let parsed = await response.json();
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export async function createSession(token: string, email: string) {
|
|||||||
});
|
});
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
|
|
||||||
cookieStore.set('session', session, {
|
cookieStore.set('session_nc_db', session, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
expires: expiresAt,
|
expires: expiresAt,
|
||||||
@@ -48,7 +48,7 @@ export async function createSession(token: string, email: string) {
|
|||||||
|
|
||||||
export async function updateSession(newToken: string) {
|
export async function updateSession(newToken: string) {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const session = cookieStore.get('session')?.value;
|
const session = cookieStore.get('session_nc_db')?.value;
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return null;
|
return null;
|
||||||
@@ -68,7 +68,7 @@ export async function updateSession(newToken: string) {
|
|||||||
|
|
||||||
const updatedSession = await encrypt(updatedPayload);
|
const updatedSession = await encrypt(updatedPayload);
|
||||||
|
|
||||||
cookieStore.set('session', updatedSession, {
|
cookieStore.set('session_nc_db', updatedSession, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: false,
|
secure: false,
|
||||||
expires: updatedPayload.expiresAt,
|
expires: updatedPayload.expiresAt,
|
||||||
@@ -79,7 +79,7 @@ export async function updateSession(newToken: string) {
|
|||||||
|
|
||||||
export async function getSessionData(type: 'token' | 'email'): Promise<string | null> {
|
export async function getSessionData(type: 'token' | 'email'): Promise<string | null> {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const sessionCookie = cookieStore.get('session')?.value;
|
const sessionCookie = cookieStore.get('session_nc_db')?.value;
|
||||||
|
|
||||||
if (!sessionCookie) {
|
if (!sessionCookie) {
|
||||||
return null;
|
return null;
|
||||||
@@ -99,7 +99,7 @@ export async function deleteSession() {
|
|||||||
console.log('delete session')
|
console.log('delete session')
|
||||||
try {
|
try {
|
||||||
const cookieStore = await cookies()
|
const cookieStore = await cookies()
|
||||||
cookieStore.delete('session');
|
cookieStore.delete('session_nc_db');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -0,0 +1,278 @@
|
|||||||
|
'use server';
|
||||||
|
|
||||||
|
import { getSessionData } from '@/app/actions/session';
|
||||||
|
import { API_BASE_URL } from '@/app/actions/definitions';
|
||||||
|
import { headers } from 'next/headers';
|
||||||
|
|
||||||
|
export async function requestFileWithTracking(fileId: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
const headersList = await headers();
|
||||||
|
const forwardedFor = headersList.get('x-forwarded-for');
|
||||||
|
const realIp = headersList.get('x-real-ip');
|
||||||
|
const ip = forwardedFor?.split(',')[0] || realIp || 'unknown';
|
||||||
|
const userAgent = headersList.get('user-agent') || 'unknown';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/file/link`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
file_id: fileId,
|
||||||
|
ip: ip,
|
||||||
|
user_agent: userAgent
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
|
||||||
|
if (contentType?.includes('application/json')) {
|
||||||
|
const data = await response.json();
|
||||||
|
return data.message_body;
|
||||||
|
} else {
|
||||||
|
const blob = await response.blob();
|
||||||
|
return URL.createObjectURL(blob);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requestFileAsBuffer(fileId: string): Promise<ArrayBuffer | null> {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
const headersList = await headers();
|
||||||
|
const forwardedFor = headersList.get('x-forwarded-for');
|
||||||
|
const realIp = headersList.get('x-real-ip');
|
||||||
|
const ip = forwardedFor?.split(',')[0] || realIp || 'unknown';
|
||||||
|
const userAgent = headersList.get('user-agent') || 'unknown';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/file/link`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
file_id: fileId,
|
||||||
|
ip: ip,
|
||||||
|
user_agent: userAgent
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (contentType?.includes('application/json')) {
|
||||||
|
const data = await response.json();
|
||||||
|
const fileUrl = data.message_body;
|
||||||
|
const fileResponse = await fetch(fileUrl);
|
||||||
|
if (!fileResponse.ok) {
|
||||||
|
throw new Error(`Failed to fetch file: ${fileResponse.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await fileResponse.arrayBuffer();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await response.arrayBuffer();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchDocumentInfo(fileId: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 20005,
|
||||||
|
message_body: {
|
||||||
|
file_id: fileId,
|
||||||
|
action: 'file_info'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function filePermisionChange(fileId: string, permissions: boolean) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 20005,
|
||||||
|
message_body: {
|
||||||
|
file_id: fileId,
|
||||||
|
action: 'change_permission',
|
||||||
|
permissions: {
|
||||||
|
DOWNLOAD: permissions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchHistoryView(
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
locale: string,
|
||||||
|
sortDirection?: 'asc' | 'desc',
|
||||||
|
sortBy?: string
|
||||||
|
) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30028,
|
||||||
|
message_body: {
|
||||||
|
action: 'history_view',
|
||||||
|
page: page,
|
||||||
|
size: size,
|
||||||
|
locale: locale,
|
||||||
|
sort_direction: sortDirection,
|
||||||
|
sort_by: sortBy
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchTrackingStats() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30028,
|
||||||
|
message_body: {
|
||||||
|
action: 'view_stats'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadTrackingFile(fileId: string, fileName?: string) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/files/public-download/${fileId}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw 'failed-to-download-file';
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrayBuffer = await response.arrayBuffer()
|
||||||
|
const base64 = Buffer.from(arrayBuffer).toString('base64')
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: base64,
|
||||||
|
contentType: response.headers.get('content-type') || 'application/octet-stream',
|
||||||
|
fileName: fileName ? fileName : 'fileName'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
'use server'
|
'use server'
|
||||||
|
|
||||||
import { getSessionData } from '@/app/actions/session';
|
import { getSessionData } from '@/app/actions/session';
|
||||||
import { API_BASE_URL, createComplaintSchema, createCaseSchema } from '@/app/actions/definitions';
|
import { API_BASE_URL, createComplaintSchema, createClaimSchema } from '@/app/actions/definitions';
|
||||||
|
|
||||||
export async function getViolationSearchStatus() {
|
export async function getViolationSearchStatus() {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
@@ -44,7 +44,6 @@ export async function getViolationSearchStatus() {
|
|||||||
|
|
||||||
export async function startGlobalMonitoring(monitoringType: 'monitoring' | 'all_files') {
|
export async function startGlobalMonitoring(monitoringType: 'monitoring' | 'all_files') {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
console.log('startGlobalMonitoring');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/global-search/start`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/global-search/start`, {
|
||||||
@@ -67,7 +66,6 @@ export async function startGlobalMonitoring(monitoringType: 'monitoring' | 'all_
|
|||||||
totalFiles: number
|
totalFiles: number
|
||||||
} = await response.json();
|
} = await response.json();
|
||||||
|
|
||||||
console.log(parsed);
|
|
||||||
if (parsed?.status === 'ACCEPTED') {
|
if (parsed?.status === 'ACCEPTED') {
|
||||||
return parsed
|
return parsed
|
||||||
} else {
|
} else {
|
||||||
@@ -82,24 +80,39 @@ export async function startGlobalMonitoring(monitoringType: 'monitoring' | 'all_
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getViolationFilesArray() {
|
export async function getViolationFilesArray(props: {
|
||||||
|
page?: number,
|
||||||
|
size?: number,
|
||||||
|
start_date?: string,
|
||||||
|
end_date?: string,
|
||||||
|
file_name?: string
|
||||||
|
}) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
const { page, size, start_date, end_date, file_name } = props;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const body: Record<string, any> = {};
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/global-search/violation-summary`, {
|
if (page !== undefined) body.page = page;
|
||||||
method: 'GET',
|
if (size !== undefined) body.size = size;
|
||||||
|
if (start_date !== undefined) body.start_date = start_date;
|
||||||
|
if (end_date !== undefined) body.end_date = end_date;
|
||||||
|
if (file_name !== undefined) body.file_name = file_name;
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/global-search/violation-summary-with-files`, {
|
||||||
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let parsed = await response.json();
|
let parsed = await response.json();
|
||||||
|
|
||||||
if (parsed.length) {
|
if (parsed.content.length) {
|
||||||
return parsed;
|
return parsed;
|
||||||
} else {
|
} else {
|
||||||
throw parsed;
|
throw parsed;
|
||||||
@@ -113,42 +126,7 @@ export async function getViolationFilesArray() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* статистика нурешений */
|
export async function getFileMatches(fileId: string, page: number, status?: string, violationId?: string, startDate?: string, endDate?: string) {
|
||||||
export async function fetchViolationStats() {
|
|
||||||
const token = await getSessionData('token');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
version: 1,
|
|
||||||
msg_id: 30010,
|
|
||||||
message_body: {
|
|
||||||
token: token
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
let parsed = await response.json();
|
|
||||||
if (parsed) {
|
|
||||||
return parsed;
|
|
||||||
} else {
|
|
||||||
throw null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw (`${response.status}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getFileViolations(fileId: string, page: number, status?: string) {
|
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -159,16 +137,19 @@ export async function getFileViolations(fileId: string, page: number, status?: s
|
|||||||
msg_id: 30009,
|
msg_id: 30009,
|
||||||
message_body: {
|
message_body: {
|
||||||
file_id: fileId,
|
file_id: fileId,
|
||||||
page: page,
|
page: (page > 0 ? page - 1 : 0),
|
||||||
size: 5,
|
size: 5,
|
||||||
sort_direction: "desc",
|
sort_direction: 'desc',
|
||||||
token: token,
|
status: status,
|
||||||
status: status
|
...(violationId && { action: 'getByViolationId' }),
|
||||||
|
...(violationId && { violation_id: violationId }),
|
||||||
|
...((startDate && endDate) && { start_date: startDate, end_date: endDate })
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -201,13 +182,13 @@ export async function fetchViolationAnalyticStatistic(group: 'domain' | 'tld') {
|
|||||||
message_body: {
|
message_body: {
|
||||||
group_by: group,
|
group_by: group,
|
||||||
action: "group",
|
action: "group",
|
||||||
token: token,
|
|
||||||
sort_direction: 'desc'
|
sort_direction: 'desc'
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -227,7 +208,7 @@ export async function fetchViolationAnalyticStatistic(group: 'domain' | 'tld') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchViolationStatistic() {
|
export async function fetchViolationStatistic(id?: string) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -237,12 +218,13 @@ export async function fetchViolationStatistic() {
|
|||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 30010,
|
msg_id: 30010,
|
||||||
message_body: {
|
message_body: {
|
||||||
token: token,
|
...(id && { file_id: id })
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -262,6 +244,39 @@ export async function fetchViolationStatistic() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchViolationFinalSummaryStatistic() {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 1,
|
||||||
|
msg_id: 30023
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
let parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed?.message_code === 0) {
|
||||||
|
return parsed?.message_body;
|
||||||
|
} else {
|
||||||
|
throw null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw (`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface complainBody {
|
interface complainBody {
|
||||||
previousText: string,
|
previousText: string,
|
||||||
violationID: string | null,
|
violationID: string | null,
|
||||||
@@ -274,6 +289,7 @@ export async function createComplaint(
|
|||||||
formData: FormData
|
formData: FormData
|
||||||
): Promise<complainBody> {
|
): Promise<complainBody> {
|
||||||
const email = await getSessionData('email');
|
const email = await getSessionData('email');
|
||||||
|
const token = await getSessionData('token');
|
||||||
const violationId = formData.get('violationId') as string || '';
|
const violationId = formData.get('violationId') as string || '';
|
||||||
const textArea = formData.get('note') as string || '';
|
const textArea = formData.get('note') as string || '';
|
||||||
|
|
||||||
@@ -317,7 +333,8 @@ export async function createComplaint(
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -364,6 +381,7 @@ export async function createComplaint(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchComplainInfo(id: number) {
|
export async function fetchComplainInfo(id: number) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
@@ -378,7 +396,8 @@ export async function fetchComplainInfo(id: number) {
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -402,7 +421,7 @@ export async function fetchComplainInfo(id: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MatchStatus = 'NEW' | 'SHOWED' | 'LEGAL_IN_WORK' | 'COMPLAINT_IN_WORK' | 'COMPLAINT_AND_LEGAL_IN_WORK' | 'AUTHORIZED_USE';
|
export type MatchStatus = 'NEW' | 'SHOWED' | 'LEGAL_IN_WORK' | 'COMPLAINT_IN_WORK' | 'COMPLAINT_AND_LEGAL_IN_WORK' | 'AUTHORIZED_USE' | 'NOT_OWNER_FILE';
|
||||||
|
|
||||||
export async function updateMatchStatus(id: number, status: MatchStatus) {
|
export async function updateMatchStatus(id: number, status: MatchStatus) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
@@ -416,13 +435,13 @@ export async function updateMatchStatus(id: number, status: MatchStatus) {
|
|||||||
message_body: {
|
message_body: {
|
||||||
action: 'updateStatus',
|
action: 'updateStatus',
|
||||||
violation_id: id,
|
violation_id: id,
|
||||||
status: status,
|
status: status
|
||||||
token: token
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -442,28 +461,18 @@ export async function updateMatchStatus(id: number, status: MatchStatus) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchCaseInfo(id: number) {
|
export async function fetchClaimInfo(id: number) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
/* body: JSON.stringify({
|
|
||||||
version: 1,
|
|
||||||
msg_id: 30017,
|
|
||||||
message_body: {
|
|
||||||
action: 'get_byId',
|
|
||||||
token: token,
|
|
||||||
id: id
|
|
||||||
}
|
|
||||||
}), */
|
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 30017,
|
msg_id: 30017,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: 'get_all',
|
action: 'get_all',
|
||||||
token: token,
|
pageSize: 1,
|
||||||
pageSize: 10,
|
|
||||||
pageNumber: 0,
|
pageNumber: 0,
|
||||||
sortBy: "createdAt",
|
sortBy: "createdAt",
|
||||||
sortDir: "desc",
|
sortDir: "desc",
|
||||||
@@ -472,7 +481,8 @@ export async function fetchCaseInfo(id: number) {
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -503,12 +513,13 @@ export async function fetchCaseInfo(id: number) {
|
|||||||
interface caseBody {
|
interface caseBody {
|
||||||
previousText: string,
|
previousText: string,
|
||||||
previousCaseTitle: string,
|
previousCaseTitle: string,
|
||||||
|
previousAmount: string,
|
||||||
violationID: string | null,
|
violationID: string | null,
|
||||||
success: boolean,
|
success: boolean,
|
||||||
errorMessage?: Record<string, string> | null
|
errorMessage?: Record<string, string> | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createCase(
|
export async function createClaim(
|
||||||
state: caseBody | undefined,
|
state: caseBody | undefined,
|
||||||
formData: FormData
|
formData: FormData
|
||||||
): Promise<caseBody> {
|
): Promise<caseBody> {
|
||||||
@@ -519,7 +530,7 @@ export async function createCase(
|
|||||||
const caseTitle = formData.get('caseTitle') as string || '';
|
const caseTitle = formData.get('caseTitle') as string || '';
|
||||||
const amount = formData.get('amount') as string || '';
|
const amount = formData.get('amount') as string || '';
|
||||||
|
|
||||||
const validatedFields = createCaseSchema.safeParse({
|
const validatedFields = createClaimSchema.safeParse({
|
||||||
textArea,
|
textArea,
|
||||||
caseTitle
|
caseTitle
|
||||||
});
|
});
|
||||||
@@ -541,6 +552,7 @@ export async function createCase(
|
|||||||
violationID: violationId,
|
violationID: violationId,
|
||||||
previousText: textArea,
|
previousText: textArea,
|
||||||
previousCaseTitle: caseTitle,
|
previousCaseTitle: caseTitle,
|
||||||
|
previousAmount: amount,
|
||||||
success: false,
|
success: false,
|
||||||
errorMessage: errors
|
errorMessage: errors
|
||||||
}
|
}
|
||||||
@@ -557,7 +569,6 @@ export async function createCase(
|
|||||||
violation_id: violationId,
|
violation_id: violationId,
|
||||||
description: textArea,
|
description: textArea,
|
||||||
email: email,
|
email: email,
|
||||||
token: token,
|
|
||||||
name: caseTitle,
|
name: caseTitle,
|
||||||
priority: 'HIGH',
|
priority: 'HIGH',
|
||||||
amount: Number(amount)
|
amount: Number(amount)
|
||||||
@@ -565,7 +576,8 @@ export async function createCase(
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -577,6 +589,7 @@ export async function createCase(
|
|||||||
violationID: violationId,
|
violationID: violationId,
|
||||||
previousCaseTitle: caseTitle,
|
previousCaseTitle: caseTitle,
|
||||||
previousText: textArea,
|
previousText: textArea,
|
||||||
|
previousAmount: amount,
|
||||||
success: true
|
success: true
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
@@ -597,6 +610,7 @@ export async function createCase(
|
|||||||
violationID: violationId,
|
violationID: violationId,
|
||||||
previousText: textArea,
|
previousText: textArea,
|
||||||
previousCaseTitle: caseTitle,
|
previousCaseTitle: caseTitle,
|
||||||
|
previousAmount: amount,
|
||||||
success: false,
|
success: false,
|
||||||
errorMessage: errors
|
errorMessage: errors
|
||||||
}
|
}
|
||||||
@@ -608,14 +622,23 @@ export async function createCase(
|
|||||||
violationID: violationId,
|
violationID: violationId,
|
||||||
previousText: textArea,
|
previousText: textArea,
|
||||||
previousCaseTitle: caseTitle,
|
previousCaseTitle: caseTitle,
|
||||||
|
previousAmount: amount,
|
||||||
success: false,
|
success: false,
|
||||||
errorMessage: errors
|
errorMessage: errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchLastCase() {
|
export interface ComplaintsCaseQueryParams {
|
||||||
|
page?: number,
|
||||||
|
size?: number,
|
||||||
|
sort_by?: string,
|
||||||
|
sort_direction?: 'desc' | 'asc'
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchLastClaims(params: ComplaintsCaseQueryParams) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
const { page, size, sort_by, sort_direction } = params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
@@ -625,19 +648,24 @@ export async function fetchLastCase() {
|
|||||||
msg_id: 30017,
|
msg_id: 30017,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: "get_all",
|
action: "get_all",
|
||||||
token: token
|
pageNumber: page,
|
||||||
|
size,
|
||||||
|
sort_by,
|
||||||
|
sort_direction
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let parsed = await response.json();
|
let parsed = await response.json();
|
||||||
if (parsed) {
|
|
||||||
return parsed;
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
} else {
|
} else {
|
||||||
throw null;
|
throw null;
|
||||||
}
|
}
|
||||||
@@ -649,30 +677,36 @@ export async function fetchLastCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchLastComplaint() {
|
export async function fetchLastComplaint(params: ComplaintsCaseQueryParams) {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
|
const { page, size, sort_by, sort_direction } = params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
msg_id: 30017,
|
msg_id: 30013,
|
||||||
message_body: {
|
message_body: {
|
||||||
action: "get_all",
|
action: "get_all",
|
||||||
token: token
|
page,
|
||||||
|
size,
|
||||||
|
sort_by,
|
||||||
|
sort_direction
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let parsed = await response.json();
|
let parsed = await response.json();
|
||||||
if (parsed) {
|
|
||||||
return parsed;
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
} else {
|
} else {
|
||||||
throw null;
|
throw null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ const checkout = new YooCheckout({ shopId: '1276731', secretKey: 'test_0Yns_0NHV
|
|||||||
|
|
||||||
export async function makePaymentOperation(email: string, tariffId: number, operationUuid: string, operationType: 'TARIFF' | 'TOKEN') {
|
export async function makePaymentOperation(email: string, tariffId: number, operationUuid: string, operationType: 'TARIFF' | 'TOKEN') {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
formData.append('email', email);
|
formData.append('email', email);
|
||||||
formData.append('tariffId', tariffId.toString());
|
formData.append('tariffId', tariffId.toString());
|
||||||
@@ -17,7 +18,10 @@ export async function makePaymentOperation(email: string, tariffId: number, oper
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/payments/create`, {
|
const response = await fetch(`${API_BASE_URL}/api/payments/create`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
@@ -79,6 +83,7 @@ export async function yooKasaCreatePayment(amount: number, tariff: number, opera
|
|||||||
|
|
||||||
export async function fetchPaymentsHistory() {
|
export async function fetchPaymentsHistory() {
|
||||||
const email = await getSessionData('email');
|
const email = await getSessionData('email');
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
const response = await fetch(`${API_BASE_URL}/api/v1/data`, {
|
||||||
@@ -93,7 +98,8 @@ export async function fetchPaymentsHistory() {
|
|||||||
}),
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
import { NextResponse } from 'next/server';
|
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
|
||||||
const { code } = await request.json();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('https://oauth.yandex.ru/token', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
||||||
body: new URLSearchParams({
|
|
||||||
grant_type: 'authorization_code',
|
|
||||||
code,
|
|
||||||
client_id: 'b356a7ceac0f4c50a3b434220f86bce0',
|
|
||||||
client_secret: '0800f7774f7d4e19ad4b434f10afd4ab',
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
if (data.access_token) {
|
|
||||||
const userInfo = await fetch('https://login.yandex.ru/info?format=json', {
|
|
||||||
headers: { 'Authorization': `OAuth ${data.access_token}` }
|
|
||||||
}).then(r => r.json());
|
|
||||||
|
|
||||||
console.log(userInfo);
|
|
||||||
|
|
||||||
// Тут мне нужно будет отправить сделать запрос в бек с userInfo и там уже редиректить на дешборд
|
|
||||||
|
|
||||||
return NextResponse.json({ success: true });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Token exchange failed:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ success: false }, { status: 400 });
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useTranslations } from 'next-intl'
|
||||||
|
|
||||||
|
interface NotificationTextHandlerProps {
|
||||||
|
notificationType: string
|
||||||
|
notificationMessage: string
|
||||||
|
notificationContext: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NotificationTextHandler({
|
||||||
|
notificationType,
|
||||||
|
notificationMessage,
|
||||||
|
notificationContext
|
||||||
|
}: NotificationTextHandlerProps) {
|
||||||
|
const t = useTranslations('Notifications');
|
||||||
|
|
||||||
|
const getNotificationText = () => {
|
||||||
|
if (!notificationType) {
|
||||||
|
return t.has(notificationMessage) ? t(notificationMessage) : notificationMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (notificationType) {
|
||||||
|
case 'USER_VERIFIED':
|
||||||
|
return t('user-verified', {
|
||||||
|
status: notificationContext?.status || t('not-specified'),
|
||||||
|
updateDate: notificationContext?.update_date || t('not-specified-date')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'MONITORING_RESULT':
|
||||||
|
return t('monitoring-result', {
|
||||||
|
message: notificationContext?.message || t('message-missing')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'COMPLAINT_STATUS_CHANGED':
|
||||||
|
return t('complaint-status-changed', {
|
||||||
|
newStatus: notificationContext?.new_status || t('not-specified'),
|
||||||
|
oldStatus: notificationContext?.old_status || t('not-specified'),
|
||||||
|
violation: notificationContext?.violation || t('not-specified'),
|
||||||
|
file: notificationContext?.file || t('not-specified')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'FILE_ADDED_TO_SYSTEM':
|
||||||
|
return t('file-added-to-system', {
|
||||||
|
status: notificationContext?.status || t('not-specified'),
|
||||||
|
fileName: notificationContext?.file_name || t('not-specified-file-name')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'FILE_MODERATION_EVENT':
|
||||||
|
return t('file-moderation-event', {
|
||||||
|
fileName: notificationContext?.file_name || t('not-specified-file-name'),
|
||||||
|
oldStatus: notificationContext?.old_status || t('not-specified'),
|
||||||
|
newStatus: notificationContext?.new_status || t('not-specified'),
|
||||||
|
comment: notificationContext?.comment || t('no-comment')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'PAYMENT_RESULT':
|
||||||
|
return t('payment-result', {
|
||||||
|
status: notificationContext?.status || t('not-specified'),
|
||||||
|
amount: notificationContext?.amount || '0'
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'REFERRAL_REGISTERED':
|
||||||
|
return t('referral-registered', {
|
||||||
|
referralUser: notificationContext?.referral_user || t('unknown-user')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'SEARCH_RESULT':
|
||||||
|
return t('search-result', {
|
||||||
|
startDate: notificationContext?.start_date || t('not-specified-date'),
|
||||||
|
endDate: notificationContext?.end_date || t('not-specified-date'),
|
||||||
|
searchType: notificationContext?.search_type || t('not-specified'),
|
||||||
|
count: notificationContext?.count || '0'
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'SEARCH_OPERATION_FAILED':
|
||||||
|
return t('search-operation-failed', {
|
||||||
|
status: notificationContext?.status || t('not-specified'),
|
||||||
|
errorMessage: notificationContext?.error_message || t('error-not-described'),
|
||||||
|
message: notificationContext?.message || t('message-missing'),
|
||||||
|
reason: notificationContext?.reason || t('reason-not-specified'),
|
||||||
|
nextRun: notificationContext?.next_run || t('not-scheduled')
|
||||||
|
});
|
||||||
|
|
||||||
|
case 'TOKEN_NOT_FOUND':
|
||||||
|
return t('token-not-found', {
|
||||||
|
currentTokens: notificationContext?.current_tokens || '0',
|
||||||
|
needTokens: notificationContext?.need_tokens || t('not-specified'),
|
||||||
|
message: notificationContext?.message || t('message-missing'),
|
||||||
|
nextRun: notificationContext?.next_run || t('not-scheduled')
|
||||||
|
});
|
||||||
|
|
||||||
|
default:
|
||||||
|
return notificationMessage || t('notification-without-text');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{getNotificationText()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { usePdf } from '@/app/contexts/PdfContext';
|
||||||
|
import { requestFileAsBuffer } from '@/app/actions/trackingActions';
|
||||||
|
|
||||||
|
interface PdfViewerProps {
|
||||||
|
fileId: string;
|
||||||
|
fileName?: string;
|
||||||
|
showDownload?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PdfViewer({ fileId, fileName = 'document.pdf', showDownload }: PdfViewerProps) {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
const [totalPages, setTotalPages] = useState(0);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [fullscreen, setFullscreen] = useState(false);
|
||||||
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const pdfDocumentRef = useRef<any>(null);
|
||||||
|
const { setPdfData } = usePdf();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let isMounted = true;
|
||||||
|
let objectUrl: string | null = null;
|
||||||
|
|
||||||
|
const loadAndDisplayPdf = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const pdfData = await requestFileAsBuffer(fileId);
|
||||||
|
|
||||||
|
if (!pdfData) {
|
||||||
|
throw new Error('Failed to load document');
|
||||||
|
}
|
||||||
|
|
||||||
|
//это нужно для скачивания так-как иначе pdfData теряется для скачивания
|
||||||
|
if (showDownload) {
|
||||||
|
const pdfDataForDownload = pdfData.slice(0);
|
||||||
|
setPdfData(pdfDataForDownload, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isMounted) return;
|
||||||
|
|
||||||
|
const pdfjs = await import('pdfjs-dist');
|
||||||
|
|
||||||
|
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
||||||
|
'pdfjs-dist/build/pdf.worker.mjs',
|
||||||
|
import.meta.url
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
const loadingTask = pdfjs.getDocument({
|
||||||
|
data: pdfData,
|
||||||
|
useSystemFonts: true,
|
||||||
|
disableFontFace: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pdfDocument = await loadingTask.promise;
|
||||||
|
|
||||||
|
if (!isMounted) return;
|
||||||
|
|
||||||
|
pdfDocumentRef.current = pdfDocument;
|
||||||
|
setTotalPages(pdfDocument.numPages);
|
||||||
|
setCurrentPage(1);
|
||||||
|
|
||||||
|
await renderPage(pdfDocument, 1);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error loading PDF:', err);
|
||||||
|
if (isMounted) {
|
||||||
|
setError('Не удалось загрузить документ');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (isMounted) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadAndDisplayPdf();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
isMounted = false;
|
||||||
|
if (pdfDocumentRef.current) {
|
||||||
|
pdfDocumentRef.current.destroy();
|
||||||
|
}
|
||||||
|
if (objectUrl) {
|
||||||
|
URL.revokeObjectURL(objectUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [fileId, setPdfData]);
|
||||||
|
|
||||||
|
const renderPage = async (pdfDocument: any, pageNum: number) => {
|
||||||
|
if (!canvasRef.current) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const page = await pdfDocument.getPage(pageNum);
|
||||||
|
|
||||||
|
const container = canvasRef.current.parentElement;
|
||||||
|
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const containerWidth = container.clientWidth || 800;
|
||||||
|
|
||||||
|
// Если ширина контейнера 0, откладываем рендер
|
||||||
|
if (containerWidth === 0) {
|
||||||
|
console.log('Container width is 0, waiting...');
|
||||||
|
setTimeout(() => renderPage(pdfDocument, pageNum), 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const originalViewport = page.getViewport({ scale: 1 });
|
||||||
|
const scale = containerWidth / originalViewport.width;
|
||||||
|
const viewport = page.getViewport({ scale: Math.min(scale, 1.5) });
|
||||||
|
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
|
||||||
|
const renderContext = {
|
||||||
|
canvasContext: context!,
|
||||||
|
viewport: viewport,
|
||||||
|
};
|
||||||
|
|
||||||
|
await page.render(renderContext).promise;
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error rendering page:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!pdfDocumentRef.current || loading) return;
|
||||||
|
|
||||||
|
const checkContainer = () => {
|
||||||
|
const container = canvasRef.current?.parentElement;
|
||||||
|
if (container && container.clientWidth > 0) {
|
||||||
|
renderPage(pdfDocumentRef.current, currentPage);
|
||||||
|
} else {
|
||||||
|
setTimeout(checkContainer, 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkContainer();
|
||||||
|
}, [pdfDocumentRef.current, loading]);
|
||||||
|
|
||||||
|
const goToPrevPage = async () => {
|
||||||
|
if (pdfDocumentRef.current && currentPage > 1) {
|
||||||
|
const newPage = currentPage - 1;
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
await renderPage(pdfDocumentRef.current, newPage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToNextPage = async () => {
|
||||||
|
if (pdfDocumentRef.current && currentPage < totalPages) {
|
||||||
|
const newPage = currentPage + 1;
|
||||||
|
setCurrentPage(newPage);
|
||||||
|
await renderPage(pdfDocumentRef.current, newPage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleFullscreen = () => {
|
||||||
|
if (!containerRef.current) return;
|
||||||
|
|
||||||
|
if (!document.fullscreenElement) {
|
||||||
|
containerRef.current.requestFullscreen();
|
||||||
|
setFullscreen(true);
|
||||||
|
} else {
|
||||||
|
document.exitFullscreen();
|
||||||
|
setFullscreen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleFullscreenChange = () => {
|
||||||
|
setFullscreen(!!document.fullscreenElement);
|
||||||
|
if (pdfDocumentRef.current && !document.fullscreenElement) {
|
||||||
|
setTimeout(() => renderPage(pdfDocumentRef.current, currentPage), 100);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
||||||
|
return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
||||||
|
}, [currentPage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
if (pdfDocumentRef.current && !loading) {
|
||||||
|
renderPage(pdfDocumentRef.current, currentPage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, [currentPage, loading]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="prev-wrap">
|
||||||
|
<div className="pdf-loading">Загрузка документа...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="prev-wrap">
|
||||||
|
<div className="pdf-error">{error}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className="pdf-container">
|
||||||
|
<div className="prev-wrap">
|
||||||
|
<canvas ref={canvasRef} className="pdf-canvas" />
|
||||||
|
</div>
|
||||||
|
{totalPages > 0 && (
|
||||||
|
<div className="document-controls">
|
||||||
|
<div className="document-controls__nav">
|
||||||
|
<button
|
||||||
|
className="document-controls__prev"
|
||||||
|
onClick={goToPrevPage}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
>
|
||||||
|
← Пред.
|
||||||
|
</button>
|
||||||
|
<span className="document-controls__current-page">
|
||||||
|
{currentPage} / {totalPages}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
className="document-controls__next"
|
||||||
|
onClick={goToNextPage}
|
||||||
|
disabled={currentPage === totalPages}
|
||||||
|
>
|
||||||
|
След. →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="document-controls__fullscreen"
|
||||||
|
onClick={toggleFullscreen}
|
||||||
|
>
|
||||||
|
{fullscreen ? 'Выйти' : 'На весь экран'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
interface CopyLinkButtonProps {
|
||||||
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CopyLinkButton({ className = 'btn btn-out', children = 'Скопировать ссылку' }: CopyLinkButtonProps) {
|
||||||
|
|
||||||
|
const copyCurrentPageLink = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(window.location.href);
|
||||||
|
toast.success('Ссылка скопирована');
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('Не удалось скопировать ссылку');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={className}
|
||||||
|
onClick={copyCurrentPageLink}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { downloadTrackingFile } from '@/app/actions/trackingActions';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
interface DownloadButtonProps {
|
||||||
|
fileId: string;
|
||||||
|
fileName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DownloadButton({ fileId, fileName }: DownloadButtonProps) {
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
try {
|
||||||
|
const result = await downloadTrackingFile(fileId, fileName);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw 'failed-to-download-file'
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteCharacters = atob(result.data);
|
||||||
|
const byteNumbers = new Array(byteCharacters.length);
|
||||||
|
for (let i = 0; i < byteCharacters.length; i++) {
|
||||||
|
byteNumbers[i] = byteCharacters.charCodeAt(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
const byteArray = new Uint8Array(byteNumbers);
|
||||||
|
const blob = new Blob([byteArray], { type: result.contentType });
|
||||||
|
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = result.fileName;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
document.body.removeChild(a);
|
||||||
|
toast.success(`${t('file-is-downloading')} - `);
|
||||||
|
} catch (error) {
|
||||||
|
toast.error(t('failed-to-download-file'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className="btn btn-out" onClick={handleDownload}>
|
||||||
|
Скачать документ
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo';
|
||||||
|
|
||||||
|
export function LoginUserBage() {
|
||||||
|
const { data: userData, isLoading, isError, error } = useUserProfile();
|
||||||
|
|
||||||
|
if (!userData?.fullName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link href="#" className="header-user">
|
||||||
|
{userData?.fullName}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
PaginationState
|
PaginationState
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconInfo, IconEye, IconCheck } from '@/app/ui/icons/icons';
|
import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter, IconFileDownload, IconInfo, IconEye, IconCheck, IconImageFile, IconLink } from '@/app/ui/icons/icons';
|
||||||
import { useTranslations, useLocale } from 'next-intl';
|
import { useTranslations, useLocale } from 'next-intl';
|
||||||
import DropDownList from '@/app/components/DropDownList';
|
import DropDownList from '@/app/components/DropDownList';
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
@@ -31,6 +31,9 @@ import { downloadFile } from '@/app/actions/fileEntity';
|
|||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { SelectedFilesAction } from '@/app/components/tanstak-table/SelectedFilesActions';
|
import { SelectedFilesAction } from '@/app/components/tanstak-table/SelectedFilesActions';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { filePermisionChange } from '@/app/actions/trackingActions';
|
||||||
|
import {FileAppealModalWindow} from '@/app/ui/modal-windows/file-appeal-modal-window';
|
||||||
|
|
||||||
export type FileItem = {
|
export type FileItem = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -39,11 +42,15 @@ export type FileItem = {
|
|||||||
violations?: number | undefined;
|
violations?: number | undefined;
|
||||||
size?: number | undefined;
|
size?: number | undefined;
|
||||||
uploadDate?: number;
|
uploadDate?: number;
|
||||||
status?: string;
|
status: string;
|
||||||
monitoring: string;
|
monitoring: string;
|
||||||
protectStatus: string;
|
protectStatus: string;
|
||||||
_original?: ApiFile;
|
_original?: ApiFile;
|
||||||
supportId: number;
|
supportId: number;
|
||||||
|
thumbnailFileUrl: string;
|
||||||
|
permissions: {
|
||||||
|
DOWNLOAD: boolean;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type ApiFile = {
|
type ApiFile = {
|
||||||
@@ -58,6 +65,10 @@ type ApiFile = {
|
|||||||
protectStatus: string;
|
protectStatus: string;
|
||||||
supportId: number;
|
supportId: number;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
|
thumbnailFileUrl: string;
|
||||||
|
permissions: {
|
||||||
|
DOWNLOAD: boolean;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type ApiResponse = {
|
type ApiResponse = {
|
||||||
@@ -73,7 +84,7 @@ const cutFileExtension = (fileName: string) => {
|
|||||||
return extension;
|
return extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
export default function TanstakFilesTable({ fileType, showFileLink }: { fileType: string, showFileLink?: boolean }) {
|
||||||
|
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
const [dateFilter, setDateFilter] = useState<string>('month');
|
const [dateFilter, setDateFilter] = useState<string>('month');
|
||||||
@@ -186,7 +197,11 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
monitoring: item.monitoring,
|
monitoring: item.monitoring,
|
||||||
protectStatus: item.protectStatus,
|
protectStatus: item.protectStatus,
|
||||||
supportId: item.supportId,
|
supportId: item.supportId,
|
||||||
_original: item
|
_original: item,
|
||||||
|
thumbnailFileUrl: item.thumbnailFileUrl,
|
||||||
|
permissions: {
|
||||||
|
DOWNLOAD: item.permissions.DOWNLOAD,
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -281,6 +296,32 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
});
|
});
|
||||||
}, [apiResponse]);
|
}, [apiResponse]);
|
||||||
|
|
||||||
|
const StatusBadge = ({ status, protectStatus }: { status: string, protectStatus: string }) => {
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case 'ACTIVE':
|
||||||
|
return <span className="table-item-status-badge protected">
|
||||||
|
{protectStatus ? t(protectStatus) : t('error')}
|
||||||
|
</span>;
|
||||||
|
case 'MODERATION':
|
||||||
|
return <span className="table-item-status-badge moderation">
|
||||||
|
{t('pending-moderation')}
|
||||||
|
</span>;
|
||||||
|
case 'BLOCKED':
|
||||||
|
return <span className="table-item-status-badge blocked">
|
||||||
|
{t('blocked')}
|
||||||
|
</span>;
|
||||||
|
case 'REMOVED':
|
||||||
|
return <span className="table-item-status-badge blocked">
|
||||||
|
{t('file-has-been-deleted')}
|
||||||
|
</span>;
|
||||||
|
default:
|
||||||
|
return <span className="table-item-status-badge protected">
|
||||||
|
{protectStatus ? t(protectStatus) : t('error')}
|
||||||
|
</span>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isAllSelected = useMemo(() => {
|
const isAllSelected = useMemo(() => {
|
||||||
if (!apiResponse?.items?.length) return false;
|
if (!apiResponse?.items?.length) return false;
|
||||||
const allIds = new Set(apiResponse.items.map(item => item.id));
|
const allIds = new Set(apiResponse.items.map(item => item.id));
|
||||||
@@ -392,16 +433,32 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
),
|
),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="flex items-center space-x-2 table-item table-item-file-name">
|
<div className="flex items-center space-x-2 table-item table-item-file-name">
|
||||||
|
{row.original.fileType === 'image' && row.original?.thumbnailFileUrl.length !== 0 ? (
|
||||||
|
<span
|
||||||
|
className="table-item-file-name-image-wrapper"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
sizes="40px"
|
||||||
|
src={row.original?.thumbnailFileUrl}
|
||||||
|
alt={row.original?.fileName}
|
||||||
|
unoptimized
|
||||||
|
fill
|
||||||
|
onError={(e) => {
|
||||||
|
const target = e.target as HTMLImageElement;
|
||||||
|
target.src = '/images/no-image.png';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
<FileTypeIcon type={row.original.fileType} />
|
<FileTypeIcon type={row.original.fileType} />
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<div className="font-medium w-full truncate" title={row.original.fileName}>
|
<div className="font-medium w-full truncate" title={row.original.fileName}>
|
||||||
<span className="font-semibold">
|
<span className="font-semibold">
|
||||||
{cutFileName(row.original.fileName)}
|
{cutFileName(row.original.fileName)}
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<span className="table-item-extension">{cutFileExtension(row.original.fileName)}</span> <span className="table-item-protected">
|
<span className="table-item-extension">{cutFileExtension(row.original.fileName)}</span> <StatusBadge status={row.original.status} protectStatus={row.original.protectStatus} />
|
||||||
{row.original?.protectStatus ? t(row.original?.protectStatus) : t('error')}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -531,6 +588,33 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'downloadPermision',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column">
|
||||||
|
<span>
|
||||||
|
Можно скачать по ссылке
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className="text-center font-semibold table-item">
|
||||||
|
<button
|
||||||
|
onClick={() => changeDownloadPermition(row.original.id, row.original.permissions.DOWNLOAD)}
|
||||||
|
disabled={isFileLoading}
|
||||||
|
className={`toggle-switch ${row.original.permissions?.DOWNLOAD ? 'active' : 'inactive'}`}
|
||||||
|
title={'change-permission'}
|
||||||
|
>
|
||||||
|
<span className="toggle-slider"></span>
|
||||||
|
<span className="toggle-label">
|
||||||
|
{row.original.permissions?.DOWNLOAD ? 'Да' : 'Нет'}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
header: () => {
|
header: () => {
|
||||||
@@ -542,6 +626,22 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
|
{(showFileLink && row.original.fileType === 'document') ? (
|
||||||
|
<div className="actions-group">
|
||||||
|
<Link
|
||||||
|
href={`/watch-doc?docid=${row.original.id}`}
|
||||||
|
className="bg-indigo-500 hover:bg-indigo-600"
|
||||||
|
title={t('view')}
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
<IconLink />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* "BLOCKED" "REMOVED" */}
|
||||||
|
{(row.original.status !== 'BLOCKED' && row.original.status !== 'REMOVED') ? (
|
||||||
|
<>
|
||||||
<div className="actions-group">
|
<div className="actions-group">
|
||||||
<Link
|
<Link
|
||||||
href={`/pages/file/${row.original.id}`}
|
href={`/pages/file/${row.original.id}`}
|
||||||
@@ -572,7 +672,22 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
<IconInfo />
|
<IconInfo />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="actions-group">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
handleAppeal(row.original)
|
||||||
|
}}
|
||||||
|
className="table-action-appeal"
|
||||||
|
title={t('file-an-appeal')}
|
||||||
|
>
|
||||||
|
<IconInfo />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
@@ -593,6 +708,17 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
setOpenWindow(true);
|
setOpenWindow(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAppeal = async (file: FileItem) => {
|
||||||
|
console.log(file);
|
||||||
|
|
||||||
|
setOpenWindowChildren(() => {
|
||||||
|
return (
|
||||||
|
<FileAppealModalWindow fileId={file.id} setWindowClose={setOpenWindow} setWindowChildren={setOpenWindowChildren} />
|
||||||
|
)
|
||||||
|
})
|
||||||
|
setOpenWindow(true);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDownload = async (file: FileItem) => {
|
const handleDownload = async (file: FileItem) => {
|
||||||
setIsFileLoading(true)
|
setIsFileLoading(true)
|
||||||
|
|
||||||
@@ -627,6 +753,16 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const changeDownloadPermition = async (fileId: string, permission: boolean) => {
|
||||||
|
const response = await filePermisionChange(fileId, !permission);
|
||||||
|
if (response) {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['userFilesData'] });
|
||||||
|
toast.success('Разрешение на скачивание изменено.');
|
||||||
|
} else {
|
||||||
|
toast.warning('Не удалось изменить разрешение на скачивание.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Фильтрация по типу файла и дате
|
// Фильтрация по типу файла и дате
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
let result = tableData;
|
let result = tableData;
|
||||||
@@ -703,6 +839,10 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
state: {
|
state: {
|
||||||
sorting,
|
sorting,
|
||||||
pagination,
|
pagination,
|
||||||
|
columnVisibility: {
|
||||||
|
monitoring: showFileLink ? false : true,
|
||||||
|
downloadPermision: showFileLink ? true : false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
pageCount: totalPages,
|
pageCount: totalPages,
|
||||||
manualPagination: true,
|
manualPagination: true,
|
||||||
@@ -753,6 +893,8 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
return t('protected-video')
|
return t('protected-video')
|
||||||
case 'audio':
|
case 'audio':
|
||||||
return t('protected-audio')
|
return t('protected-audio')
|
||||||
|
case 'document':
|
||||||
|
return t('protected-document')
|
||||||
default:
|
default:
|
||||||
return t('table-description')
|
return t('table-description')
|
||||||
}
|
}
|
||||||
@@ -878,6 +1020,21 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
))}
|
))}
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className={`tanstak-table-body ${isFetching ? 'loading' : ''}`}>
|
<tbody className={`tanstak-table-body ${isFetching ? 'loading' : ''}`}>
|
||||||
|
{isLoading && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={columns.length} className="text-center relative h-12.5">
|
||||||
|
<div
|
||||||
|
className="loading-animation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="global-spinner"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
{table.getRowModel().rows.length > 0 ? (
|
{table.getRowModel().rows.length > 0 ? (
|
||||||
table.getRowModel().rows.map(row => (
|
table.getRowModel().rows.map(row => (
|
||||||
<tr key={row.id}>
|
<tr key={row.id}>
|
||||||
@@ -892,9 +1049,11 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
|
{!isLoading && (
|
||||||
<td colSpan={columns.length} className="text-center">
|
<td colSpan={columns.length} className="text-center">
|
||||||
{t('no-data-for-selected-filters')}
|
{t('no-data-for-selected-filters')}
|
||||||
</td>
|
</td>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -908,10 +1067,7 @@ export default function TanstakFilesTable({ fileType }: { fileType: string }) {
|
|||||||
{t('page')}{' '}
|
{t('page')}{' '}
|
||||||
<strong>
|
<strong>
|
||||||
{pagination.pageIndex + 1} {t('out-of')} {totalPages || 1}
|
{pagination.pageIndex + 1} {t('out-of')} {totalPages || 1}
|
||||||
</strong>
|
</strong> | {t('total-files')}: {totalItems}
|
||||||
</span>
|
|
||||||
<span className="pagination-info-files">
|
|
||||||
| {t('shown')} {tableData.length} {t('out-of')} {totalItems} {pluralizeFiles(totalItems)}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { createContext, useContext, useRef, ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface PdfContextType {
|
||||||
|
downloadPdf: () => Promise<void>;
|
||||||
|
setPdfData: (data: ArrayBuffer | null, fileName: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PdfContext = createContext<PdfContextType | null>(null);
|
||||||
|
|
||||||
|
export function PdfProvider({ children }: { children: ReactNode }) {
|
||||||
|
const pdfDataRef = useRef<ArrayBuffer | null>(null);
|
||||||
|
const fileNameRef = useRef<string>('');
|
||||||
|
|
||||||
|
const setPdfData = (data: ArrayBuffer | null, fileName: string) => {
|
||||||
|
pdfDataRef.current = data;
|
||||||
|
fileNameRef.current = fileName;
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadPdf = async () => {
|
||||||
|
if (!pdfDataRef.current) {
|
||||||
|
console.log('No PDF data available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blob = new Blob([pdfDataRef.current], { type: 'application/pdf' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = fileNameRef.current || 'document.pdf';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error downloading PDF:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PdfContext.Provider value={{ downloadPdf, setPdfData }}>
|
||||||
|
{children}
|
||||||
|
</PdfContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePdf() {
|
||||||
|
const context = useContext(PdfContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('usePdf must be used within PdfProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { getFileViolations } from '@/app/actions/violationActions';
|
import { getFileMatches } from '@/app/actions/violationActions';
|
||||||
|
|
||||||
export interface ViolationFileDetail {
|
export interface ViolationFileDetail {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -10,23 +10,25 @@ export interface ViolationFileDetail {
|
|||||||
status: string;
|
status: string;
|
||||||
created_date: string;
|
created_date: string;
|
||||||
file_id: string;
|
file_id: string;
|
||||||
|
country: string;
|
||||||
|
country_code: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ViolationFile {
|
interface ViolationFile {
|
||||||
violations: ViolationFileDetail[],
|
violations: ViolationFileDetail[];
|
||||||
total_elements: number,
|
total_elements: number;
|
||||||
total_pages: number,
|
total_pages: number;
|
||||||
current_page: number,
|
current_page: number;
|
||||||
page_size: number,
|
page_size: number;
|
||||||
has_next: boolean,
|
has_next: boolean;
|
||||||
has_previous: boolean
|
has_previous: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useFileViolations = (id: string, page: number, status?: string) => {
|
export const useFileViolations = (id: string, page: number, status?: string, violationId?: string, startDate?: string, endDate?: string,) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['fileViolations', id, page, status],
|
queryKey: ['fileViolations', id, page, status, violationId, startDate, endDate],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return getFileViolations(id, page, status)
|
return getFileMatches(id, page, status, violationId, startDate, endDate)
|
||||||
},
|
},
|
||||||
select: (data: ViolationFile | null) => {
|
select: (data: ViolationFile | null) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchHistoryView } from '@/app/actions/trackingActions';
|
||||||
|
|
||||||
|
export interface HistoryViewItem {
|
||||||
|
city: string;
|
||||||
|
country: string;
|
||||||
|
viewer: string;
|
||||||
|
document: string;
|
||||||
|
view_date: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UseHistoryView {
|
||||||
|
documentsCount: number;
|
||||||
|
history: HistoryViewItem[];
|
||||||
|
pageNumber: number;
|
||||||
|
pageSize: number;
|
||||||
|
totalElements: number;
|
||||||
|
totalPages: number;
|
||||||
|
uniqueCityCount: number;
|
||||||
|
uniqueCountryCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useHistoryView = (
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
locale: string,
|
||||||
|
sortDirection?: 'asc' | 'desc',
|
||||||
|
sortBy?: string
|
||||||
|
) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['violationStatistic', page, size, locale, sortDirection, sortBy],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchHistoryView(page, size, locale, sortDirection, sortBy)
|
||||||
|
},
|
||||||
|
select: (data: UseHistoryView | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false,
|
||||||
|
placeholderData: (previousData) => previousData // для оптимистичных обновлений
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,15 +1,37 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { fetchLastCase } from '@/app/actions/violationActions';
|
import { fetchLastClaims, ComplaintsCaseQueryParams } from '@/app/actions/violationActions';
|
||||||
|
|
||||||
|
export interface Case {
|
||||||
|
amount: number;
|
||||||
|
content: null | string;
|
||||||
|
created_at: string;
|
||||||
|
description: string;
|
||||||
|
id: number;
|
||||||
|
lawyer: null | string;
|
||||||
|
name: string;
|
||||||
|
page_number: number;
|
||||||
|
page_size: number;
|
||||||
|
priority: "HIGH" | "MEDIUM" | "LOW";
|
||||||
|
total_elements: number;
|
||||||
|
total_pages: number;
|
||||||
|
type: "ACTIVE" | "INACTIVE" | string;
|
||||||
|
updated_at: string;
|
||||||
|
violation_id: number;
|
||||||
|
file_id: string;
|
||||||
|
}
|
||||||
export interface useLastCase {
|
export interface useLastCase {
|
||||||
test: number,
|
content: Case[],
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
totalElements: number,
|
||||||
|
totalPages: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useLastCases = () => {
|
export const useLastCases = (params: ComplaintsCaseQueryParams) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['lastCases'],
|
queryKey: ['lastCases', params.page, params.size, params.sort_by, params.sort_direction],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return fetchLastCase()
|
return fetchLastClaims(params)
|
||||||
},
|
},
|
||||||
select: (data: useLastCase | null) => {
|
select: (data: useLastCase | null) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@@ -17,6 +39,7 @@ export const useLastCases = () => {
|
|||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
retry: false
|
retry: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -1,15 +1,30 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { fetchLastComplaint } from '@/app/actions/violationActions';
|
import { fetchLastComplaint, ComplaintsCaseQueryParams } from '@/app/actions/violationActions';
|
||||||
|
|
||||||
|
export interface Complaint {
|
||||||
|
complaint_text: string;
|
||||||
|
created_at: string;
|
||||||
|
email: string;
|
||||||
|
id: number;
|
||||||
|
not_moderated: boolean;
|
||||||
|
status: "CREATED" | string;
|
||||||
|
updated_at: string;
|
||||||
|
violation_id: number;
|
||||||
|
file_id: string;
|
||||||
|
}
|
||||||
export interface useLastComplaint {
|
export interface useLastComplaint {
|
||||||
test: number,
|
content: Complaint[],
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
totalElements: number,
|
||||||
|
totalPages: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useLastComplaints = () => {
|
export const useLastComplaints = (params: ComplaintsCaseQueryParams) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['lastComplaints'],
|
queryKey: ['lastComplaints', params.page, params.size, params.sort_by, params.sort_direction],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return fetchLastComplaint()
|
return fetchLastComplaint(params)
|
||||||
},
|
},
|
||||||
select: (data: useLastComplaint | null) => {
|
select: (data: useLastComplaint | null) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
@@ -17,6 +32,7 @@ export const useLastComplaints = () => {
|
|||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
retry: false
|
retry: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchMonitoringInfo } from '@/app/actions/monitoringActions';
|
||||||
|
|
||||||
|
interface MonitoringCount {
|
||||||
|
day: number;
|
||||||
|
month: number;
|
||||||
|
weekly: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMonitoringCount = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['monitoringCount'],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchMonitoringInfo()
|
||||||
|
},
|
||||||
|
select: (data: MonitoringCount | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchTrackingStats } from '@/app/actions/trackingActions';
|
||||||
|
|
||||||
|
export interface useTrackingStats {
|
||||||
|
totalViews: number,
|
||||||
|
uniqueIps: number,
|
||||||
|
uniqueUsers: number,
|
||||||
|
documentsCount: number,
|
||||||
|
uniqueCountryCount: number,
|
||||||
|
uniqueCityCount: number,
|
||||||
|
firstViewDate: string,
|
||||||
|
lastViewDate: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTrackingStats = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['trackingStats'],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchTrackingStats()
|
||||||
|
},
|
||||||
|
select: (data: useTrackingStats | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -39,7 +39,7 @@ export interface UserData {
|
|||||||
tariffs: Tariff[];
|
tariffs: Tariff[];
|
||||||
tariffInfo: TariffInfo;
|
tariffInfo: TariffInfo;
|
||||||
permission: number;
|
permission: number;
|
||||||
verifiedStatus?: string;
|
verifiedStatus?: 'unknow' | 'NOT_VERIFIED' | 'VERIFICATION_IN_PROGRESS' | 'VERIFIED' | 'VERIFICATION_FAILED';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUserProfile = () => {
|
export const useUserProfile = () => {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { fetchViolationFinalSummaryStatistic } from '@/app/actions/violationActions';
|
||||||
|
|
||||||
|
export interface UseViolationFinalSummaryStatistic {
|
||||||
|
complaints_without_case: number;
|
||||||
|
total_complaints: number;
|
||||||
|
total_law_cases: number;
|
||||||
|
total_violations: number;
|
||||||
|
violations_with_complaint: number;
|
||||||
|
violations_without_complaint: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useViolationFinalSummaryStatistic = () => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['violationFinalSummaryStatistic'],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchViolationFinalSummaryStatistic()
|
||||||
|
},
|
||||||
|
select: (data: UseViolationFinalSummaryStatistic | null) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -5,14 +5,16 @@ export interface UseViolationStatistic {
|
|||||||
total_violations: number,
|
total_violations: number,
|
||||||
new_violations: number,
|
new_violations: number,
|
||||||
in_progress_violations: number,
|
in_progress_violations: number,
|
||||||
resolved_violations: number
|
resolved_violations: number,
|
||||||
|
total_complaints: number,
|
||||||
|
total_law_cases: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useViolationStatistic = () => {
|
export const useViolationStatistic = (id?: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ['violationStatistic'],
|
queryKey: ['violationStatistic', id],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return fetchViolationStatistic()
|
return fetchViolationStatistic(id)
|
||||||
},
|
},
|
||||||
select: (data: UseViolationStatistic | null) => {
|
select: (data: UseViolationStatistic | null) => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Providers from './getQueryServer';
|
||||||
|
|
||||||
|
export function WatchDocProviders({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Providers>
|
||||||
|
{children}
|
||||||
|
</Providers>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,34 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
isServer,
|
|
||||||
QueryClient,
|
|
||||||
QueryClientProvider,
|
QueryClientProvider,
|
||||||
} from '@tanstack/react-query'
|
} from '@tanstack/react-query'
|
||||||
|
|
||||||
import { getQueryClient } from '@/app/providers/getQueryClient';
|
import { getQueryClient } from '@/app/providers/getQueryClient';
|
||||||
|
|
||||||
/* export function makeQueryClient() {
|
|
||||||
return new QueryClient({
|
|
||||||
defaultOptions: {
|
|
||||||
queries: {
|
|
||||||
staleTime: 60 * 1000,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let browserQueryClient: QueryClient | undefined = undefined
|
|
||||||
|
|
||||||
function getQueryClient() {
|
|
||||||
if (isServer) {
|
|
||||||
return makeQueryClient()
|
|
||||||
} else {
|
|
||||||
if (!browserQueryClient) browserQueryClient = makeQueryClient()
|
|
||||||
return browserQueryClient
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
export default function Providers({ children }: { children: React.ReactNode }) {
|
export default function Providers({ children }: { children: React.ReactNode }) {
|
||||||
const queryClient = getQueryClient();
|
const queryClient = getQueryClient();
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
@use './VKLogin.scss';
|
@use './VKLogin.scss';
|
||||||
@use './animation.scss';
|
@use './animation.scss';
|
||||||
@use './violation-details.scss';
|
@use './violation-details.scss';
|
||||||
|
@use './watch-doc.scss';
|
||||||
|
@use './tracking-statistic.scss';
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
@@ -161,7 +163,7 @@
|
|||||||
|
|
||||||
.btn,
|
.btn,
|
||||||
.btn-s,
|
.btn-s,
|
||||||
.btn-m {
|
.btn-l {
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -195,7 +197,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
background: linear-gradient(135deg, v.$p-color, v.$s-color);
|
/* background: linear-gradient(135deg, v.$p-color, v.$s-color); */
|
||||||
|
background: #6366f1;
|
||||||
color: v.$white;
|
color: v.$white;
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
|
|
||||||
@@ -220,6 +223,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background: v.$bg-hover;
|
||||||
|
color: v.$text-p;
|
||||||
|
border: 2px solid v.$border-color-1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.btn-outline {
|
.btn-outline {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 2px solid v.$p-color;
|
border: 2px solid v.$p-color;
|
||||||
@@ -777,7 +790,7 @@
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: #000000;
|
background: v.$black;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@@ -53,3 +53,6 @@ path:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
@import 'swiper/css';
|
||||||
|
@import 'swiper/css/navigation';
|
||||||
|
@import 'swiper/css/pagination';
|
||||||
@@ -270,15 +270,16 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: -5px;
|
right: -5px;
|
||||||
bottom: -5px;
|
bottom: -5px;
|
||||||
width: 20px;
|
/* width: 22px;
|
||||||
height: 20px;
|
height: 22px; */
|
||||||
border-radius: 50%;
|
border-radius: 15px;
|
||||||
background: v.$red;
|
background: v.$red;
|
||||||
color: v.$white;
|
color: v.$white;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
padding: 2px 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -650,6 +650,10 @@
|
|||||||
&.text-filter-search {
|
&.text-filter-search {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&.end {
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@@ -686,7 +690,7 @@
|
|||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
outline: 2px solid #0000;
|
outline: 2px solid v.$black;
|
||||||
box-shadow: 0 0 0 2px #6365f187;
|
box-shadow: 0 0 0 2px #6365f187;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -901,6 +905,20 @@
|
|||||||
padding: 15px 16px;
|
padding: 15px 16px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-item-file-name-image-wrapper {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f5f5f5;
|
||||||
|
box-shadow: 1px 1px 2px #00000030;
|
||||||
|
|
||||||
|
img {
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-item {
|
.table-item {
|
||||||
@@ -912,13 +930,24 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-item-protected {
|
.table-item-status-badge {
|
||||||
color: #10b981;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
|
|
||||||
|
&.protected {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.moderation {
|
||||||
|
color: v.$color-warning;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.blocked {
|
||||||
|
color: v.$red;
|
||||||
|
}
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '•';
|
content: '•';
|
||||||
color: v.$text-s;
|
color: v.$text-s;
|
||||||
@@ -974,6 +1003,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: v.$white;
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
@@ -994,11 +1027,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-action-delete {
|
.table-action-appeal {
|
||||||
background-color: #e80a14;
|
background-color: #9f0712;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #9f0712;
|
background-color: v.$red;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1009,6 +1042,67 @@
|
|||||||
background-color: #155dfc;
|
background-color: #155dfc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggle-switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 60px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 6px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
background-color: #615fff;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch.active {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch.inactive {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch .toggle-slider {
|
||||||
|
position: absolute;
|
||||||
|
width: 30px;
|
||||||
|
height: 28px;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #615fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch.active .toggle-slider {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch.inactive .toggle-slider {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch .toggle-label {
|
||||||
|
z-index: 1;
|
||||||
|
color: v.$text-p;
|
||||||
|
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
|
||||||
|
width: 30px;
|
||||||
|
height: 26px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-item-checkbox {
|
.table-item-checkbox {
|
||||||
@@ -1615,7 +1709,8 @@
|
|||||||
|
|
||||||
.dashboard-user-stats {
|
.dashboard-user-stats {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, 1fr);
|
/* grid-template-columns: repeat(5, 1fr); */
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
max-width: calc(100vw - var(--side-bar-width) - 60px);
|
max-width: calc(100vw - var(--side-bar-width) - 60px);
|
||||||
@@ -1715,13 +1810,13 @@
|
|||||||
.left-column {
|
.left-column {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-right: 15px;
|
padding-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-column {
|
.right-column {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-left: 15px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1360px) {
|
@media (max-width: 1360px) {
|
||||||
@@ -1775,6 +1870,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.file-item {
|
.file-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -2578,7 +2674,7 @@
|
|||||||
.violations-my-cases-section {
|
.violations-my-cases-section {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
padding: 32px;
|
padding: 32px 32px 16px;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
box-shadow: 0 10px 40px rgba(99, 102, 241, 0.1);
|
box-shadow: 0 10px 40px rgba(99, 102, 241, 0.1);
|
||||||
max-width: calc(100vw - var(--side-bar-width) - 60px);
|
max-width: calc(100vw - var(--side-bar-width) - 60px);
|
||||||
@@ -2597,39 +2693,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.my-cases-title {
|
.my-cases-title {
|
||||||
font-size: 1.5rem;
|
font-size: 18px;
|
||||||
font-weight: 800;
|
font-weight: 600;
|
||||||
color: #1e293b;
|
color: v.$text-p;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-outline-small {
|
|
||||||
background: transparent;
|
|
||||||
color: #667eea;
|
|
||||||
border: 2px solid #667eea;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 10px;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cases-carousel-track {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
@media (max-width: 890px) {
|
|
||||||
grid-template-columns: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.case-card {
|
.case-card {
|
||||||
@@ -2642,6 +2708,11 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
&:hover::before {
|
&:hover::before {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
@@ -2653,7 +2724,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 4px;
|
width: 4px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: #667eea;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
border-radius: 16px 0 0 16px;
|
border-radius: 16px 0 0 16px;
|
||||||
@@ -2665,6 +2736,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
.case-number {
|
.case-number {
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
@@ -2673,12 +2745,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.case-card-title {
|
.case-card-content {
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1e293b;
|
color: #1e293b;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.case-card-meta {
|
.case-card-meta {
|
||||||
@@ -2726,10 +2802,66 @@
|
|||||||
letter-spacing: 0.3px;
|
letter-spacing: 0.3px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: v.$status-showed;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #667eea;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cases-carousel-track {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.swiper-slide {
|
||||||
|
height: auto;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-cases-tabs {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-button {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
background: v.$b-color-1;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: v.$white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiper-pagination-bullet {
|
||||||
|
width: 12px !important;
|
||||||
|
height: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiper-pagination-bullet-active {
|
||||||
|
background: #3b82f6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swiper-pagination-fraction,
|
||||||
|
.swiper-pagination-custom,
|
||||||
|
.swiper-horizontal>.swiper-pagination-bullets,
|
||||||
|
.swiper-pagination-bullets.swiper-pagination-horizontal {
|
||||||
|
margin-top: 16px;
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.violation-check-all-section {
|
.violation-check-all-section {
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
@@ -3678,7 +3810,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-header {
|
.info-header {
|
||||||
background: linear-gradient(0deg, #6366f1 50%, #8b5cf6 130%);
|
background: #6366f1;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-bottom: 1px solid v.$border-color-1;
|
border-bottom: 1px solid v.$border-color-1;
|
||||||
@@ -3726,6 +3858,84 @@
|
|||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
border-bottom: 1px solid #f1f5f9;
|
border-bottom: 1px solid #f1f5f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.appeal-form {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appeal-textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appeal-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #6366f1;
|
||||||
|
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.appeal-textarea:disabled {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appeal-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appeal-submit-btn {
|
||||||
|
padding: 8px 20px;
|
||||||
|
background-color: #6366f1;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appeal-submit-btn:hover:not(:disabled) {
|
||||||
|
background-color: #4f46e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appeal-submit-btn:disabled {
|
||||||
|
background-color: #9ca3af;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appeal-cancel-btn {
|
||||||
|
padding: 8px 20px;
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
color: #374151;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appeal-cancel-btn:hover:not(:disabled) {
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.appeal-cancel-btn:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3771,48 +3981,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-add-file {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.section-drop-down-list {
|
|
||||||
position: absolute;
|
|
||||||
left: 80%;
|
|
||||||
top: 50%;
|
|
||||||
max-height: 0;
|
|
||||||
color: v.$text-p;
|
|
||||||
font-size: 16px;
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 0;
|
|
||||||
border: none;
|
|
||||||
width: fit-content;
|
|
||||||
z-index: 2;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
background: #ffffff00;
|
|
||||||
overflow: hidden;
|
|
||||||
transition: all 0.5s ease;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
&.opened {
|
|
||||||
max-height: 500px;
|
|
||||||
padding: 15px 10px;
|
|
||||||
box-shadow: 0 4px 20px #00000014;
|
|
||||||
background: v.$white;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
white-space: nowrap;
|
|
||||||
color: v.$p-color;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.violations-table {
|
.violations-table {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
@@ -5060,6 +5228,10 @@
|
|||||||
background: v.$white;
|
background: v.$white;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 490px) {
|
@media (max-width: 490px) {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,13 +70,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.analytics-grid {
|
.analytics-grid {
|
||||||
display: flex;
|
|
||||||
gap: 30px;
|
gap: 30px;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 10px;
|
||||||
justify-content: space-between;
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|
||||||
@media (max-width: 970px) {
|
.tanstak-table-wrapper .tanstak-table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1370px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.analytics-card {
|
.analytics-card {
|
||||||
@@ -85,10 +90,8 @@
|
|||||||
padding: 30px;
|
padding: 30px;
|
||||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.1);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
flex: 1;
|
|
||||||
min-width: 350px;
|
|
||||||
max-width: calc(50% - 15px);
|
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
@media (max-width: 970px) {
|
@media (max-width: 970px) {
|
||||||
max-width: calc(100vw - var(--side-bar-width) - 30px);
|
max-width: calc(100vw - var(--side-bar-width) - 30px);
|
||||||
|
|||||||
@@ -230,6 +230,10 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.drag-drop-zone {
|
.drag-drop-zone {
|
||||||
border: 2px dashed #6366f1;
|
border: 2px dashed #6366f1;
|
||||||
border-radius: .75rem;
|
border-radius: .75rem;
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
@use './variable.scss' as v;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--p: #6366f1;
|
||||||
|
--pd: #4f46e5;
|
||||||
|
--pl: #e0e7ff;
|
||||||
|
--g: #10b981;
|
||||||
|
--a: #f59e0b;
|
||||||
|
--r: #f43f5e;
|
||||||
|
--s: #0ea5e9;
|
||||||
|
--bg: #f1f5f9;
|
||||||
|
--sur: #fff;
|
||||||
|
--brd: #e2e8f0;
|
||||||
|
--tx: #1e293b;
|
||||||
|
--mu: #64748b;
|
||||||
|
--rad: 14px;
|
||||||
|
--sh: 0 2px 10px rgba(0, 0, 0, .07);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tracking-page {
|
||||||
|
.general-statistic {
|
||||||
|
background: linear-gradient(135deg, v.$p-color 55%, v.$s-color);
|
||||||
|
border-radius: var(--rad);
|
||||||
|
padding: 32px;
|
||||||
|
color: #fff;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-sub {
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: .75;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
background: rgba(255, 255, 255, .13);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px 18px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, .15);
|
||||||
|
|
||||||
|
&-ico {
|
||||||
|
font-size: 20px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
svg {
|
||||||
|
fill: v.$white;
|
||||||
|
color: v.$white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-lbl {
|
||||||
|
font-size: 11px;
|
||||||
|
opacity: .8;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .5px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-val {
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,9 +11,13 @@ $bg-hover: #e9ecef;
|
|||||||
$bg-light: #f8f9ff;
|
$bg-light: #f8f9ff;
|
||||||
$bg-darkening: #0000007a;
|
$bg-darkening: #0000007a;
|
||||||
$shadow-1: #0000001a;
|
$shadow-1: #0000001a;
|
||||||
|
$shadow-2: #8686861a;
|
||||||
$white: #fff;
|
$white: #fff;
|
||||||
|
$black: #000;
|
||||||
$red: #dc2626;
|
$red: #dc2626;
|
||||||
$green: #00a63e;
|
$green: #00a63e;
|
||||||
|
$green-2: #10b981;
|
||||||
|
$grey: #5a6e8a;
|
||||||
$p-color-disabled: #6365f18e;
|
$p-color-disabled: #6365f18e;
|
||||||
$s-color-disabled: #8a5cf663;
|
$s-color-disabled: #8a5cf663;
|
||||||
$border-color-1: #e2e8f0;
|
$border-color-1: #e2e8f0;
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
@use './variable.scss' as v;
|
@use './variable.scss' as v;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--violation-info-indents: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1480px) {
|
||||||
|
:root {
|
||||||
|
--violation-info-indents: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1240px) {
|
||||||
|
:root {
|
||||||
|
--violation-info-indents: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.violation-details {
|
.violation-details {
|
||||||
.page-title-color-frame {
|
.page-title-color-frame {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -142,11 +158,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary-small {
|
.btn-primary-small {
|
||||||
border: 2px solid v.$status-in-work;
|
border: 2px solid v.$status-showed;
|
||||||
color: v.$status-in-work;
|
color: v.$status-showed;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: v.$status-in-work;
|
background: v.$status-showed;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -164,19 +180,29 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 3fr;
|
grid-template-columns: 1fr 3fr;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
height: 80vh;
|
height: 81vh;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.sources-list {
|
.sources-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
.source-card {
|
.source-card {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #f3f4f6;
|
border: 1px solid #f3f4f6;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
box-shadow: 0 4px 20px #0000001a;
|
box-shadow: 0px 1px 0 2px v.$shadow-2;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -189,9 +215,13 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
|
@media (max-width: 1580px) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
|
/* box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.1); */
|
||||||
border-color: #cbd5e1;
|
border-color: #cbd5e1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +276,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.source-url {
|
.source-url {
|
||||||
color: #000;
|
color: v.$black;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -281,7 +311,7 @@
|
|||||||
fill: #64748b;
|
fill: #64748b;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
fill: #000;
|
fill: v.$black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,6 +322,15 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
.pagination-controls-pages button,
|
||||||
|
.pagination-controls .arrow {
|
||||||
|
color: v.$text-p;
|
||||||
|
|
||||||
|
&.current {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.source-status {
|
.source-status {
|
||||||
@@ -340,25 +379,34 @@
|
|||||||
|
|
||||||
.btn-resolve {
|
.btn-resolve {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: #10b981;
|
color: v.$green-2;
|
||||||
border: 2px solid #10b981;
|
border: 2px solid v.$green-2;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
/* transform: translateY(-2px);
|
background: v.$green-2;
|
||||||
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.4); */
|
|
||||||
background: #10b981;
|
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-no-my-file {
|
||||||
|
white-space: nowrap;
|
||||||
|
color: v.$grey;
|
||||||
|
border: 2px solid v.$grey;
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: v.$grey;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.violation-info {
|
.violation-info {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #f3f4f6;
|
border: 1px solid #f3f4f6;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
box-shadow: 0 4px 20px #0000001a;
|
box-shadow: 0 4px 20px #0000001a;
|
||||||
padding: 15px;
|
padding: var(--violation-info-indents);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -375,7 +423,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-grid {
|
&-grid {
|
||||||
@@ -387,12 +435,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
/* border-bottom: 1px solid v.$border-color-1; */
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
&.not-selected-match {
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-image-wrapper {
|
&-image-wrapper {
|
||||||
@@ -404,14 +456,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-image {
|
&-image {
|
||||||
width: 300px;
|
width: 285px;
|
||||||
height: 200px;
|
height: 190px;
|
||||||
position: relative;
|
position: relative;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-shadow: 4px 4px 10px v.$bg-darkening;
|
box-shadow: 4px 4px 10px v.$bg-darkening;
|
||||||
|
|
||||||
|
@media (max-width: 1240px) {
|
||||||
|
width: 150px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -425,6 +482,8 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 5px;
|
bottom: 5px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
|
||||||
.image-btn {
|
.image-btn {
|
||||||
width: 36px;
|
width: 36px;
|
||||||
@@ -439,6 +498,11 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
@media (max-width: 1240px) {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #f1f5f9;
|
background: #f1f5f9;
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
@@ -453,9 +517,8 @@
|
|||||||
&-right-side {
|
&-right-side {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: start;
|
||||||
padding-left: 16px;
|
overflow: hidden;
|
||||||
/* gap: 10px; */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&-date,
|
&-date,
|
||||||
@@ -466,7 +529,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.source {
|
.source {
|
||||||
color: #000;
|
color: v.$black;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@@ -483,7 +546,7 @@
|
|||||||
padding-bottom: 4px;
|
padding-bottom: 4px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
fill: #000;
|
fill: v.$black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +573,7 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
color: #000;
|
color: v.$black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,6 +584,8 @@
|
|||||||
|
|
||||||
&-action {
|
&-action {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 10px
|
||||||
}
|
}
|
||||||
|
|
||||||
&-actions {
|
&-actions {
|
||||||
@@ -530,16 +595,18 @@
|
|||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
padding-bottom: 5px;
|
padding-bottom: var(--violation-info-indents);
|
||||||
padding-right: 16px;
|
|
||||||
padding-left: 10px;
|
|
||||||
border-bottom: 1px solid rgb(226, 232, 240);
|
border-bottom: 1px solid rgb(226, 232, 240);
|
||||||
|
|
||||||
&-grid {
|
&-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 300px auto;
|
grid-template-columns: 300px auto;
|
||||||
gap: 10px;
|
gap: var(--violation-info-indents);
|
||||||
margin-bottom: 10px;
|
margin-bottom: var(--violation-info-indents);
|
||||||
|
|
||||||
|
@media (max-width: 1240px) {
|
||||||
|
grid-template-columns: 150px auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-bottom {
|
&-bottom {
|
||||||
@@ -550,7 +617,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-case {
|
&-case {
|
||||||
padding: 10px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -620,7 +686,7 @@
|
|||||||
.violation-info-tabs {
|
.violation-info-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
padding: 10px;
|
padding: var(--violation-info-indents) 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,9 +700,6 @@
|
|||||||
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
/* background: v.$status-in-work;
|
|
||||||
color: v.$white;
|
|
||||||
border: 2px solid transparent; */
|
|
||||||
border: 2px solid transparent;
|
border: 2px solid transparent;
|
||||||
|
|
||||||
color: white;
|
color: white;
|
||||||
@@ -737,9 +800,15 @@
|
|||||||
background-color: v.$white;
|
background-color: v.$white;
|
||||||
max-width: 50%;
|
max-width: 50%;
|
||||||
|
|
||||||
|
@media (max-width: 1240px) {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
background-color: v.$bg-hover;
|
background-color: v.$bg-hover;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
@@ -748,10 +817,6 @@
|
|||||||
&:first-child {
|
&:first-child {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: v.$status-in-work;
|
color: v.$status-in-work;
|
||||||
|
|
||||||
.complaint-details-header {
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(even) {
|
&:nth-child(even) {
|
||||||
@@ -763,23 +828,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-header {
|
|
||||||
/* background-color: v.$b-color-1; */
|
|
||||||
padding: 10px 16px;
|
|
||||||
border-bottom: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-title {
|
&-title {
|
||||||
color: #495057;
|
color: v.$text-s;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
/* text-transform: uppercase; */
|
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
&-content {
|
||||||
font-weight: 600;
|
color: v.$black;
|
||||||
color: #212529;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,21 +880,45 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-textarea {
|
|
||||||
|
.note-form-flex {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--violation-info-indents);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 80px;
|
/* justify-content: space-between; */
|
||||||
padding: 10px 20px;
|
}
|
||||||
border: 1px solid v.$border-color-1;
|
|
||||||
border-radius: 8px;
|
.note-case-label {
|
||||||
font-family: inherit;
|
font-weight: 600;
|
||||||
resize: vertical;
|
display: block;
|
||||||
|
background: #e9ecef;
|
||||||
|
color: v.$text-p;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-case-input,
|
||||||
|
.note-textarea {
|
||||||
|
background: v.$white;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: #f8fafc;
|
padding: 10px 20px;
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: v.$bg-light;
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 2px 2px 4px v.$shadow-1;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-textarea {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 80px;
|
||||||
|
font-family: inherit;
|
||||||
|
resize: none;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
&-wrapper {
|
&-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -846,43 +927,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-form-flex {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note-case-input {
|
.note-case-input {
|
||||||
border: 1px solid v.$border-color-1;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
background: #f8fafc;
|
|
||||||
padding: 10px 20px;
|
|
||||||
|
|
||||||
&::-webkit-outer-spin-button,
|
&::-webkit-outer-spin-button,
|
||||||
&::-webkit-inner-spin-button {
|
&::-webkit-inner-spin-button {
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 2px 2px 4px v.$shadow-1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&::placeholder {
|
.note-form-section {
|
||||||
font-size: 12px;
|
overflow: hidden;
|
||||||
}
|
margin-bottom: var(--violation-info-indents);
|
||||||
}
|
width: 100%;
|
||||||
|
|
||||||
.note-case-label {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.note-form-group {
|
.note-form-group {
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0px;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
input {
|
input {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* input[name="amount"] {
|
||||||
|
&::placeholder {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -920,7 +994,7 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: v.$white;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
@@ -930,3 +1004,106 @@
|
|||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.template-modal-content {
|
||||||
|
min-width: 400px;
|
||||||
|
max-width: 500px;
|
||||||
|
background: v.$white;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid v.$border-color-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-modal-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-modal-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
padding: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-modal-close:hover {
|
||||||
|
background: v.$border-color-1;
|
||||||
|
color: v.$text-p;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: v.$text-p;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-name-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid v.$border-color-1;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-name-input:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px v.$shadow-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-preview {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-preview-label {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: v.$text-p;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-preview-text {
|
||||||
|
background: v.$bg-hover;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
color: v.$text-s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.template-modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-top: 1px solid v.$border-color-1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,319 @@
|
|||||||
|
@use './variable.scss' as v;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #f5f6fa;
|
||||||
|
--card: #fff;
|
||||||
|
--border: #e2e5ef;
|
||||||
|
--border2: #d0d4e4;
|
||||||
|
--text: #0f1221;
|
||||||
|
--text2: #4b5270;
|
||||||
|
--muted: #8890ad;
|
||||||
|
--accent: #5b47e0;
|
||||||
|
--accent2: #7c5cf6;
|
||||||
|
--green: #059669;
|
||||||
|
--green-bg: #ecfdf5;
|
||||||
|
--green-bd: #a7f3d0;
|
||||||
|
--red: #dc2626;
|
||||||
|
--red-bg: #fef2f2;
|
||||||
|
--amber: #b45309;
|
||||||
|
--shadow: 0 2px 12px rgba(15, 18, 33, .07);
|
||||||
|
}
|
||||||
|
|
||||||
|
.watch-doc {
|
||||||
|
&-wrapper {
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.6;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
|
|
||||||
|
.btn-acc {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 3px 12px rgba(91, 71, 224, .28);
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--accent2);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-out {
|
||||||
|
background: var(--card);
|
||||||
|
color: var(--text2);
|
||||||
|
border: 1px solid var(--border2);
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 8px;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
&__nav {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__next,
|
||||||
|
&__prev {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--accent);
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--border2);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
display: inline;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__current-page {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--muted);
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__fullscreen {
|
||||||
|
font-size: .75rem;
|
||||||
|
color: var(--accent);
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--border2);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.prev-wrap {
|
||||||
|
min-height: 400px;
|
||||||
|
min-width: 300px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-canvas {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-container {
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-container:fullscreen {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 20px;
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.prev-wrap {
|
||||||
|
flex: 1;
|
||||||
|
min-height: auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pdf-canvas {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: calc(100vh - 120px);
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-controls {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
z-index: 1000;
|
||||||
|
min-width: 300px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-page {
|
||||||
|
max-width: 880px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 16px 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 18px 0 20px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
margin-bottom: 28px;
|
||||||
|
|
||||||
|
.header-logo {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: v.$p-color;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-user {
|
||||||
|
font-size: .83rem;
|
||||||
|
color: var(--text2);
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 7px 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-status {
|
||||||
|
background: var(--green-bg);
|
||||||
|
border: 1.5px solid var(--green-bd);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 14px;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
padding: 20px 22px;
|
||||||
|
border-radius: 14px;
|
||||||
|
|
||||||
|
.st-title {
|
||||||
|
color: var(--green);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.st-sub {
|
||||||
|
font-size: .83rem;
|
||||||
|
color: var(--text2);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 28px 0 0;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: .76rem;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
margin-top: 14px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-card {
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
.card-head {
|
||||||
|
padding: 14px 20px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 9px;
|
||||||
|
|
||||||
|
font-size: .78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: .6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 14px 20px;
|
||||||
|
|
||||||
|
.owner-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.owner-av {
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.owner-name {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.owner-meta {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-description {
|
||||||
|
font-size: .82rem;
|
||||||
|
color: var(--text2);
|
||||||
|
margin-bottom: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 9px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import TanstakFilesTable from '@/app/components/tanstak-table/TanstakTable';
|
import TanstakFilesTable from '@/app/components/tanstak-table/TanstakTable';
|
||||||
|
|
||||||
type FilesTable = {
|
type FilesTable = {
|
||||||
fileType: string
|
fileType: string;
|
||||||
|
showFileLink?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FilesTable({ fileType }: FilesTable) {
|
export default function FilesTable({ fileType, showFileLink }: FilesTable) {
|
||||||
return (
|
return (
|
||||||
<div className="block-wrapper">
|
<div className="block-wrapper">
|
||||||
<TanstakFilesTable fileType={fileType} />
|
<TanstakFilesTable fileType={fileType} showFileLink={showFileLink}/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -25,10 +25,6 @@ export default function DashboardFilesInfo() {
|
|||||||
<span className="type-card-stat-label">{t('PROTECTED')}</span>
|
<span className="type-card-stat-label">{t('PROTECTED')}</span>
|
||||||
<span className="type-card-stat-value">{filesInfo?.images.protected ? filesInfo?.images.protected : 0}</span>
|
<span className="type-card-stat-value">{filesInfo?.images.protected ? filesInfo?.images.protected : 0}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="type-card-stat-row">
|
|
||||||
<span className="type-card-stat-label">{t('violations')}</span>
|
|
||||||
<span className="type-card-stat-value">{filesInfo?.images.violation ? filesInfo?.images.violation : 0}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -47,10 +43,6 @@ export default function DashboardFilesInfo() {
|
|||||||
<span className="type-card-stat-label">{t('PROTECTED')}</span>
|
<span className="type-card-stat-label">{t('PROTECTED')}</span>
|
||||||
<span className="type-card-stat-value">{filesInfo?.videos.protected ? filesInfo?.videos.protected : 0}</span>
|
<span className="type-card-stat-value">{filesInfo?.videos.protected ? filesInfo?.videos.protected : 0}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="type-card-stat-row">
|
|
||||||
<span className="type-card-stat-label">{t('violations')}</span>
|
|
||||||
<span className="type-card-stat-value">{filesInfo?.videos.violation ? filesInfo?.videos.violation : 0}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -69,10 +61,6 @@ export default function DashboardFilesInfo() {
|
|||||||
<span className="type-card-stat-label">{t('PROTECTED')}</span>
|
<span className="type-card-stat-label">{t('PROTECTED')}</span>
|
||||||
<span className="type-card-stat-value">{filesInfo?.audios.protected ? filesInfo?.audios.protected : 0}</span>
|
<span className="type-card-stat-value">{filesInfo?.audios.protected ? filesInfo?.audios.protected : 0}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="type-card-stat-row">
|
|
||||||
<span className="type-card-stat-label">{t('violations')}</span>
|
|
||||||
<span className="type-card-stat-value">{filesInfo?.audios.violation ? filesInfo?.audios.violation : 0}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -85,16 +73,12 @@ export default function DashboardFilesInfo() {
|
|||||||
<div className="type-card-stats">
|
<div className="type-card-stats">
|
||||||
<div className="type-card-stat-row">
|
<div className="type-card-stat-row">
|
||||||
<span className="type-card-stat-label">{t('size')}</span>
|
<span className="type-card-stat-label">{t('size')}</span>
|
||||||
<span className="type-card-stat-value">{filesInfo?.documents.check? convertBytes(filesInfo?.documents.check) : 0}</span>
|
<span className="type-card-stat-value">{filesInfo?.documents.size? convertBytes(filesInfo?.documents.size) : 0}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="type-card-stat-row">
|
<div className="type-card-stat-row">
|
||||||
<span className="type-card-stat-label">{t('PROTECTED')}</span>
|
<span className="type-card-stat-label">{t('PROTECTED')}</span>
|
||||||
<span className="type-card-stat-value">{filesInfo?.documents.protected ? filesInfo?.documents.protected : 0}</span>
|
<span className="type-card-stat-value">{filesInfo?.documents.protected ? filesInfo?.documents.protected : 0}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="type-card-stat-row">
|
|
||||||
<span className="type-card-stat-label">{t('violations')}</span>
|
|
||||||
<span className="type-card-stat-value">{filesInfo?.documents.violation ? filesInfo?.documents.violation : 0}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,63 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import { fetchLastMonitoringCheck } from '@/app/actions/monitoringActions';
|
||||||
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
export default function DashboardLastCheck() {
|
export default function DashboardLastCheck() {
|
||||||
|
const { data, isLoading } = useQuery({
|
||||||
|
queryKey: ['lastMonitoringCheck'],
|
||||||
|
queryFn: () => {
|
||||||
|
return fetchLastMonitoringCheck()
|
||||||
|
},
|
||||||
|
select: (data) => {
|
||||||
|
if (!data) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
retry: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-last-check">
|
<div className="dashboard-last-check">
|
||||||
<div className="last-check-info">
|
<div className="last-check-info">
|
||||||
<div className="last-check-title">Последняя проверка контента</div>
|
<div className="last-check-title">
|
||||||
|
{t('last-content-check')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="relative"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<div
|
||||||
|
className="loading-animation"
|
||||||
|
>
|
||||||
|
<div className="global-spinner"></div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="last-check-date">
|
<div className="last-check-date">
|
||||||
17.10.2025 в 10:23</div>
|
{data?.recent_searches[0]?.created_at ? `${formatDate(data.recent_searches[0].created_at)} ${t('in')} ${formatDateTime(data.recent_searches[0].created_at)}` : '---'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="last-check-actions">
|
<div className="last-check-actions">
|
||||||
<Link
|
<Link
|
||||||
className="btn-report"
|
className="btn-report"
|
||||||
href={'reports'}
|
href={'/pages/reports'}
|
||||||
>
|
>
|
||||||
📄 Отчёт
|
{t('reports')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
{/* <Link
|
||||||
className="btn-new-search"
|
className="btn-new-search"
|
||||||
href={'violations'}
|
href={'violations'}
|
||||||
>
|
>
|
||||||
🔍 Мониторинг нарушений
|
Мониторинг нарушений
|
||||||
</Link>
|
</Link> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,11 +2,9 @@
|
|||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { getUserFilesData } from '@/app/actions/fileEntity';
|
import { getUserFilesData } from '@/app/actions/fileEntity';
|
||||||
import { useEffect, useState, useRef } from 'react';
|
|
||||||
import { FileTypeIcon } from '@/app/components/FileTypeIcon';
|
import { FileTypeIcon } from '@/app/components/FileTypeIcon';
|
||||||
import { convertBytes } from '@/app/lib/convertBytes';
|
import { convertBytes } from '@/app/lib/convertBytes';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { useClickOutside } from '@/app/hooks/useClickOutside';
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
type FileItem = {
|
type FileItem = {
|
||||||
@@ -71,15 +69,6 @@ export default function DashboardUserFiles() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
const [openDropDownList, setOpenDropDownList] = useState(false);
|
|
||||||
const dropDownList = useRef(null);
|
|
||||||
useClickOutside(
|
|
||||||
dropDownList,
|
|
||||||
() => {
|
|
||||||
setOpenDropDownList(false)
|
|
||||||
},
|
|
||||||
openDropDownList
|
|
||||||
);
|
|
||||||
|
|
||||||
const formatDate = (timestamp: number) => {
|
const formatDate = (timestamp: number) => {
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
@@ -97,34 +86,25 @@ export default function DashboardUserFiles() {
|
|||||||
{t('your-files')}
|
{t('your-files')}
|
||||||
</h3>
|
</h3>
|
||||||
<div
|
<div
|
||||||
className="section-add-file btn view-all-link"
|
className="btn view-all-link"
|
||||||
onClick={() => {
|
|
||||||
setOpenDropDownList(!openDropDownList);
|
|
||||||
}}
|
|
||||||
ref={dropDownList}
|
|
||||||
>
|
>
|
||||||
{t('add')}
|
<Link href='/pages/my-content'>
|
||||||
<div
|
{t('all')}
|
||||||
className={`section-drop-down-list ${openDropDownList ? 'opened' : ''}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="flex flex-col text-center"
|
|
||||||
>
|
|
||||||
<Link href='/pages/marking-images'>
|
|
||||||
{t('photo-marking')}
|
|
||||||
</Link>
|
</Link>
|
||||||
<Link href='/pages/marking-video'>
|
|
||||||
{t('video-marking')}
|
|
||||||
</Link>
|
|
||||||
<Link href='/pages/marking-audio'>
|
|
||||||
{t('audio-marking')}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="user-files-list">
|
<div className="user-files-list">
|
||||||
|
{isLoading && (
|
||||||
|
<div
|
||||||
|
className="loading-animation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="global-spinner"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{tableData?.length ? (
|
{tableData?.length ? (
|
||||||
tableData?.map((file) => {
|
tableData?.map((file) => {
|
||||||
@@ -151,9 +131,11 @@ export default function DashboardUserFiles() {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
|
!isLoading && (
|
||||||
<div>
|
<div>
|
||||||
{t('there-are-no-files-yet')}
|
{t('there-are-no-files-yet')}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function DashboardUserStats() {
|
|||||||
<IconSearch />
|
<IconSearch />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-value">
|
{/* <div className="stat-value">
|
||||||
<div
|
<div
|
||||||
className="loading-placeholder start"
|
className="loading-placeholder start"
|
||||||
>
|
>
|
||||||
@@ -58,13 +58,26 @@ export default function DashboardUserStats() {
|
|||||||
filesInfo?.total.check ? filesInfo?.total.check : 0
|
filesInfo?.total.check ? filesInfo?.total.check : 0
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div> */}
|
||||||
|
<div className="stat-value">
|
||||||
|
<div
|
||||||
|
className="loading-placeholder start"
|
||||||
|
>
|
||||||
|
{violationStatisticLoading ? (
|
||||||
|
<div className="loading-animation">
|
||||||
|
<div className="global-spinner" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
violationStatistic?.total_violations ?? 0
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-label">
|
<div className="stat-label">
|
||||||
{t('matches-found')}
|
{t('matches-found')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="stat-card violations">
|
<div className="stat-card violations hidden">
|
||||||
<div className="stat-header">
|
<div className="stat-header">
|
||||||
<div className="stat-icon">
|
<div className="stat-icon">
|
||||||
<IconWarning />
|
<IconWarning />
|
||||||
@@ -84,7 +97,7 @@ export default function DashboardUserStats() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-label">
|
<div className="stat-label">
|
||||||
{t('violations')}
|
{t('matches')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Link from 'next/link';
|
|||||||
import { getViolationFilesArray } from '@/app/actions/violationActions';
|
import { getViolationFilesArray } from '@/app/actions/violationActions';
|
||||||
import { IconEye } from '@/app/ui/icons/icons';
|
import { IconEye } from '@/app/ui/icons/icons';
|
||||||
|
|
||||||
interface ViolationFile {
|
interface FileViolation {
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
fileId: string;
|
fileId: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
@@ -23,20 +23,26 @@ interface ViolationFile {
|
|||||||
violationCount: number;
|
violationCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ViolationData {
|
||||||
|
content: FileViolation[];
|
||||||
|
}
|
||||||
|
|
||||||
export default function DashboardUserViolations() {
|
export default function DashboardUserViolations() {
|
||||||
const {
|
const {
|
||||||
data: violationData,
|
data: violationData,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
error,
|
error,
|
||||||
} = useQuery<ViolationFile[]>({
|
} = useQuery<ViolationData>({
|
||||||
queryKey: ['violationData'],
|
queryKey: ['violationData', { page: 0, size: 5 }],
|
||||||
queryFn: () => getViolationFilesArray(),
|
queryFn: () => getViolationFilesArray({
|
||||||
|
page: 0,
|
||||||
|
size: 5,
|
||||||
|
}),
|
||||||
|
|
||||||
select: (data) => {
|
select: (data) => {
|
||||||
return data?.slice(0, 5) || [];
|
return data;
|
||||||
},
|
}
|
||||||
/* refetchInterval: 30000 */
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
@@ -56,7 +62,7 @@ export default function DashboardUserViolations() {
|
|||||||
{t('violation')}
|
{t('violation')}
|
||||||
</h3>
|
</h3>
|
||||||
<div
|
<div
|
||||||
className="section-add-file btn view-all-link"
|
className="btn view-all-link"
|
||||||
>
|
>
|
||||||
<Link href='/pages/violations'>
|
<Link href='/pages/violations'>
|
||||||
{t('all')}
|
{t('all')}
|
||||||
@@ -65,9 +71,19 @@ export default function DashboardUserViolations() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="user-files-list">
|
<div className="user-files-list">
|
||||||
|
{isLoading && (
|
||||||
|
<div
|
||||||
|
className="loading-animation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="global-spinner"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{violationData?.length ? (
|
{violationData?.content?.length ? (
|
||||||
violationData?.map((file) => {
|
violationData?.content.map((file) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={file.fileId}
|
key={file.fileId}
|
||||||
@@ -94,9 +110,11 @@ export default function DashboardUserViolations() {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
) : (
|
) : (
|
||||||
|
!isLoading && (
|
||||||
<div>
|
<div>
|
||||||
{t('there-are-no-files-yet')}
|
{t('there-are-no-files-yet')}
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
|
||||||
import { Pagination, Autoplay } from 'swiper/modules';
|
|
||||||
import Image from 'next/image';
|
|
||||||
import 'swiper/css';
|
|
||||||
import 'swiper/css/pagination';
|
|
||||||
import testImage from '@/app/src/image-preview.png'
|
|
||||||
import { useEffect, useRef } from 'react';
|
|
||||||
import { useTranslations } from 'next-intl';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
|
||||||
import { getViolationFilesArray } from '@/app/actions/violationActions';
|
|
||||||
|
|
||||||
interface ViolationFile {
|
|
||||||
createdAt: string;
|
|
||||||
fileId: string;
|
|
||||||
fileName: string;
|
|
||||||
fileSize: number;
|
|
||||||
latestViolationDate: string;
|
|
||||||
mimeType: string;
|
|
||||||
status: string;
|
|
||||||
supportId: number;
|
|
||||||
violationCount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ViolationsCarousel() {
|
|
||||||
const {
|
|
||||||
data: violationData,
|
|
||||||
isLoading,
|
|
||||||
isError,
|
|
||||||
error,
|
|
||||||
} = useQuery<ViolationFile[]>({
|
|
||||||
queryKey: ['violationData'],
|
|
||||||
queryFn: () => getViolationFilesArray(),
|
|
||||||
|
|
||||||
select: (data) => {
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
/* refetchInterval: 30000 */
|
|
||||||
});
|
|
||||||
const paginationRef = useRef(null);
|
|
||||||
const t = useTranslations('Global');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(violationData);
|
|
||||||
}, [violationData])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="content-section">
|
|
||||||
<div className="section-header">
|
|
||||||
<h3 className="section-title">{t('violation')}</h3>
|
|
||||||
<Link
|
|
||||||
href="violations"
|
|
||||||
className="view-all-link"
|
|
||||||
>
|
|
||||||
Смотреть все
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{t('are-no-violations')}
|
|
||||||
</div>
|
|
||||||
<div className="violations-carousel">
|
|
||||||
<Swiper
|
|
||||||
spaceBetween={50}
|
|
||||||
slidesPerView={1}
|
|
||||||
modules={[Pagination, Autoplay]}
|
|
||||||
pagination={{
|
|
||||||
clickable: true,
|
|
||||||
el: '.custon-swiper-pagination'
|
|
||||||
}}
|
|
||||||
loop={true}
|
|
||||||
autoplay={{
|
|
||||||
delay: 3000,
|
|
||||||
disableOnInteraction: false,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* {violationData?.map(item => {
|
|
||||||
return (
|
|
||||||
<SwiperSlide>
|
|
||||||
<div
|
|
||||||
className="violation-slide"
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</SwiperSlide>
|
|
||||||
)
|
|
||||||
})} */}
|
|
||||||
{/* <SwiperSlide>
|
|
||||||
<div className="violation-slide">
|
|
||||||
<div className="violation-image-container">
|
|
||||||
<Image src={testImage} alt="Preview" className="violation-image" />
|
|
||||||
<div className="violation-badge">НАРУШЕНИЕ</div>
|
|
||||||
<div className="violation-status-badge">ОБРАБОТАНО</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="violation-content">
|
|
||||||
<div className="violation-title-row">
|
|
||||||
<div className="violation-filename">
|
|
||||||
🖼️banner111.png</div>
|
|
||||||
<div className="violation-type-badge photo">
|
|
||||||
Фото
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="violation-info-grid">
|
|
||||||
<div className="violation-info-item">
|
|
||||||
<div className="violation-info-label">
|
|
||||||
🌍 Регион
|
|
||||||
</div>
|
|
||||||
<div className="violation-info-value">
|
|
||||||
<span className="region-flag">🇺🇸</span>
|
|
||||||
<span>США</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="violation-info-item">
|
|
||||||
<div className="violation-info-label">📅 Обнаружено</div>
|
|
||||||
<div className="violation-info-value">
|
|
||||||
17.10.2025</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="violation-info-item">
|
|
||||||
<div className="violation-info-label">🌐 Домен</div>
|
|
||||||
<div className="violation-info-value">
|
|
||||||
m.apkpure.com </div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="violation-info-item">
|
|
||||||
<div className="violation-info-label">📊 Категория</div>
|
|
||||||
<div className="violation-info-value">
|
|
||||||
Единичное
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="violation-info-item">
|
|
||||||
<div className="violation-info-label">📁 Тип</div>
|
|
||||||
<div className="violation-info-value">
|
|
||||||
Изображение</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="violation-info-item">
|
|
||||||
<div className="violation-info-label">⚡ Статус</div>
|
|
||||||
<div className="violation-info-value">
|
|
||||||
✅ Обработано</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="violation-actions-row">
|
|
||||||
<button className="btn-visit-site">
|
|
||||||
🔗 Перейти на сайт
|
|
||||||
</button>
|
|
||||||
<button className="violation-action">
|
|
||||||
🚨 Открыть дело
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SwiperSlide> */}
|
|
||||||
</Swiper>
|
|
||||||
<div
|
|
||||||
className="flex justify-center mt-6"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
ref={paginationRef}
|
|
||||||
className="flex justify-center gap-2 custon-swiper-pagination"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,10 @@
|
|||||||
export default function FilePpageFileStatistic() {
|
'use client'
|
||||||
|
|
||||||
|
import { useViolationStatistic } from '@/app/hooks/react-query/useViolationStatistic';
|
||||||
|
|
||||||
|
export default function FilePageFileStatistic({ id }: { id: string }) {
|
||||||
|
const { data: violationStatistic } = useViolationStatistic(id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="block-wrapper"
|
className="block-wrapper"
|
||||||
@@ -15,7 +21,7 @@ export default function FilePpageFileStatistic() {
|
|||||||
className="stat-mini-value"
|
className="stat-mini-value"
|
||||||
style={{ 'color': '#dc2626' }}
|
style={{ 'color': '#dc2626' }}
|
||||||
>
|
>
|
||||||
0
|
{violationStatistic?.new_violations ? violationStatistic?.new_violations : 0}
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-mini-label">Новых</div>
|
<div className="stat-mini-label">Новых</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -24,7 +30,7 @@ export default function FilePpageFileStatistic() {
|
|||||||
className="stat-mini-value"
|
className="stat-mini-value"
|
||||||
style={{ 'color': '#d97706' }}
|
style={{ 'color': '#d97706' }}
|
||||||
>
|
>
|
||||||
0
|
{violationStatistic?.in_progress_violations ? violationStatistic?.in_progress_violations : 0}
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-mini-label">В работе</div>
|
<div className="stat-mini-label">В работе</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -33,7 +39,7 @@ export default function FilePpageFileStatistic() {
|
|||||||
className="stat-mini-value"
|
className="stat-mini-value"
|
||||||
style={{ 'color': '#059669' }}
|
style={{ 'color': '#059669' }}
|
||||||
>
|
>
|
||||||
0
|
{violationStatistic?.resolved_violations ? violationStatistic?.resolved_violations : 0}
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-mini-label">Решено</div>
|
<div className="stat-mini-label">Решено</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,7 +48,7 @@ export default function FilePpageFileStatistic() {
|
|||||||
className="stat-mini-value"
|
className="stat-mini-value"
|
||||||
style={{ 'color': '#6366f1' }}
|
style={{ 'color': '#6366f1' }}
|
||||||
>
|
>
|
||||||
0
|
{violationStatistic?.total_violations ? violationStatistic?.total_violations : 0}
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-mini-label">Всего</div>
|
<div className="stat-mini-label">Всего</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+88
-42
@@ -1,9 +1,9 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations';
|
import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations';
|
||||||
import { useEffect, useState, useActionState } from 'react';
|
import { useEffect, useActionState, SetStateAction } from 'react';
|
||||||
import { fetchCaseInfo, MatchStatus, createCase } from '@/app/actions/violationActions';
|
import { fetchClaimInfo, createClaim, MatchStatus } from '@/app/actions/violationActions';
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
@@ -16,39 +16,43 @@ interface CaseInfo {
|
|||||||
priority: string;
|
priority: string;
|
||||||
type: string;
|
type: string;
|
||||||
lawyer: string | null;
|
lawyer: string | null;
|
||||||
createdAt: string;
|
created_at: string;
|
||||||
updatedAt: string;
|
updated_at: string;
|
||||||
pageNumber: number;
|
page_number: number;
|
||||||
pageSize: number;
|
page_size: number;
|
||||||
totalElements: number;
|
total_elements: number;
|
||||||
totalPages: number;
|
total_pages: number;
|
||||||
violationId: number;
|
violation_id: number;
|
||||||
content: string | null;
|
content: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ENABLED_STATUSES = ['LEGAL_IN_WORK', 'COMPLAINT_AND_LEGAL_IN_WORK'];
|
const ENABLED_STATUSES = ['LEGAL_IN_WORK', 'COMPLAINT_AND_LEGAL_IN_WORK'];
|
||||||
type UpdateStatusHandler = (id: number, status: MatchStatus) => Promise<boolean>;
|
type UpdateStatusHandler = (id: number, status: MatchStatus) => Promise<boolean>;
|
||||||
|
|
||||||
export default function CaseViolation({ selectedViolation, updateStatusHandler }: { selectedViolation: ViolationFileDetail, updateStatusHandler: UpdateStatusHandler }) {
|
export default function CaseViolation({ selectedViolation, updateStatusHandler, setLocalViolation }:
|
||||||
|
{
|
||||||
|
selectedViolation: ViolationFileDetail,
|
||||||
|
updateStatusHandler: UpdateStatusHandler,
|
||||||
|
setLocalViolation: React.Dispatch<SetStateAction<ViolationFileDetail>>
|
||||||
|
}
|
||||||
|
) {
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
const queryClient = useQueryClient();
|
const [state, formAction, isPending] = useActionState(createClaim, undefined);
|
||||||
const [localViolation, setLocalViolation] = useState<ViolationFileDetail>(selectedViolation);
|
|
||||||
const [state, formAction, isPending] = useActionState(createCase, undefined);
|
|
||||||
|
|
||||||
const { data: caseInfo, isLoading: isLoadingCase } = useQuery({
|
const { data: caseInfo, isLoading: isLoadingCase } = useQuery({
|
||||||
queryKey: ['caseInfo', localViolation.id],
|
queryKey: ['caseInfo', selectedViolation.id],
|
||||||
queryFn: () => fetchCaseInfo(localViolation.id),
|
queryFn: () => fetchClaimInfo(selectedViolation.id),
|
||||||
select: (data) => {
|
select: (data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled: ENABLED_STATUSES.includes(localViolation.status)
|
enabled: ENABLED_STATUSES.includes(selectedViolation.status)
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (state?.success) {
|
if (state?.success) {
|
||||||
toast.success(t('the-complaint-has-been-registered'));
|
toast.success(t('the-claims-has-been-registered'));
|
||||||
|
|
||||||
const newStatus = selectedViolation.status === 'COMPLAINT_IN_WORK'
|
const newStatus = selectedViolation.status === 'COMPLAINT_IN_WORK'
|
||||||
? 'COMPLAINT_AND_LEGAL_IN_WORK'
|
? 'COMPLAINT_AND_LEGAL_IN_WORK'
|
||||||
@@ -57,7 +61,7 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
|||||||
setLocalViolation(prev => ({
|
setLocalViolation(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
status: newStatus
|
status: newStatus
|
||||||
}));
|
}))
|
||||||
|
|
||||||
updateStatusHandler(selectedViolation.id, newStatus);
|
updateStatusHandler(selectedViolation.id, newStatus);
|
||||||
} else if (state && !state.success) {
|
} else if (state && !state.success) {
|
||||||
@@ -65,36 +69,41 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
|||||||
if (state.errorMessage?.server) {
|
if (state.errorMessage?.server) {
|
||||||
toast.warning(t(state.errorMessage.server));
|
toast.warning(t(state.errorMessage.server));
|
||||||
} else {
|
} else {
|
||||||
toast.warning(t('the-complaint-has-not-been-registered'));
|
toast.warning(t('the-claims-has-not-been-registered'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [state, updateStatusHandler, selectedViolation.id]);
|
}, [state, updateStatusHandler, selectedViolation.id]);
|
||||||
|
|
||||||
if (!ENABLED_STATUSES.includes(localViolation.status)) {
|
if (!ENABLED_STATUSES.includes(selectedViolation.status)) {
|
||||||
return (
|
return (
|
||||||
<div className="violation-info-case">
|
<div className="violation-info-case">
|
||||||
<div className="note-form">
|
<div className="note-form">
|
||||||
<form action={formAction}>
|
<form action={formAction}>
|
||||||
<input type="hidden" name="violationId" value={localViolation.id} />
|
<input type="hidden" name="violationId" value={selectedViolation.id} />
|
||||||
<div
|
<div
|
||||||
className="note-form-flex"
|
className="note-form-flex"
|
||||||
>
|
>
|
||||||
<section
|
<section
|
||||||
|
className="note-form-section"
|
||||||
|
>
|
||||||
|
<div
|
||||||
className="note-form-group"
|
className="note-form-group"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="note-case-label"
|
className="note-case-label"
|
||||||
htmlFor="caseTitle"
|
htmlFor="caseTitle"
|
||||||
>
|
>
|
||||||
{/* {t('case-name')} */}
|
{t('case-name')}
|
||||||
Название претензии
|
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="caseTitle"
|
name="caseTitle"
|
||||||
className="note-case-input"
|
className="note-case-input"
|
||||||
placeholder={t('fill-title-of-the-case') + '...'}
|
placeholder={t('fill-title-of-the-case') + '...'}
|
||||||
|
defaultValue={state?.previousCaseTitle ? state?.previousCaseTitle : ''}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{state?.errorMessage?.caseTitle && (
|
{state?.errorMessage?.caseTitle && (
|
||||||
<p
|
<p
|
||||||
className="text-sm text-red-500"
|
className="text-sm text-red-500"
|
||||||
@@ -113,6 +122,9 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
|||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
<section
|
<section
|
||||||
|
className="note-form-section"
|
||||||
|
>
|
||||||
|
<div
|
||||||
className="note-form-group"
|
className="note-form-group"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
@@ -126,7 +138,10 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
|||||||
name="amount"
|
name="amount"
|
||||||
className="note-case-input"
|
className="note-case-input"
|
||||||
placeholder={t('enter-the-amount-of-damage') + '...'}
|
placeholder={t('enter-the-amount-of-damage') + '...'}
|
||||||
|
defaultValue={state?.previousAmount ? state?.previousAmount : ''}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{state?.errorMessage?.amount && (
|
{state?.errorMessage?.amount && (
|
||||||
<p
|
<p
|
||||||
className="text-sm text-red-500"
|
className="text-sm text-red-500"
|
||||||
@@ -147,7 +162,10 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
className="note-textarea-wrapper"
|
className="note-form-section"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="note-form-group"
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className="note-case-label"
|
className="note-case-label"
|
||||||
@@ -155,12 +173,16 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
|||||||
>
|
>
|
||||||
{t('text-of-the-case')}
|
{t('text-of-the-case')}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
name="note"
|
name="note"
|
||||||
className="note-textarea"
|
className="note-textarea"
|
||||||
placeholder={t('fill-text-of-the-case') + '...'}
|
placeholder={t('fill-text-of-the-case') + '...'}
|
||||||
|
defaultValue={state?.previousText ? state?.previousText : ''}
|
||||||
>
|
>
|
||||||
</textarea>
|
</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
{state?.errorMessage?.textArea && (
|
{state?.errorMessage?.textArea && (
|
||||||
<p
|
<p
|
||||||
className="text-sm text-red-500"
|
className="text-sm text-red-500"
|
||||||
@@ -219,47 +241,71 @@ export default function CaseViolation({ selectedViolation, updateStatusHandler }
|
|||||||
<div key={item.id}>
|
<div key={item.id}>
|
||||||
<div className="complaint-details">
|
<div className="complaint-details">
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
|
||||||
<h5>{item.name}</h5>
|
<h5>{item.name}</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
<div className="complaint-details-title">
|
||||||
<span className="complaint-details-title">{t('Status')}: {item.type}</span>
|
{t('Status')}:
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="complaint-details-content"
|
||||||
|
>
|
||||||
|
{item.type}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
<div className="complaint-details-title">
|
||||||
<span className="complaint-details-title">{t('date-of-creation')}: {item.createdAt ? `${formatDate(item.createdAt)}: ${formatDateTime(item.createdAt)}` : '---'}</span>
|
{t('date-of-creation')}:
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="complaint-details-content"
|
||||||
|
>
|
||||||
|
{item.created_at ? `${formatDate(item.created_at)}: ${formatDateTime(item.created_at)}` : '---'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
<div className="complaint-details-title">
|
||||||
<span className="complaint-details-title">{t('date-of-update')}: {item.updatedAt ? `${formatDate(item.updatedAt)}: ${formatDateTime(item.updatedAt)}` : '---'}</span>
|
{t('date-of-update')}:
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="complaint-details-content"
|
||||||
|
>
|
||||||
|
{item.updated_at ? `${formatDate(item.updated_at)}: ${formatDateTime(item.updated_at)}` : '---'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
<div className="complaint-details-title">
|
||||||
<span className="complaint-details-title">{t('amount-of-damage')}: {item.amount}</span>
|
{t('amount-of-damage')}:
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="complaint-details-content"
|
||||||
|
>
|
||||||
|
{item.amount}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
<div className="complaint-details-title">
|
||||||
<span className="complaint-details-title">{t('lawyer')}: {item.lawyer}</span>
|
{t('lawyer')}:
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="complaint-details-content"
|
||||||
|
>
|
||||||
|
{item.lawyer}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
<div className="complaint-details-title">
|
||||||
<span className="complaint-details-title">{t('description')}:
|
{t('description')}:
|
||||||
<br />
|
</div>
|
||||||
|
<div
|
||||||
|
className="complaint-details-content"
|
||||||
|
>
|
||||||
{item.description}
|
{item.description}
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations';
|
import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations';
|
||||||
import { useActionState, useEffect, useState } from 'react';
|
import { useActionState, useEffect, useState, SetStateAction } from 'react';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { createComplaint, fetchComplainInfo } from '@/app/actions/violationActions';
|
import { createComplaint, fetchComplainInfo, MatchStatus } from '@/app/actions/violationActions';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { MatchStatus } from '@/app/actions/violationActions';
|
|
||||||
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
import ModalWindow from '@/app/components/ModalWindow';
|
||||||
|
|
||||||
interface complaintInfo {
|
interface complaintInfo {
|
||||||
id: number,
|
id: number,
|
||||||
@@ -23,27 +23,29 @@ interface complaintInfo {
|
|||||||
const ENABLED_STATUSES = ['COMPLAINT_IN_WORK', 'COMPLAINT_AND_LEGAL_IN_WORK'];
|
const ENABLED_STATUSES = ['COMPLAINT_IN_WORK', 'COMPLAINT_AND_LEGAL_IN_WORK'];
|
||||||
type UpdateStatusHandler = (id: number, status: MatchStatus) => Promise<boolean>;
|
type UpdateStatusHandler = (id: number, status: MatchStatus) => Promise<boolean>;
|
||||||
|
|
||||||
export default function CaseComplaint({ selectedViolation, updateStatusHandler }: { selectedViolation: ViolationFileDetail, updateStatusHandler: UpdateStatusHandler }) {
|
export default function CaseComplaint({ selectedViolation, updateStatusHandler, setLocalViolation }:
|
||||||
|
{
|
||||||
|
selectedViolation: ViolationFileDetail,
|
||||||
|
updateStatusHandler: UpdateStatusHandler,
|
||||||
|
setLocalViolation: React.Dispatch<SetStateAction<ViolationFileDetail>>
|
||||||
|
}
|
||||||
|
) {
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const [localViolation, setLocalViolation] = useState<ViolationFileDetail>(selectedViolation);
|
|
||||||
const [noteText, setNoteText] = useState('');
|
const [noteText, setNoteText] = useState('');
|
||||||
const [state, formAction, isPending] = useActionState(createComplaint, undefined);
|
const [state, formAction, isPending] = useActionState(createComplaint, undefined);
|
||||||
|
const [isTemplateModalOpen, setIsTemplateModalOpen] = useState(false);
|
||||||
useEffect(() => {
|
const [templateName, setTemplateName] = useState('');
|
||||||
setLocalViolation(selectedViolation);
|
|
||||||
}, [selectedViolation]);
|
|
||||||
|
|
||||||
const { data: complainInfo, isLoading: isLoadingComplain } = useQuery({
|
const { data: complainInfo, isLoading: isLoadingComplain } = useQuery({
|
||||||
queryKey: ['complainInfo', localViolation.id],
|
queryKey: ['complainInfo', selectedViolation.id],
|
||||||
queryFn: () => fetchComplainInfo(localViolation.id),
|
queryFn: () => fetchComplainInfo(selectedViolation.id),
|
||||||
select: (data) => {
|
select: (data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled: ENABLED_STATUSES.includes(localViolation.status),
|
enabled: ENABLED_STATUSES.includes(selectedViolation.status),
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -57,7 +59,7 @@ export default function CaseComplaint({ selectedViolation, updateStatusHandler }
|
|||||||
setLocalViolation(prev => ({
|
setLocalViolation(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
status: newStatus
|
status: newStatus
|
||||||
}));
|
}))
|
||||||
|
|
||||||
updateStatusHandler(selectedViolation.id, newStatus);
|
updateStatusHandler(selectedViolation.id, newStatus);
|
||||||
} else if (state && !state.success) {
|
} else if (state && !state.success) {
|
||||||
@@ -79,19 +81,52 @@ export default function CaseComplaint({ selectedViolation, updateStatusHandler }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ENABLED_STATUSES.includes(localViolation.status)) {
|
function openTemplateSaveModal() {
|
||||||
return (
|
if (!noteText.trim()) {
|
||||||
<div className="violation-info-case">
|
toast.warning(t('please-enter-complaint-text-first'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTemplateName('');
|
||||||
|
setIsTemplateModalOpen(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveTemplateHandler() {
|
||||||
|
if (!templateName.trim()) {
|
||||||
|
toast.warning(t('please-enter-template-name'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// пока нету эндпоинта оставлю заглушку
|
||||||
|
console.log('СОХРАНЕНИЕ ШАБЛОНА');
|
||||||
|
console.log('Название шаблона:', templateName);
|
||||||
|
console.log('Текст жалобы:', noteText);
|
||||||
|
console.log('========================');
|
||||||
|
|
||||||
|
toast.success(t('template-saved-successfully') || 'Шаблон успешно сохранен');
|
||||||
|
setIsTemplateModalOpen(false);
|
||||||
|
setTemplateName('');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ENABLED_STATUSES.includes(selectedViolation.status)) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="violation-info-case">
|
||||||
<div className="note-form">
|
<div className="note-form">
|
||||||
<form action={formAction}>
|
<form action={formAction}>
|
||||||
<input type="hidden" name="violationId" value={localViolation.id} />
|
<input type="hidden" name="violationId" value={selectedViolation.id} />
|
||||||
|
<section
|
||||||
|
className="note-form-section"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="note-form-group"
|
||||||
|
>
|
||||||
<label
|
<label
|
||||||
className="note-case-label"
|
className="note-case-label"
|
||||||
htmlFor="note"
|
htmlFor="note"
|
||||||
>
|
>
|
||||||
{t('text-of-the-complaint')}
|
{t('text-of-the-complaint')}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<textarea
|
<textarea
|
||||||
name="note"
|
name="note"
|
||||||
className="note-textarea"
|
className="note-textarea"
|
||||||
@@ -100,33 +135,12 @@ export default function CaseComplaint({ selectedViolation, updateStatusHandler }
|
|||||||
onChange={(e) => setNoteText(e.target.value)}
|
onChange={(e) => setNoteText(e.target.value)}
|
||||||
>
|
>
|
||||||
</textarea>
|
</textarea>
|
||||||
<div
|
</div>
|
||||||
className="button-actions"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="btn-s btn-primary-small"
|
|
||||||
disabled={isPending}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Сохранить шаблон
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="btn-s btn-primary-small"
|
|
||||||
disabled={isPending}
|
|
||||||
>
|
|
||||||
{t('submit-violation')}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{state?.errorMessage?.textArea && (
|
{state?.errorMessage?.textArea && (
|
||||||
<p
|
<p
|
||||||
className="text-sm text-red-500"
|
className="text-sm text-red-500"
|
||||||
>
|
>
|
||||||
|
|
||||||
{
|
{
|
||||||
state?.errorMessage?.textArea.split('&').map((e, index) => {
|
state?.errorMessage?.textArea.split('&').map((e, index) => {
|
||||||
return (
|
return (
|
||||||
@@ -139,9 +153,28 @@ export default function CaseComplaint({ selectedViolation, updateStatusHandler }
|
|||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="button-actions"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn-s btn-primary-small"
|
||||||
|
onClick={openTemplateSaveModal}
|
||||||
|
>
|
||||||
|
{t('save-template')}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn-s btn-primary-small"
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{t('submit-violation')}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="samples-wrapper"
|
className="samples-wrapper"
|
||||||
@@ -241,6 +274,63 @@ export default function CaseComplaint({ selectedViolation, updateStatusHandler }
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ModalWindow
|
||||||
|
state={isTemplateModalOpen}
|
||||||
|
callBack={setIsTemplateModalOpen}
|
||||||
|
>
|
||||||
|
<div className="template-modal-content">
|
||||||
|
<div className="template-modal-header">
|
||||||
|
<h3>{t('save-template')}</h3>
|
||||||
|
<button
|
||||||
|
className="template-modal-close"
|
||||||
|
onClick={() => setIsTemplateModalOpen(false)}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="template-modal-body">
|
||||||
|
<div className="template-form-group">
|
||||||
|
<label htmlFor="templateName">
|
||||||
|
{t('template-name')}:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="templateName"
|
||||||
|
type="text"
|
||||||
|
className="template-name-input"
|
||||||
|
placeholder={t('enter-template-name')}
|
||||||
|
value={templateName}
|
||||||
|
onChange={(e) => setTemplateName(e.target.value)}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="template-preview">
|
||||||
|
<div className="template-preview-label">
|
||||||
|
{t('complaint-text-preview')}
|
||||||
|
</div>
|
||||||
|
<div className="template-preview-text">
|
||||||
|
{noteText || t('no-text')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="template-modal-footer">
|
||||||
|
<button
|
||||||
|
className="btn-s btn-primary-small"
|
||||||
|
onClick={() => setIsTemplateModalOpen(false)}
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn-s btn-resolve"
|
||||||
|
onClick={saveTemplateHandler}
|
||||||
|
>
|
||||||
|
{t('save')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ModalWindow>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (isLoadingComplain) {
|
if (isLoadingComplain) {
|
||||||
@@ -260,49 +350,48 @@ export default function CaseComplaint({ selectedViolation, updateStatusHandler }
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="violation-info-case">
|
<div className="violation-info-case">
|
||||||
{complainInfo?.body.content ? (
|
{complainInfo?.body ? (
|
||||||
<>
|
<div>
|
||||||
{complainInfo.body.content.map((item: complaintInfo) => {
|
|
||||||
return (
|
|
||||||
<div key={item.id}>
|
|
||||||
<div className="complaint-details">
|
<div className="complaint-details">
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
<div className="complaint-details-header">
|
||||||
<h5>COM-{item.id}</h5>
|
<h5>COM-{complainInfo.body?.id}</h5>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
<div className="complaint-details-title">{t('Status')}:</div>
|
||||||
<span className="complaint-details-title">{t('Status')}:</span> {item.status}
|
<div className="complaint-details-content">
|
||||||
|
{complainInfo.body?.status}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
<div className="complaint-details-title">{t('date-of-creation')}:</div>
|
||||||
<span className="complaint-details-title">{t('date-of-creation')}: {item.created_at ? `${formatDate(item.created_at)}: ${formatDateTime(item.created_at)}` : '---'}</span>
|
<div className="complaint-details-content">
|
||||||
|
{complainInfo.body?.created_at
|
||||||
|
? `${formatDate(complainInfo.body.created_at)}: ${formatDateTime(complainInfo.body.created_at)}`
|
||||||
|
: '---'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
<div className="complaint-details-title">{t('date-of-update')}:</div>
|
||||||
<span className="complaint-details-title">{t('date-of-update')}: {item.updated_at ? `${formatDate(item.updated_at)}: ${formatDateTime(item.updated_at)}` : '---'}</span>
|
<div className="complaint-details-content">
|
||||||
|
{complainInfo.body?.updated_at
|
||||||
|
? `${formatDate(complainInfo.body.updated_at)}: ${formatDateTime(complainInfo.body.updated_at)}`
|
||||||
|
: '---'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="complaint-details-item">
|
<div className="complaint-details-item">
|
||||||
<div className="complaint-details-header">
|
<div className="complaint-details-title">{t('text-of-the-complaint')}:</div>
|
||||||
<span className="complaint-details-title">{t('text-of-the-complaint')}:
|
<div className="complaint-details-content">
|
||||||
<br />
|
{complainInfo.body?.complaint_text}
|
||||||
{item.complaint_text}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
{t('no-information-about-the-complaint')}
|
{t('no-information-about-the-complaint')}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default function FilePageViolationEpmtyScreen() {
|
|||||||
className="violation-info"
|
className="violation-info"
|
||||||
>
|
>
|
||||||
<h4
|
<h4
|
||||||
className="violation-info-title"
|
className="violation-info-title not-selected-match"
|
||||||
>
|
>
|
||||||
{t('to-view-a-file-match-click-on-the-card-from-the-list')}
|
{t('to-view-a-file-match-click-on-the-card-from-the-list')}
|
||||||
</h4>
|
</h4>
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import CaseComplaint from '@/app/ui/file-page/violation-table/case-complaint';
|
import CaseComplaint from '@/app/ui/file-page/violation-table/case-complaint';
|
||||||
import CaseLegal from '@/app/ui/file-page/violation-table/case-legal';
|
import CaseLegal from '@/app/ui/file-page/violation-table/case-claims';
|
||||||
import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations';
|
import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations';
|
||||||
import { MatchStatus } from '@/app/actions/violationActions';
|
import { MatchStatus } from '@/app/actions/violationActions';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo';
|
import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
type UpdateStatusHandler = (id: number, status: MatchStatus) => Promise<boolean>;
|
type UpdateStatusHandler = (id: number, status: MatchStatus) => Promise<boolean>;
|
||||||
|
|
||||||
const STATUSES_IN_WORK = ['COMPLAINT_IN_WORK', 'LEGAL_IN_WORK', 'COMPLAINT_AND_LEGAL_IN_WORK'];
|
const STATUSES_IN_WORK = ['COMPLAINT_IN_WORK', 'LEGAL_IN_WORK', 'COMPLAINT_AND_LEGAL_IN_WORK'];
|
||||||
|
|
||||||
type TabType = 'complaint' | 'legal'
|
type TabType = 'complaint' | 'claim'
|
||||||
|
|
||||||
export default function FilePageViolationInfoTabs({ selectedViolation, updateStatusHandler }: { selectedViolation: ViolationFileDetail, updateStatusHandler: UpdateStatusHandler }) {
|
export default function FilePageViolationInfoTabs({ selectedViolation, updateStatusHandler, caseType }: { selectedViolation: ViolationFileDetail, updateStatusHandler: UpdateStatusHandler, caseType?: 'claim' | 'complaint' }) {
|
||||||
|
const t = useTranslations('Global');
|
||||||
const [activeTab, setActiveTab] = useState<TabType>('complaint');
|
const [activeTab, setActiveTab] = useState<TabType>('complaint');
|
||||||
const [showTabs, setShowTabs] = useState(false);
|
const [showTabs, setShowTabs] = useState(false);
|
||||||
const isInWork = STATUSES_IN_WORK.includes(selectedViolation.status);
|
const isInWork = STATUSES_IN_WORK.includes(selectedViolation.status);
|
||||||
@@ -23,20 +25,16 @@ export default function FilePageViolationInfoTabs({ selectedViolation, updateSta
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalViolation(selectedViolation);
|
setLocalViolation(selectedViolation);
|
||||||
}, [selectedViolation]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
if (caseType) {
|
||||||
console.log(localViolation.status);
|
setActiveTab(caseType)
|
||||||
}, [localViolation]);
|
}
|
||||||
|
}, [selectedViolation]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowTabs(false);
|
setShowTabs(false);
|
||||||
}, [localViolation.id]);
|
}, [localViolation.id]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(userData);
|
|
||||||
}, [userData])
|
|
||||||
|
|
||||||
if (userData?.verifiedStatus !== "VERIFIED") {
|
if (userData?.verifiedStatus !== "VERIFIED") {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -61,7 +59,9 @@ export default function FilePageViolationInfoTabs({ selectedViolation, updateSta
|
|||||||
if (!isInWork && !showTabs) {
|
if (!isInWork && !showTabs) {
|
||||||
return (
|
return (
|
||||||
<div className="violation-info-choice">
|
<div className="violation-info-choice">
|
||||||
<h4>Вы можете</h4>
|
<h4>
|
||||||
|
{t('you-can')}
|
||||||
|
</h4>
|
||||||
<div className="violation-info-choice-buttons">
|
<div className="violation-info-choice-buttons">
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
@@ -70,21 +70,21 @@ export default function FilePageViolationInfoTabs({ selectedViolation, updateSta
|
|||||||
setActiveTab('complaint');
|
setActiveTab('complaint');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Подать жалобу
|
{t('file-a-complaint')}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
или
|
{t('or')}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowTabs(true);
|
setShowTabs(true);
|
||||||
setActiveTab('legal');
|
setActiveTab('claim');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Подать притензию
|
{t('file-a-claim')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,13 +98,13 @@ export default function FilePageViolationInfoTabs({ selectedViolation, updateSta
|
|||||||
className={`btn-s btn-primary ${activeTab === 'complaint' ? 'active' : ''} ${localViolation.status === 'COMPLAINT_IN_WORK' || localViolation.status === 'COMPLAINT_AND_LEGAL_IN_WORK' ? '' : 'action'}`}
|
className={`btn-s btn-primary ${activeTab === 'complaint' ? 'active' : ''} ${localViolation.status === 'COMPLAINT_IN_WORK' || localViolation.status === 'COMPLAINT_AND_LEGAL_IN_WORK' ? '' : 'action'}`}
|
||||||
onClick={() => setActiveTab('complaint')}
|
onClick={() => setActiveTab('complaint')}
|
||||||
>
|
>
|
||||||
{localViolation.status === 'COMPLAINT_IN_WORK' || localViolation.status === 'COMPLAINT_AND_LEGAL_IN_WORK' ? 'Жалоба' : 'Подать жалобу'}
|
{localViolation.status === 'COMPLAINT_IN_WORK' || localViolation.status === 'COMPLAINT_AND_LEGAL_IN_WORK' ? t('complaint') : t('file-a-complaint')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`btn-s btn-primary ${activeTab === 'legal' ? 'active' : ''} ${localViolation.status === 'LEGAL_IN_WORK' || localViolation.status === 'COMPLAINT_AND_LEGAL_IN_WORK' ? '' : 'action'}`}
|
className={`btn-s btn-primary ${activeTab === 'claim' ? 'active' : ''} ${localViolation.status === 'LEGAL_IN_WORK' || localViolation.status === 'COMPLAINT_AND_LEGAL_IN_WORK' ? '' : 'action'}`}
|
||||||
onClick={() => setActiveTab('legal')}
|
onClick={() => setActiveTab('claim')}
|
||||||
>
|
>
|
||||||
{localViolation.status === 'LEGAL_IN_WORK' || localViolation.status === 'COMPLAINT_AND_LEGAL_IN_WORK' ? 'Претензия' : 'Подать претензию'}
|
{localViolation.status === 'LEGAL_IN_WORK' || localViolation.status === 'COMPLAINT_AND_LEGAL_IN_WORK' ? t('case') : t('file-a-claim')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="violation-info-content">
|
<div className="violation-info-content">
|
||||||
@@ -113,13 +113,15 @@ export default function FilePageViolationInfoTabs({ selectedViolation, updateSta
|
|||||||
key={"complaint_" + localViolation.id}
|
key={"complaint_" + localViolation.id}
|
||||||
selectedViolation={localViolation}
|
selectedViolation={localViolation}
|
||||||
updateStatusHandler={updateStatusHandler}
|
updateStatusHandler={updateStatusHandler}
|
||||||
|
setLocalViolation={setLocalViolation}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{activeTab === 'legal' && (
|
{activeTab === 'claim' && (
|
||||||
<CaseLegal
|
<CaseLegal
|
||||||
key={"legal_" + localViolation.id}
|
key={"legal_" + localViolation.id}
|
||||||
selectedViolation={localViolation}
|
selectedViolation={localViolation}
|
||||||
updateStatusHandler={updateStatusHandler}
|
updateStatusHandler={updateStatusHandler}
|
||||||
|
setLocalViolation={setLocalViolation}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
|||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations';
|
import { ViolationFileDetail } from '@/app/hooks/react-query/useFileViolations';
|
||||||
import CaseComplaint from '@/app/ui/file-page/violation-table/case-complaint';
|
import CaseComplaint from '@/app/ui/file-page/violation-table/case-complaint';
|
||||||
import CaseViolation from '@/app/ui/file-page/violation-table/case-legal';
|
import CaseViolation from '@/app/ui/file-page/violation-table/case-claims';
|
||||||
import { updateMatchStatus } from '@/app/actions/violationActions';
|
import { updateMatchStatus } from '@/app/actions/violationActions';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
@@ -16,8 +16,22 @@ import Image from 'next/image';
|
|||||||
import FilePageViolationInfoTabs from '@/app/ui/file-page/violation-table/file-page-violation-info-tabs';
|
import FilePageViolationInfoTabs from '@/app/ui/file-page/violation-table/file-page-violation-info-tabs';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { IconContentCopy, IconFollowToLink, IconFullScreen } from '@/app/ui/icons/icons';
|
import { IconContentCopy, IconFollowToLink, IconFullScreen } from '@/app/ui/icons/icons';
|
||||||
|
import { useViewport } from '@/app/hooks/useViewport';
|
||||||
|
|
||||||
export default function FilePageViolationInfo({ selectedViolation }: { selectedViolation: ViolationFileDetail | null }) {
|
const IN_WORK_STATUS = ['COMPLAINT_AND_LEGAL_IN_WORK', 'LEGAL_IN_WORK', 'COMPLAINT_IN_WORK'];
|
||||||
|
|
||||||
|
export default function FilePageViolationInfo(
|
||||||
|
{
|
||||||
|
selectedViolation,
|
||||||
|
caseType,
|
||||||
|
callBackSelectedViolation
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
selectedViolation: ViolationFileDetail | null,
|
||||||
|
caseType?: 'claim' | 'complaint',
|
||||||
|
callBackSelectedViolation: React.Dispatch<React.SetStateAction<ViolationFileDetail | null>>,
|
||||||
|
}
|
||||||
|
) {
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
const tStatus = useTranslations('Match-status');
|
const tStatus = useTranslations('Match-status');
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -26,6 +40,7 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
|
|||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
const viewport = useViewport();
|
||||||
|
|
||||||
const openInNewTab = () => {
|
const openInNewTab = () => {
|
||||||
if (selectedViolation?.url) {
|
if (selectedViolation?.url) {
|
||||||
@@ -51,6 +66,16 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
|
|||||||
}
|
}
|
||||||
queryClient.invalidateQueries({ queryKey: ['fileViolations'] });
|
queryClient.invalidateQueries({ queryKey: ['fileViolations'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['complainInfo', id] });
|
queryClient.invalidateQueries({ queryKey: ['complainInfo', id] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['violationData'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['lastCases'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['lastComplaints'] });
|
||||||
|
|
||||||
|
if (status === 'NOT_OWNER_FILE') {
|
||||||
|
console.log('NOT_OWNER_FILE');
|
||||||
|
setLocalViolation(null);
|
||||||
|
callBackSelectedViolation(null);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
setLocalViolation(selectedViolation);
|
setLocalViolation(selectedViolation);
|
||||||
@@ -125,6 +150,30 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
|
|||||||
.catch();
|
.catch();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMaxLengthByWidth = (): number => {
|
||||||
|
if (viewport.device === 'MOBILE') return 22;
|
||||||
|
if (viewport.device === 'TABLET') return 50;
|
||||||
|
if (viewport.device === 'DESKTOP') return 100;
|
||||||
|
return 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cutFileName = (fileName: string) => {
|
||||||
|
const MAX_FILE_NAME_LENGTH = getMaxLengthByWidth();
|
||||||
|
const nameWithoutExtension = fileName.substring(0, MAX_FILE_NAME_LENGTH);
|
||||||
|
|
||||||
|
if (fileName.length <= MAX_FILE_NAME_LENGTH) {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxNameLength = MAX_FILE_NAME_LENGTH;
|
||||||
|
|
||||||
|
if (maxNameLength <= 0) {
|
||||||
|
return '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameWithoutExtension.substring(0, maxNameLength) + '...';
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="violation-info"
|
className="violation-info"
|
||||||
@@ -161,7 +210,7 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
|
|||||||
target.src = '/images/no-image.png';
|
target.src = '/images/no-image.png';
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="violation-info-image-buttons">
|
<div className="violation-info-image-buttons">
|
||||||
<button
|
<button
|
||||||
className="image-btn image-btn-view"
|
className="image-btn image-btn-view"
|
||||||
@@ -179,15 +228,29 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="violation-info-right-side"
|
className="violation-info-right-side"
|
||||||
>
|
>
|
||||||
<h3
|
<h3
|
||||||
className="violation-info-title"
|
className="violation-info-title"
|
||||||
>
|
>
|
||||||
{selectedViolation?.page_title}
|
{selectedViolation?.page_title ? cutFileName(selectedViolation?.page_title) : '#'}
|
||||||
</h3>
|
</h3>
|
||||||
|
<div
|
||||||
|
className="mt-auto"
|
||||||
|
>
|
||||||
|
{selectedViolation?.country && (
|
||||||
|
<div
|
||||||
|
className="violation-info-source"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{t('country')}:
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
{selectedViolation?.country}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div
|
<div
|
||||||
className="violation-info-source"
|
className="violation-info-source"
|
||||||
>
|
>
|
||||||
@@ -201,7 +264,7 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
|
|||||||
<span
|
<span
|
||||||
className="source"
|
className="source"
|
||||||
>
|
>
|
||||||
{selectedViolation?.page_url ? cleanUrlForDisplay(selectedViolation?.page_url) : selectedViolation?.page_url}
|
{selectedViolation?.page_url ? cutFileName(cleanUrlForDisplay(selectedViolation?.page_url)) : selectedViolation?.page_url}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
@@ -221,7 +284,7 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="violation-info-status"
|
className="violation-info-status"
|
||||||
>
|
>
|
||||||
@@ -271,6 +334,21 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
|
|||||||
<div
|
<div
|
||||||
className="violation-info-action"
|
className="violation-info-action"
|
||||||
>
|
>
|
||||||
|
{!IN_WORK_STATUS.includes(selectedViolation.status) && (
|
||||||
|
<button
|
||||||
|
className="btn-s btn-no-my-file"
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedViolation?.id) {
|
||||||
|
updateStatusHandler(selectedViolation.id, 'NOT_OWNER_FILE');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={isUpdating}
|
||||||
|
>
|
||||||
|
{tStatus('NOT_OWNER_FILE')}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!IN_WORK_STATUS.includes(selectedViolation.status) && (
|
||||||
<button
|
<button
|
||||||
className="btn-s btn-resolve"
|
className="btn-s btn-resolve"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -282,6 +360,7 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
|
|||||||
>
|
>
|
||||||
{t('allow-use')}
|
{t('allow-use')}
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -289,6 +368,7 @@ export default function FilePageViolationInfo({ selectedViolation }: { selectedV
|
|||||||
<FilePageViolationInfoTabs
|
<FilePageViolationInfoTabs
|
||||||
selectedViolation={selectedViolation}
|
selectedViolation={selectedViolation}
|
||||||
updateStatusHandler={updateStatusHandler}
|
updateStatusHandler={updateStatusHandler}
|
||||||
|
caseType={caseType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,11 +15,19 @@ import Image from 'next/image';
|
|||||||
export default function FilePageViolationsList({
|
export default function FilePageViolationsList({
|
||||||
currentPage,
|
currentPage,
|
||||||
fileId,
|
fileId,
|
||||||
status
|
status,
|
||||||
|
violationId,
|
||||||
|
caseType,
|
||||||
|
startDate,
|
||||||
|
endDate
|
||||||
}: {
|
}: {
|
||||||
currentPage: number,
|
currentPage: number,
|
||||||
fileId: string,
|
fileId: string,
|
||||||
status: string | undefined
|
status: string | undefined,
|
||||||
|
violationId?: string | undefined,
|
||||||
|
caseType?: 'claim' | 'complaint',
|
||||||
|
startDate?: string,
|
||||||
|
endDate?: string
|
||||||
}) {
|
}) {
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
const tStatus = useTranslations('Match-status');
|
const tStatus = useTranslations('Match-status');
|
||||||
@@ -34,10 +42,13 @@ export default function FilePageViolationsList({
|
|||||||
current_page: number;
|
current_page: number;
|
||||||
} | null>(null);
|
} | null>(null);
|
||||||
|
|
||||||
const { data: fileViolations, error, isPending } = useFileViolations(fileId, currentPage, status);
|
const { data: fileViolations, error, isPending } = useFileViolations(fileId, currentPage, status, violationId, startDate, endDate);
|
||||||
|
|
||||||
|
function selectHandler(violation: ViolationFileDetail) {
|
||||||
|
setSelectedViolation(violation);
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(fileViolations);
|
|
||||||
if (fileViolations?.violations) {
|
if (fileViolations?.violations) {
|
||||||
setCachedPagination({
|
setCachedPagination({
|
||||||
total_pages: fileViolations.total_pages,
|
total_pages: fileViolations.total_pages,
|
||||||
@@ -45,6 +56,10 @@ export default function FilePageViolationsList({
|
|||||||
current_page: fileViolations.current_page
|
current_page: fileViolations.current_page
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (violationId && fileViolations?.violations.length) {
|
||||||
|
setSelectedViolation(fileViolations?.violations[0]);
|
||||||
|
}
|
||||||
}, [fileViolations]);
|
}, [fileViolations]);
|
||||||
|
|
||||||
const paginationData = fileViolations?.violations ? fileViolations : cachedPagination;
|
const paginationData = fileViolations?.violations ? fileViolations : cachedPagination;
|
||||||
@@ -59,9 +74,12 @@ export default function FilePageViolationsList({
|
|||||||
cachedCurrentPage={paginationData?.current_page}
|
cachedCurrentPage={paginationData?.current_page}
|
||||||
/>;
|
/>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePageChange = (page: number) => {
|
const handlePageChange = (page: number) => {
|
||||||
const params = new URLSearchParams(searchParams);
|
const params = new URLSearchParams(searchParams);
|
||||||
params.set('page', page.toString());
|
params.set('page', page.toString());
|
||||||
|
params.delete('violationId');
|
||||||
|
params.delete('caseType');
|
||||||
router.push(`${pathname}?${params.toString()}`, { scroll: false });
|
router.push(`${pathname}?${params.toString()}`, { scroll: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -74,6 +92,8 @@ export default function FilePageViolationsList({
|
|||||||
}
|
}
|
||||||
setSelectedViolation(null);
|
setSelectedViolation(null);
|
||||||
params.set('page', '1');
|
params.set('page', '1');
|
||||||
|
params.delete('violationId');
|
||||||
|
params.delete('caseType');
|
||||||
router.push(`${pathname}?${params.toString()}`, { scroll: false });
|
router.push(`${pathname}?${params.toString()}`, { scroll: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,10 +117,6 @@ export default function FilePageViolationsList({
|
|||||||
return pages;
|
return pages;
|
||||||
};
|
};
|
||||||
|
|
||||||
function selectHandler(violation: ViolationFileDetail) {
|
|
||||||
setSelectedViolation(violation);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="block-wrapper">
|
<div className="block-wrapper">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
@@ -208,14 +224,15 @@ export default function FilePageViolationsList({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
{/* general info for match */}
|
{/* general info for match */}
|
||||||
<FilePageViolationInfo selectedViolation={selectedViolation} />
|
<FilePageViolationInfo selectedViolation={selectedViolation} caseType={caseType} callBackSelectedViolation={setSelectedViolation} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
{/* pagination */}
|
||||||
<div
|
<div
|
||||||
className="sources-list-pagination"
|
className="sources-list-pagination"
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
{t('total-violations')}: {fileViolations?.total_elements} | {t('page')} {fileViolations?.current_page} {t('out-of')} {fileViolations?.total_pages}
|
{t('page')} <strong>{fileViolations?.current_page + 1} {t('out-of')} {fileViolations?.total_pages}</strong> | {t('total-violations')}: {fileViolations?.total_elements}
|
||||||
</span>
|
</span>
|
||||||
{fileViolations?.total_pages > 1 && (
|
{fileViolations?.total_pages > 1 && (
|
||||||
<div className="pagination-controls">
|
<div className="pagination-controls">
|
||||||
|
|||||||
@@ -256,9 +256,12 @@ export default function RegisterForm() {
|
|||||||
<ConfirmMailModalWindow userId={state?.userId} email={formState?.previousState?.email} onClose={() => { setShowMailConfirmWindow(false) }} />
|
<ConfirmMailModalWindow userId={state?.userId} email={formState?.previousState?.email} onClose={() => { setShowMailConfirmWindow(false) }} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<form action={(e) => {
|
<form
|
||||||
|
action={(e) => {
|
||||||
formAction(e);
|
formAction(e);
|
||||||
}}>
|
}}
|
||||||
|
autoComplete="off"
|
||||||
|
>
|
||||||
<div className={`${styles['form-switcher']}`}>
|
<div className={`${styles['form-switcher']}`}>
|
||||||
<button
|
<button
|
||||||
className={`${styles['form-switcher-button']} ${accountType === 'b2c' ? styles['active'] : ''}`}
|
className={`${styles['form-switcher-button']} ${accountType === 'b2c' ? styles['active'] : ''}`}
|
||||||
@@ -432,6 +435,7 @@ export default function RegisterForm() {
|
|||||||
e.target.value = e.target.value.toLocaleLowerCase();
|
e.target.value = e.target.value.toLocaleLowerCase();
|
||||||
onChangeHandler(e)
|
onChangeHandler(e)
|
||||||
}}
|
}}
|
||||||
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
{formState?.error?.email && (
|
{formState?.error?.email && (
|
||||||
<p className="text-sm text-red-500">
|
<p className="text-sm text-red-500">
|
||||||
@@ -486,6 +490,7 @@ export default function RegisterForm() {
|
|||||||
placeholder={t('password-placeholder')}
|
placeholder={t('password-placeholder')}
|
||||||
defaultValue={formState?.previousState?.password ? formState?.previousState?.password : ''}
|
defaultValue={formState?.previousState?.password ? formState?.previousState?.password : ''}
|
||||||
onChange={onChangeHandler}
|
onChange={onChangeHandler}
|
||||||
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ import { useTranslations } from 'next-intl';
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { IconInfoMessage, IconPolicy, IconFiles, IconAccountBalanceWallet, IconCheckbook, IconGavel } from '@/app/ui/icons/icons';
|
import { IconInfoMessage, IconPolicy, IconFiles, IconAccountBalanceWallet, IconCheckbook, IconGavel } from '@/app/ui/icons/icons';
|
||||||
import { useActiveNotifications } from '@/app/hooks/react-query/useActiveNotificationsList';
|
import { useActiveNotifications } from '@/app/hooks/react-query/useActiveNotificationsList';
|
||||||
import { NotificationBody, notificationsMarkAsRead } from '@/app/actions/notificationActions';
|
import { notificationsMarkAsRead } from '@/app/actions/notificationActions';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { formatDate } from '@/app/lib/formatDate';
|
import { formatDate } from '@/app/lib/formatDate';
|
||||||
|
import { NotificationTextHandler } from '@/app/components/NotificationTextHandler';
|
||||||
|
|
||||||
export default function NotificationsButton() {
|
export default function NotificationsButton() {
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
@@ -121,6 +122,21 @@ export default function NotificationsButton() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getFontSizeByCount = (count: number | undefined) => {
|
||||||
|
if (!count) return '12px';
|
||||||
|
|
||||||
|
const countStr = String(count);
|
||||||
|
|
||||||
|
switch (countStr.length) {
|
||||||
|
case 3:
|
||||||
|
return '10px';
|
||||||
|
case 4:
|
||||||
|
return '8px';
|
||||||
|
default:
|
||||||
|
return '12px';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="notification-wrapper" ref={menuRef}>
|
<div className="notification-wrapper" ref={menuRef}>
|
||||||
<button
|
<button
|
||||||
@@ -132,11 +148,12 @@ export default function NotificationsButton() {
|
|||||||
<svg viewBox="0 0 24 24" fill="currentColor" style={{ width: "20px", height: "20px" }}>
|
<svg viewBox="0 0 24 24" fill="currentColor" style={{ width: "20px", height: "20px" }}>
|
||||||
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"></path>
|
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
{notificationList?.unreadCount !== 0 && (
|
{(notificationList?.unreadCount !== 0 && notificationList?.unreadCount !== undefined) && (
|
||||||
<span
|
<span
|
||||||
className="notification-unread-count"
|
className="notification-unread-count"
|
||||||
|
/* style={{ fontSize: getFontSizeByCount(notificationList?.unreadCount) }} */
|
||||||
>
|
>
|
||||||
{notificationList?.unreadCount ? notificationList?.unreadCount : ''}
|
{notificationList?.unreadCount > 9 ? '9+' : notificationList?.unreadCount}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
@@ -204,7 +221,7 @@ export default function NotificationsButton() {
|
|||||||
className="notification-item-header"
|
className="notification-item-header"
|
||||||
>
|
>
|
||||||
<div className="notification-title">
|
<div className="notification-title">
|
||||||
{norificationTranslate.has(item.type) ? norificationTranslate(item.type) : item.type}
|
{norificationTranslate.has(item.message) ? norificationTranslate(item.message) : item.message}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="notification-data">
|
<div className="notification-data">
|
||||||
@@ -213,7 +230,7 @@ export default function NotificationsButton() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="notification-text">
|
<div className="notification-text">
|
||||||
{norificationTranslate.has(item.message) ? norificationTranslate(item.message) : item.message}
|
<NotificationTextHandler notificationType={item.type} notificationMessage={item.message} notificationContext={item.context} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { logout } from '@/app/actions/auth';
|
|||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo';
|
import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
import {formatDate} from '@/app/lib/formatDate';
|
||||||
|
|
||||||
export default function UserMenuButton() {
|
export default function UserMenuButton() {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
@@ -62,14 +63,16 @@ export default function UserMenuButton() {
|
|||||||
<div className="user-email">
|
<div className="user-email">
|
||||||
{userData?.email ?? ''}
|
{userData?.email ?? ''}
|
||||||
</div>
|
</div>
|
||||||
|
{userData?.tariffInfo?.tariffName && (
|
||||||
<div className="user-subscription">
|
<div className="user-subscription">
|
||||||
<span className="text-[14px]">{t('rate')}: </span>
|
<span className="text-[14px]">{t('rate')}: </span>
|
||||||
<span className="subscription-badge">
|
<span className="subscription-badge">
|
||||||
{userData?.tariffInfo?.tariffName ? userData?.tariffInfo?.tariffName : '---'}
|
{userData?.tariffInfo?.tariffName ? userData?.tariffInfo?.tariffName : '---'}
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
<span className="user-subscription-expire">{t('expires')}: 00.00.00</span>
|
<span className="user-subscription-expire">{t('expires')}: {formatDate(userData?.tariffInfo.endTariff)}</span>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -210,54 +210,84 @@ export function IconCheck() {
|
|||||||
|
|
||||||
export function IconPolicy() {
|
export function IconPolicy() {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" ><path d="M480-80q-139-35-229.5-159.5T160-516v-244l320-120 320 120v244q0 85-29 163.5T688-214L560-342q-18 11-38.5 16.5T480-320q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 22-5.5 42.5T618-398l60 60q20-41 31-86t11-92v-189l-240-90-240 90v189q0 121 68 220t172 132q26-8 49.5-20.5T576-214l56 56q-33 27-71.5 47T480-80Zm56.5-343.5Q560-447 560-480t-23.5-56.5Q513-560 480-560t-56.5 23.5Q400-513 400-480t23.5 56.5Q447-400 480-400t56.5-23.5ZM488-477Z" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon"><path d="M480-80q-139-35-229.5-159.5T160-516v-244l320-120 320 120v244q0 85-29 163.5T688-214L560-342q-18 11-38.5 16.5T480-320q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 22-5.5 42.5T618-398l60 60q20-41 31-86t11-92v-189l-240-90-240 90v189q0 121 68 220t172 132q26-8 49.5-20.5T576-214l56 56q-33 27-71.5 47T480-80Zm56.5-343.5Q560-447 560-480t-23.5-56.5Q513-560 480-560t-56.5 23.5Q400-513 400-480t23.5 56.5Q447-400 480-400t56.5-23.5ZM488-477Z" /></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IconFiles() {
|
export function IconFiles() {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" ><path d="M160-160q-33 0-56.5-23.5T80-240v-400q0-33 23.5-56.5T160-720h240l80-80h320q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm73-280h207v-207L233-440Zm-73-40 160-160H160v160Zm0 120v120h640v-480H520v280q0 33-23.5 56.5T440-360H160Zm280-160Z" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon"><path d="M160-160q-33 0-56.5-23.5T80-240v-400q0-33 23.5-56.5T160-720h240l80-80h320q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm73-280h207v-207L233-440Zm-73-40 160-160H160v160Zm0 120v120h640v-480H520v280q0 33-23.5 56.5T440-360H160Zm280-160Z" /></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IconAccountBalanceWallet() {
|
export function IconAccountBalanceWallet() {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" ><path d="M200-200v-560 560Zm0 80q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v100h-80v-100H200v560h560v-100h80v100q0 33-23.5 56.5T760-120H200Zm320-160q-33 0-56.5-23.5T440-360v-240q0-33 23.5-56.5T520-680h280q33 0 56.5 23.5T880-600v240q0 33-23.5 56.5T800-280H520Zm280-80v-240H520v240h280Zm-117.5-77.5Q700-455 700-480t-17.5-42.5Q665-540 640-540t-42.5 17.5Q580-505 580-480t17.5 42.5Q615-420 640-420t42.5-17.5Z" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon"><path d="M200-200v-560 560Zm0 80q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v100h-80v-100H200v560h560v-100h80v100q0 33-23.5 56.5T760-120H200Zm320-160q-33 0-56.5-23.5T440-360v-240q0-33 23.5-56.5T520-680h280q33 0 56.5 23.5T880-600v240q0 33-23.5 56.5T800-280H520Zm280-80v-240H520v240h280Zm-117.5-77.5Q700-455 700-480t-17.5-42.5Q665-540 640-540t-42.5 17.5Q580-505 580-480t17.5 42.5Q615-420 640-420t42.5-17.5Z" /></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IconCheckbook() {
|
export function IconCheckbook() {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" ><path d="M240-360h280l80-80H240v80Zm0-160h240v-80H240v80Zm-80-160v400h280l-80 80H80v-560h800v120h-80v-40H160Zm756 212q5 5 5 11t-5 11l-36 36-70-70 36-36q5-5 11-5t11 5l48 48ZM520-120v-70l266-266 70 70-266 266h-70ZM160-680v400-400Z" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon"><path d="M240-360h280l80-80H240v80Zm0-160h240v-80H240v80Zm-80-160v400h280l-80 80H80v-560h800v120h-80v-40H160Zm756 212q5 5 5 11t-5 11l-36 36-70-70 36-36q5-5 11-5t11 5l48 48ZM520-120v-70l266-266 70 70-266 266h-70ZM160-680v400-400Z" /></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IconGavel() {
|
export function IconGavel() {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" ><path d="M160-120v-80h480v80H160Zm226-194L160-540l84-86 228 226-86 86Zm254-254L414-796l86-84 226 226-86 86Zm184 408L302-682l56-56 522 522-56 56Z" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon"><path d="M160-120v-80h480v80H160Zm226-194L160-540l84-86 228 226-86 86Zm254-254L414-796l86-84 226 226-86 86Zm184 408L302-682l56-56 522 522-56 56Z" /></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IconInfoMessage() {
|
export function IconInfoMessage() {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" ><path d="M440-280h80v-240h-80v240Zm68.5-331.5Q520-623 520-640t-11.5-28.5Q497-680 480-680t-28.5 11.5Q440-657 440-640t11.5 28.5Q463-600 480-600t28.5-11.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon"><path d="M440-280h80v-240h-80v240Zm68.5-331.5Q520-623 520-640t-11.5-28.5Q497-680 480-680t-28.5 11.5Q440-657 440-640t11.5 28.5Q463-600 480-600t28.5-11.5ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z" /></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IconContentCopy() {
|
export function IconContentCopy() {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm160-240v-480 480Z" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360Zm0-80h360v-480H360v480ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Zm160-240v-480 480Z" /></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IconFollowToLink() {
|
export function IconFollowToLink() {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z" /></svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IconFullScreen() {
|
export function IconFullScreen() {
|
||||||
return (
|
return (
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><path d="M120-120v-200h80v120h120v80H120Zm520 0v-80h120v-120h80v200H640ZM120-640v-200h200v80H200v120h-80Zm640 0v-120H640v-80h200v200h-80Z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon"><path d="M120-120v-200h80v120h120v80H120Zm520 0v-80h120v-120h80v200H640ZM120-640v-200h200v80H200v120h-80Zm640 0v-120H640v-80h200v200h-80Z" /></svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IconLink() {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon"><path d="M440-280H280q-83 0-141.5-58.5T80-480q0-83 58.5-141.5T280-680h160v80H280q-50 0-85 35t-35 85q0 50 35 85t85 35h160v80ZM320-440v-80h320v80H320Zm200 160v-80h160q50 0 85-35t35-85q0-50-35-85t-85-35H520v-80h160q83 0 141.5 58.5T880-480q0 83-58.5 141.5T680-280H520Z" /></svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IconPerson() {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon">
|
||||||
|
<path d="M367-527q-47-47-47-113t47-113q47-47 113-47t113 47q47 47 47 113t-47 113q-47 47-113 47t-113-47ZM160-160v-112q0-34 17.5-62.5T224-378q62-31 126-46.5T480-440q66 0 130 15.5T736-378q29 15 46.5 43.5T800-272v112H160Zm80-80h480v-32q0-11-5.5-20T700-306q-54-27-109-40.5T480-360q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32Zm296.5-343.5Q560-607 560-640t-23.5-56.5Q513-720 480-720t-56.5 23.5Q400-673 400-640t23.5 56.5Q447-560 480-560t56.5-23.5ZM480-640Zm0 400Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IconGlobe() {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon">
|
||||||
|
<path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-7-.5-14.5T799-507q-5 29-27 48t-52 19h-80q-33 0-56.5-23.5T560-520v-40H400v-80q0-33 23.5-56.5T480-720h40q0-23 12.5-40.5T563-789q-20-5-40.5-8t-42.5-3q-134 0-227 93t-93 227h200q66 0 113 47t47 113v40H400v110q20 5 39.5 7.5T480-160Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function IconCity() {
|
||||||
|
return (
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" className="icon">
|
||||||
|
<path d="M120-120v-560h240v-80l120-120 120 120v240h240v400H120Zm80-80h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm240 320h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm0-160h80v-80h-80v80Zm240 480h80v-80h-80v80Zm0-160h80v-80h-80v80Z" />
|
||||||
|
</svg>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { convertBytes } from '@/app/lib/convertBytes';
|
||||||
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { FileDetails } from '@/app/[locale]/pages/file/[id]/page';
|
||||||
|
import { FileAnAppeal } from '@/app/actions/fileEntity';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
export function FileAppealModalWindow({ fileId, setWindowClose, setWindowChildren }: {
|
||||||
|
fileId: string,
|
||||||
|
setWindowClose: any,
|
||||||
|
setWindowChildren: any
|
||||||
|
}) {
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
const [appealReason, setAppealReason] = useState('');
|
||||||
|
const [additionalInfo, setAdditionalInfo] = useState('');
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
|
const handleSubmitAppeal = async () => {
|
||||||
|
if (!appealReason.trim()) {
|
||||||
|
toast.error(t('please-enter-appeal-reason'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSubmitting(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await FileAnAppeal(fileId, appealReason, additionalInfo);
|
||||||
|
|
||||||
|
if (result?.message_desc === 'Appeal submitted successfully') {
|
||||||
|
toast.success(t('appeal-submitted-successfully'));
|
||||||
|
setWindowClose(false);
|
||||||
|
} else if (result?.message_desc === 'Active appeal already exists for this file') {
|
||||||
|
toast.error(t('active-appeal-already-exists-for-this-file'));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
toast.error(result?.message_desc || t('appeal-submission-failed'));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Appeal submission error:', error);
|
||||||
|
toast.error(t('appeal-submission-failed'));
|
||||||
|
} finally {
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="modal-window-view-file">
|
||||||
|
<div className="modal-window-view-file-content">
|
||||||
|
{/* Форма подачи апелляции */}
|
||||||
|
<div className="file-info-card appeal-form">
|
||||||
|
<div className="info-header">
|
||||||
|
<h4>{t('file-an-appeal')}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="info-item">
|
||||||
|
<div className="info-label">
|
||||||
|
{t('appeal-reason')} <span className="required">*</span>
|
||||||
|
</div>
|
||||||
|
<div className="info-value">
|
||||||
|
<textarea
|
||||||
|
className="appeal-textarea"
|
||||||
|
value={appealReason}
|
||||||
|
onChange={(e) => setAppealReason(e.target.value)}
|
||||||
|
placeholder={t('enter-appeal-reason')}
|
||||||
|
rows={4}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="info-item">
|
||||||
|
<div className="info-label">{t('additional-info')}</div>
|
||||||
|
<div className="info-value">
|
||||||
|
<textarea
|
||||||
|
className="appeal-textarea"
|
||||||
|
value={additionalInfo}
|
||||||
|
onChange={(e) => setAdditionalInfo(e.target.value)}
|
||||||
|
placeholder={t('enter-additional-info')}
|
||||||
|
rows={3}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="appeal-actions">
|
||||||
|
<button
|
||||||
|
className="btn btn-cancel"
|
||||||
|
onClick={() => setWindowClose(false)}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={handleSubmitAppeal}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
>
|
||||||
|
{isSubmitting ? t('submitting') : t('submit-appeal')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,6 +14,11 @@
|
|||||||
"href": "/pages/my-content",
|
"href": "/pages/my-content",
|
||||||
"img": "M20 6h-2.18c.11-.31.18-.65.18-1a2.996 2.996 0 0 0-5.5-1.65l-.5.67-.5-.68C10.96 2.54 10 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1z"
|
"img": "M20 6h-2.18c.11-.31.18-.65.18-1a2.996 2.996 0 0 0-5.5-1.65l-.5.67-.5-.68C10.96 2.54 10 2 9 2 7.34 2 6 3.34 6 5c0 .35.07.69.18 1H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-5-2c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM9 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "tracking",
|
||||||
|
"href": "/pages/tracking",
|
||||||
|
"img": "M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "referral-program",
|
"name": "referral-program",
|
||||||
"href": "/pages/refferals",
|
"href": "/pages/refferals",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
createColumnHelper,
|
createColumnHelper,
|
||||||
RowSelectionState,
|
RowSelectionState,
|
||||||
} from '@tanstack/react-table';
|
} from '@tanstack/react-table';
|
||||||
|
import { NotificationTextHandler } from '@/app/components/NotificationTextHandler';
|
||||||
|
|
||||||
export function NotificationsList() {
|
export function NotificationsList() {
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
@@ -47,42 +47,62 @@ export function NotificationsList() {
|
|||||||
header: ({ table }) => {
|
header: ({ table }) => {
|
||||||
const currentPageRows = table.getCoreRowModel().rows;
|
const currentPageRows = table.getCoreRowModel().rows;
|
||||||
const unreadRowsOnPage = currentPageRows.filter(row => row.original.status !== 'READIED');
|
const unreadRowsOnPage = currentPageRows.filter(row => row.original.status !== 'READIED');
|
||||||
const allUnreadSelectedOnPage = unreadRowsOnPage.every(row => rowSelection[row.original.id]);
|
const allNotificationSelectedOnPage = currentPageRows.every(row => rowSelection[row.original.id]);
|
||||||
|
|
||||||
|
const unreadIdsSet = new Set(unreadRowsOnPage.map(row => row.original.id));
|
||||||
|
const selectedUnreadIds = Object.keys(rowSelection)
|
||||||
|
.map(Number)
|
||||||
|
.filter(id => unreadIdsSet.has(id));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="select-all-header">
|
<div className="select-all-header">
|
||||||
|
{selectedUnreadIds.length > 0 && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (allUnreadSelectedOnPage) {
|
const selectedIds = Object.keys(rowSelection).map(Number);
|
||||||
|
notificationActionHandler(selectedUnreadIds, 'mark-as-read');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('mark-as-read')} ({selectedUnreadIds.length})
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{Object.keys(rowSelection).length > 0 && (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => {
|
||||||
|
const selectedIds = Object.keys(rowSelection).map(Number);
|
||||||
|
notificationActionHandler(selectedIds, 'remove');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('remove')} ({Object.keys(rowSelection).length})
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{allNotifications?.notifications.length !== 0 && (
|
||||||
|
<button
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => {
|
||||||
|
if (allNotificationSelectedOnPage) {
|
||||||
|
|
||||||
const newSelection = { ...rowSelection };
|
const newSelection = { ...rowSelection };
|
||||||
unreadRowsOnPage.forEach(row => {
|
currentPageRows.forEach(row => {
|
||||||
delete newSelection[row.original.id];
|
delete newSelection[row.original.id];
|
||||||
});
|
});
|
||||||
setRowSelection(newSelection);
|
setRowSelection(newSelection);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
const newSelection = { ...rowSelection };
|
const newSelection = { ...rowSelection };
|
||||||
unreadRowsOnPage.forEach(row => {
|
currentPageRows.forEach(row => {
|
||||||
newSelection[row.original.id] = true;
|
newSelection[row.original.id] = true;
|
||||||
});
|
});
|
||||||
setRowSelection(newSelection);
|
setRowSelection(newSelection);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{allUnreadSelectedOnPage ? t('deselect') : t('select-all')}
|
{allNotificationSelectedOnPage ? t('deselect') : t('select-all')}
|
||||||
</button>
|
|
||||||
|
|
||||||
{Object.keys(rowSelection).length > 0 && (
|
|
||||||
<button
|
|
||||||
className="btn btn-primary"
|
|
||||||
onClick={() => {
|
|
||||||
const selectedIds = Object.keys(rowSelection).map(Number);
|
|
||||||
notificationActionHandler(selectedIds, 'mark-as-read');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('mark-as-read')} ({Object.keys(rowSelection).length})
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -94,39 +114,46 @@ export function NotificationsList() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="notification-check">
|
<div className="notification-check">
|
||||||
{!isReadied ? (
|
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRowSelection((prev) => ({
|
setRowSelection((prev) => {
|
||||||
...prev,
|
const newSelection = { ...prev };
|
||||||
[row.original.id]: !prev[row.original.id],
|
|
||||||
}));
|
if (newSelection[row.original.id]) {
|
||||||
|
delete newSelection[row.original.id];
|
||||||
|
} else {
|
||||||
|
newSelection[row.original.id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSelection;
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isSelected && <IconCheck />}
|
{isSelected && <IconCheck />}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
|
||||||
<div className="empty-block"></div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
columnHelper.accessor('message', {
|
columnHelper.accessor('message', {
|
||||||
id: 'message',
|
id: 'message',
|
||||||
header: () => null, // No header text, using custom header above
|
header: () => null,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const item = row.original;
|
const item = row.original;
|
||||||
return (
|
return (
|
||||||
<div className="notification-body">
|
<div className="notification-body">
|
||||||
<div className="notification-header">
|
<div className="notification-header">
|
||||||
{/* Optional header content */}
|
<div
|
||||||
</div>
|
className="notification-title"
|
||||||
<div className="notification-text">
|
>
|
||||||
{norificationTranslate.has(item.message)
|
{norificationTranslate.has(item.message)
|
||||||
? norificationTranslate(item.message)
|
? norificationTranslate(item.message)
|
||||||
: item.message} ({item.id})
|
: item.message} ({item.id})
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="notification-text">
|
||||||
|
<NotificationTextHandler notificationType={item.type} notificationMessage={item.message} notificationContext={item.context} />
|
||||||
|
</div>
|
||||||
<div className="notification-footer">
|
<div className="notification-footer">
|
||||||
<div className="notification-date">
|
<div className="notification-date">
|
||||||
{item.createdAt
|
{item.createdAt
|
||||||
@@ -182,11 +209,9 @@ export function NotificationsList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (response?.success) {
|
if (response?.success) {
|
||||||
|
setRowSelection({});
|
||||||
await queryClient.invalidateQueries({ queryKey: ['activeNotifications'] });
|
await queryClient.invalidateQueries({ queryKey: ['activeNotifications'] });
|
||||||
await queryClient.invalidateQueries({ queryKey: ['allNotifications'] });
|
await queryClient.invalidateQueries({ queryKey: ['allNotifications'] });
|
||||||
|
|
||||||
// Clear selection after successful action
|
|
||||||
setRowSelection({});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = action === 'mark-as-read' ? 'Failed to mark as read' : 'Failed to remove';
|
const errorMessage = action === 'mark-as-read' ? 'Failed to mark as read' : 'Failed to remove';
|
||||||
@@ -313,13 +338,19 @@ export function NotificationsList() {
|
|||||||
<div className="pagination-controls">
|
<div className="pagination-controls">
|
||||||
<button
|
<button
|
||||||
className="arrow"
|
className="arrow"
|
||||||
onClick={() => table.firstPage()}
|
onClick={() => {
|
||||||
|
setRowSelection({});
|
||||||
|
table.firstPage();
|
||||||
|
}}
|
||||||
disabled={!table.getCanPreviousPage()}
|
disabled={!table.getCanPreviousPage()}
|
||||||
>
|
>
|
||||||
<IconDoubleArrowLeft />
|
<IconDoubleArrowLeft />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => table.previousPage()}
|
onClick={() => {
|
||||||
|
setRowSelection({});
|
||||||
|
table.previousPage()
|
||||||
|
}}
|
||||||
className="arrow"
|
className="arrow"
|
||||||
disabled={!table.getCanPreviousPage()}
|
disabled={!table.getCanPreviousPage()}
|
||||||
>
|
>
|
||||||
@@ -330,7 +361,10 @@ export function NotificationsList() {
|
|||||||
{getPageNumbers().map((pageNum, index) => (
|
{getPageNumbers().map((pageNum, index) => (
|
||||||
<button
|
<button
|
||||||
key={index}
|
key={index}
|
||||||
onClick={() => typeof pageNum === 'number' && table.setPageIndex(pageNum)}
|
onClick={() => {
|
||||||
|
setRowSelection({});
|
||||||
|
typeof pageNum === 'number' && table.setPageIndex(pageNum);
|
||||||
|
}}
|
||||||
className={pagination.pageIndex === pageNum ? 'current' : 'other'}
|
className={pagination.pageIndex === pageNum ? 'current' : 'other'}
|
||||||
disabled={pageNum === '...'}
|
disabled={pageNum === '...'}
|
||||||
>
|
>
|
||||||
@@ -340,7 +374,10 @@ export function NotificationsList() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => table.nextPage()}
|
onClick={() => {
|
||||||
|
setRowSelection({});
|
||||||
|
table.nextPage();
|
||||||
|
}}
|
||||||
className="arrow"
|
className="arrow"
|
||||||
disabled={!table.getCanNextPage()}
|
disabled={!table.getCanNextPage()}
|
||||||
>
|
>
|
||||||
@@ -348,7 +385,10 @@ export function NotificationsList() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="arrow"
|
className="arrow"
|
||||||
onClick={() => table.lastPage()}
|
onClick={() => {
|
||||||
|
setRowSelection({});
|
||||||
|
table.lastPage();
|
||||||
|
}}
|
||||||
disabled={!table.getCanNextPage()}
|
disabled={!table.getCanNextPage()}
|
||||||
>
|
>
|
||||||
<IconDoubleArrowRight />
|
<IconDoubleArrowRight />
|
||||||
|
|||||||
@@ -429,9 +429,6 @@ export default function PaymentTabTransactionHistory() {
|
|||||||
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1}
|
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1}
|
||||||
</strong>
|
</strong>
|
||||||
</span>
|
</span>
|
||||||
<span className="pagination-info-files">
|
|
||||||
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pagination-controls">
|
<div className="pagination-controls">
|
||||||
|
|||||||
@@ -402,9 +402,6 @@ export default function InvitationsTable() {
|
|||||||
<strong>
|
<strong>
|
||||||
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1}
|
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1}
|
||||||
</strong>
|
</strong>
|
||||||
</span>
|
|
||||||
<span className="pagination-info-files">
|
|
||||||
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {pluralizeFiles(filteredData.length || 0)}
|
|
||||||
</span> */}
|
</span> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -425,9 +425,6 @@ export default function PaymentsHistory() {
|
|||||||
<strong>
|
<strong>
|
||||||
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1}
|
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1}
|
||||||
</strong>
|
</strong>
|
||||||
</span>
|
|
||||||
<span className="pagination-info-files">
|
|
||||||
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length} {pluralizeFiles(filteredData.length || 0)}
|
|
||||||
</span> */}
|
</span> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export default function AnalyticsCardGeography() {
|
|||||||
<div className="analytics-header">
|
<div className="analytics-header">
|
||||||
<div className="analytics-icon"></div>
|
<div className="analytics-icon"></div>
|
||||||
<h3 className="analytics-title">
|
<h3 className="analytics-title">
|
||||||
{t('geography-of-violations')}
|
{t('geography-of-matches')}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
{violationGeography && (
|
{violationGeography && (
|
||||||
|
|||||||
@@ -2,21 +2,20 @@
|
|||||||
|
|
||||||
import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo';
|
import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useViolationStatistic } from '@/app/hooks/react-query/useViolationStatistic';
|
|
||||||
import { useMonitoringStats } from '@/app/hooks/react-query/useMonitoringStats';
|
import { useMonitoringStats } from '@/app/hooks/react-query/useMonitoringStats';
|
||||||
|
import { useViolationStatistic } from '@/app/hooks/react-query/useViolationStatistic';
|
||||||
|
|
||||||
export default function FinalSummary() {
|
export default function FinalSummary() {
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo();
|
const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo();
|
||||||
const { data: violationStatistic } = useViolationStatistic();
|
|
||||||
const { data: monitoringStats } = useMonitoringStats();
|
const { data: monitoringStats } = useMonitoringStats();
|
||||||
|
const { data: violationStatistic } = useViolationStatistic();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="final-summary">
|
<div className="final-summary">
|
||||||
<div className="summary-header">
|
<div className="summary-header">
|
||||||
<h2 className="summary-title">
|
<h2 className="summary-title">
|
||||||
{t('summary-statistics')} {/* за Декабрь 2025 */}
|
{t('summary-statistics')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="summary-subtitle">
|
<p className="summary-subtitle">
|
||||||
{t('general-indicators-of-the-protection-system')}
|
{t('general-indicators-of-the-protection-system')}
|
||||||
@@ -24,44 +23,44 @@ export default function FinalSummary() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="summary-stats">
|
<div className="summary-stats">
|
||||||
<div className="summary-stat">
|
<div className="summary-stat">
|
||||||
{/* <div className="summary-stat-icon">🔍</div> */}
|
|
||||||
<div className="summary-stat-value">
|
<div className="summary-stat-value">
|
||||||
{monitoringStats?.total_searches ? monitoringStats?.total_searches : 0}
|
{monitoringStats?.total_searches ? monitoringStats?.total_searches : 0}
|
||||||
{/* {filesInfo?.total.check ? filesInfo?.total.check : 0} */}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="summary-stat-label">
|
<div className="summary-stat-label">
|
||||||
{t('total-number-of-checks')}
|
{t('total-number-of-checks')}
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="summary-stat-change">+15% к прошлому месяцу</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="summary-stat center">
|
<div className="summary-stat center">
|
||||||
{/* <div className="summary-stat-icon">🚨</div> */}
|
|
||||||
<div>
|
<div>
|
||||||
<div className="summary-stat-value">
|
<div className="summary-stat-value">
|
||||||
{violationStatistic?.total_violations ?? 0}
|
{violationStatistic?.total_violations ?? 0}
|
||||||
</div>
|
</div>
|
||||||
<div className="summary-stat-label">
|
<div className="summary-stat-label">
|
||||||
{t('violations-found')}
|
{t('matches-found')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="summary-stat-value">
|
<div className="summary-stat-value">
|
||||||
{violationStatistic?.in_progress_violations ?? 0}
|
{violationStatistic?.total_complaints ?? 0}
|
||||||
</div>
|
</div>
|
||||||
<div className="summary-stat-label">
|
<div className="summary-stat-label">
|
||||||
{t('violations-registered')}
|
{t('violations-registered')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
{/* <div className="summary-stat-change">Отличный результат!</div> */}
|
<div className="summary-stat-value">
|
||||||
|
{violationStatistic?.total_law_cases ?? 0}
|
||||||
|
</div>
|
||||||
|
<div className="summary-stat-label">
|
||||||
|
{t('claims-registered')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="summary-stat">
|
<div className="summary-stat">
|
||||||
{/* <div className="summary-stat-icon">🛡️</div> */}
|
|
||||||
<div className="summary-stat-value">{filesInfo?.total.quantity ? filesInfo?.total.quantity : 0}</div>
|
<div className="summary-stat-value">{filesInfo?.total.quantity ? filesInfo?.total.quantity : 0}</div>
|
||||||
<div className="summary-stat-label">
|
<div className="summary-stat-label">
|
||||||
{t('protected-content')}
|
{t('protected-content')}
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="summary-stat-change">+0 за период</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import { useTranslations } from 'next-intl';
|
|||||||
import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo';
|
import { useUserFilesInfo } from '@/app/hooks/react-query/useUserFilesInfo';
|
||||||
import { IconDocument, IconSearch, IconWarning, IconGraph } from '@/app/ui/icons/icons';
|
import { IconDocument, IconSearch, IconWarning, IconGraph } from '@/app/ui/icons/icons';
|
||||||
import { useViolationStatistic } from '@/app/hooks/react-query/useViolationStatistic';
|
import { useViolationStatistic } from '@/app/hooks/react-query/useViolationStatistic';
|
||||||
import { useMonitoringStats } from '@/app/hooks/react-query/useMonitoringStats';
|
import { useMonitoringCount } from '@/app/hooks/react-query/useMonitoringCount';
|
||||||
|
|
||||||
export default function ReportsInfo() {
|
export default function ReportsInfo() {
|
||||||
const t = useTranslations("Global");
|
const t = useTranslations("Global");
|
||||||
const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo();
|
const { data: filesInfo, isLoading, isError, error } = useUserFilesInfo();
|
||||||
const { data: violationStatistic } = useViolationStatistic();
|
const { data: violationStatistic } = useViolationStatistic();
|
||||||
const { data: monitoringStats } = useMonitoringStats();
|
const { data: monitoringCount, isLoading: isLoadingMonitoringCount } = useMonitoringCount();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -32,7 +32,7 @@ export default function ReportsInfo() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-value">{violationStatistic?.total_violations ?? 0}</div>
|
<div className="stat-value">{violationStatistic?.total_violations ?? 0}</div>
|
||||||
<div className="stat-label">{t('violations-few')}</div>
|
<div className="stat-label">{t('matches')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="stat-card checks" data-icon="">
|
<div className="stat-card checks" data-icon="">
|
||||||
@@ -42,9 +42,19 @@ export default function ReportsInfo() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-value">
|
<div className="stat-value">
|
||||||
{monitoringStats?.total_searches ? monitoringStats?.total_searches : 0}
|
<div
|
||||||
|
className="loading-placeholder start"
|
||||||
|
>
|
||||||
|
{isLoadingMonitoringCount ? (
|
||||||
|
<div className="loading-animation">
|
||||||
|
<div className="global-spinner" />
|
||||||
</div>
|
</div>
|
||||||
<div className="stat-label">{t('monitoring')}</div>
|
) : (
|
||||||
|
`${monitoringCount?.day}/${monitoringCount?.weekly}/${monitoringCount?.month}`
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="stat-label">{t('monitoring')} {t('every')} {t('day')}/ {t('week_accusative')}/ {t('month')}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="stat-card storage" data-icon="">
|
<div className="stat-card storage" data-icon="">
|
||||||
|
|||||||
@@ -243,7 +243,6 @@ export default function SectionSearchFile() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!isCancelledRef.current) {
|
if (!isCancelledRef.current) {
|
||||||
setError(t('error-uploading-file'));
|
setError(t('error-uploading-file'));
|
||||||
console.error('Upload error:', error);
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setUploadId(null);
|
setUploadId(null);
|
||||||
@@ -321,14 +320,14 @@ export default function SectionSearchFile() {
|
|||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
onChange={handleFileInputChange}
|
onChange={handleFileInputChange}
|
||||||
aria-label="Выбор файла для защиты"
|
aria-label="Выбор файла для защиты"
|
||||||
disabled={isFileSearchLoading || (uploadProgress !== undefined && uploadProgress !== 100)}
|
disabled={isFileSearchLoading || (uploadProgress && uploadProgress !== 100 ? true : false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary"
|
||||||
onClick={handleButtonClick}
|
onClick={handleButtonClick}
|
||||||
type="button"
|
type="button"
|
||||||
disabled={isFileSearchLoading || (uploadProgress !== undefined && uploadProgress !== 100)}
|
disabled={isFileSearchLoading || (uploadProgress && uploadProgress !== 100 ? true : false)}
|
||||||
>
|
>
|
||||||
Выбрать файл
|
Выбрать файл
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -48,9 +48,9 @@ export default function VerificationUploadSection({
|
|||||||
const [isDragging, setIsDragging] = useState<boolean>(false);
|
const [isDragging, setIsDragging] = useState<boolean>(false);
|
||||||
const [selectedFiles, setSelectedFiles] = useState<SelectedFile[]>([]);
|
const [selectedFiles, setSelectedFiles] = useState<SelectedFile[]>([]);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [activeUploads, setActiveUploads] = useState<Set<string>>(new Set()); // ID активных загрузок
|
const [activeUploads, setActiveUploads] = useState<Set<string>>(new Set());
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const isCancelledRef = useRef<Map<string, boolean>>(new Map()); // Для отслеживания отмен по файлам
|
const isCancelledRef = useRef<Map<string, boolean>>(new Map());
|
||||||
const selectedFilesRef = useRef<SelectedFile[]>(selectedFiles);
|
const selectedFilesRef = useRef<SelectedFile[]>(selectedFiles);
|
||||||
|
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
@@ -62,9 +62,9 @@ export default function VerificationUploadSection({
|
|||||||
return allowedExtensions.map(e => `.${e}`).join(', ');
|
return allowedExtensions.map(e => `.${e}`).join(', ');
|
||||||
}, [allowedExtensions]);
|
}, [allowedExtensions]);
|
||||||
|
|
||||||
const validateFile = (file: File): { isValid: boolean; errorMessage?: string } => {
|
const validateFile = (file: File, extension: string | undefined): { isValid: boolean; errorMessage?: string } => {
|
||||||
if (!allowedExtensions.includes(file.type as string)) {
|
if (!allowedExtensions.includes(file.type as string)) {
|
||||||
const extension = file.name.split('.').pop()?.toLowerCase();
|
|
||||||
if (!extension || !allowedExtensions.includes(extension as string)) {
|
if (!extension || !allowedExtensions.includes(extension as string)) {
|
||||||
return {
|
return {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
@@ -127,8 +127,8 @@ export default function VerificationUploadSection({
|
|||||||
duplicateNames.push(file.name);
|
duplicateNames.push(file.name);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const extension = file.name.split('.').pop()?.toLowerCase();
|
||||||
const validation = validateFile(file);
|
const validation = validateFile(file, extension);
|
||||||
|
|
||||||
if (!validation.isValid) {
|
if (!validation.isValid) {
|
||||||
setError(prev => prev ? `${prev}, ${validation.errorMessage}` : validation.errorMessage || t('unknown-validation-error'));
|
setError(prev => prev ? `${prev}, ${validation.errorMessage}` : validation.errorMessage || t('unknown-validation-error'));
|
||||||
@@ -139,7 +139,8 @@ export default function VerificationUploadSection({
|
|||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
} | undefined = undefined;
|
} | undefined = undefined;
|
||||||
if (fileType === "image") {
|
|
||||||
|
if (fileType === "image" && extension !== 'pdf') {
|
||||||
try {
|
try {
|
||||||
dimensions = await getImageDimensions(file) as { width: number, height: number };
|
dimensions = await getImageDimensions(file) as { width: number, height: number };
|
||||||
if (dimensions?.width < 100 || dimensions?.height < 100 ||
|
if (dimensions?.width < 100 || dimensions?.height < 100 ||
|
||||||
@@ -224,7 +225,6 @@ export default function VerificationUploadSection({
|
|||||||
f.uploadId !== fileId && f.name !== fileId
|
f.uploadId !== fileId && f.name !== fileId
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
console.log('clear all')
|
|
||||||
selectedFiles.forEach(file => {
|
selectedFiles.forEach(file => {
|
||||||
if (file.preview) {
|
if (file.preview) {
|
||||||
URL.revokeObjectURL(file.preview);
|
URL.revokeObjectURL(file.preview);
|
||||||
@@ -247,17 +247,16 @@ export default function VerificationUploadSection({
|
|||||||
const uploadSingleFile = async (fileInfo: SelectedFile, uploadType: 'single' | 'multi', isLastFIle: boolean): Promise<void> => {
|
const uploadSingleFile = async (fileInfo: SelectedFile, uploadType: 'single' | 'multi', isLastFIle: boolean): Promise<void> => {
|
||||||
const fileId = `${fileInfo.name}-${Date.now()}`;
|
const fileId = `${fileInfo.name}-${Date.now()}`;
|
||||||
isCancelledRef.current.set(fileId, false);
|
isCancelledRef.current.set(fileId, false);
|
||||||
|
|
||||||
if (uploadType === 'single') {
|
|
||||||
setError(null);
|
setError(null);
|
||||||
const priceCheck = await checkOperationPrice('checkTokensForProtect', 1, fileType);
|
|
||||||
|
|
||||||
|
/* if (uploadType === 'single') {
|
||||||
|
const priceCheck = await checkOperationPrice('checkTokensForProtect', 1, fileType);
|
||||||
if (!priceCheck.success) {
|
if (!priceCheck.success) {
|
||||||
const errorMessage = `${t('there-are-not-enough-funds-to-protect-file', { count: priceCheck?.tokens_count })}`;
|
const errorMessage = `${t('there-are-not-enough-funds-to-protect-file', { count: priceCheck?.tokens_count })}`;
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setSelectedFiles(prev => prev.map(f =>
|
setSelectedFiles(prev => prev.map(f =>
|
||||||
@@ -267,7 +266,7 @@ export default function VerificationUploadSection({
|
|||||||
const extension = fileInfo.file.name.split('.').pop() || '';
|
const extension = fileInfo.file.name.split('.').pop() || '';
|
||||||
const initMessageBody = {
|
const initMessageBody = {
|
||||||
file_name: fileInfo.file.name,
|
file_name: fileInfo.file.name,
|
||||||
file_type: fileType,
|
file_type: extension === 'pdf' ? 'document' : fileType,
|
||||||
extension: extension,
|
extension: extension,
|
||||||
file_size: fileInfo.file.size
|
file_size: fileInfo.file.size
|
||||||
};
|
};
|
||||||
@@ -353,9 +352,7 @@ export default function VerificationUploadSection({
|
|||||||
if (selectedFilesRef.current.length === 0) {
|
if (selectedFilesRef.current.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const priceCheck = await checkOperationPrice('checkTokensForProtect', selectedFilesRef.current.length, fileType);
|
|
||||||
|
|
||||||
if (priceCheck.success) {
|
|
||||||
const files = selectedFilesRef.current;
|
const files = selectedFilesRef.current;
|
||||||
const totalFiles = files.length;
|
const totalFiles = files.length;
|
||||||
|
|
||||||
@@ -365,10 +362,6 @@ export default function VerificationUploadSection({
|
|||||||
|
|
||||||
await uploadSingleFile(file, 'multi', isLastFile);
|
await uploadSingleFile(file, 'multi', isLastFile);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const errorMessage = `${t('there-are-not-enough-funds-to-protect-files', { count: priceCheck?.tokens_count })}`;
|
|
||||||
setError(errorMessage);
|
|
||||||
}
|
|
||||||
}, [maxParallelUploads]);
|
}, [maxParallelUploads]);
|
||||||
|
|
||||||
const getTotalProgress = useMemo(() => {
|
const getTotalProgress = useMemo(() => {
|
||||||
@@ -404,7 +397,7 @@ export default function VerificationUploadSection({
|
|||||||
return (
|
return (
|
||||||
<div className="verefication-block" >
|
<div className="verefication-block" >
|
||||||
<h3>
|
<h3>
|
||||||
Загрузка изображений удостоверения личности
|
{t('uploading-id-images')}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
< div
|
< div
|
||||||
@@ -413,10 +406,6 @@ export default function VerificationUploadSection({
|
|||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
onDrop={handleDrop}
|
onDrop={handleDrop}
|
||||||
>
|
>
|
||||||
{/* <h4>
|
|
||||||
{t('drag-the-file-here')}
|
|
||||||
</h4> */}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
fileType === "image" && (
|
fileType === "image" && (
|
||||||
<p className="description" >
|
<p className="description" >
|
||||||
@@ -427,18 +416,15 @@ export default function VerificationUploadSection({
|
|||||||
<p className="description" >
|
<p className="description" >
|
||||||
{t('file-format')}: {acceptString}
|
{t('file-format')}: {acceptString}
|
||||||
</p>
|
</p>
|
||||||
{/* < p className="description" >
|
|
||||||
{t('file-size')}: {t('to')} {(maxFileSize / 1024 / 1024)} {t('mb')}
|
|
||||||
</p> */}
|
|
||||||
|
|
||||||
< input
|
< input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
accept={acceptString}
|
accept={`${acceptString}`}
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
onChange={handleFileInputChange}
|
onChange={handleFileInputChange}
|
||||||
aria-label="Выбор файла для защиты"
|
aria-label="Выбор файла для защиты"
|
||||||
multiple // Добавляем multiple для выбора нескольких файлов
|
multiple
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -453,22 +439,6 @@ export default function VerificationUploadSection({
|
|||||||
selectedFiles.length > 0 && (
|
selectedFiles.length > 0 && (
|
||||||
<div className="mt-4" >
|
<div className="mt-4" >
|
||||||
<div className="selected-file-action-panel" >
|
<div className="selected-file-action-panel" >
|
||||||
{/* <div>
|
|
||||||
<p className="selected-file-action-info" >
|
|
||||||
<span>{t('total-files')}: {selectedFiles.length}</span>
|
|
||||||
<span>{t('uploading')}: {uploadingCount}</span>
|
|
||||||
<span>{t('completed')}: {completedCount}</span>
|
|
||||||
</p>
|
|
||||||
<div className="w-full bg-gray-200 rounded-full h-2.5 mt-2">
|
|
||||||
<div
|
|
||||||
className="bg-blue-600 h-2.5 rounded-full transition-all duration-300"
|
|
||||||
style={{ width: `${getTotalProgress}%` }
|
|
||||||
}
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> */}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className={`btn btn-primary`}
|
className={`btn btn-primary`}
|
||||||
onClick={handleStartAllUploads}
|
onClick={handleStartAllUploads}
|
||||||
@@ -498,27 +468,6 @@ export default function VerificationUploadSection({
|
|||||||
<p className="text-gray-700" >
|
<p className="text-gray-700" >
|
||||||
<span className="font-medium" > {t('file')}: </span> {file.name}
|
<span className="font-medium" > {t('file')}: </span> {file.name}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* < p className="text-gray-700" >
|
|
||||||
<span className="font-medium" > {t('size')}: </span> {file.size}
|
|
||||||
</p> */}
|
|
||||||
|
|
||||||
{/* < p className="text-gray-700" >
|
|
||||||
<span className="font-medium" > {t('status')}: </span>
|
|
||||||
<span className={`ml-2 ${file.status === 'completed' ? 'text-green-600' :
|
|
||||||
file.status === 'uploading' ? 'text-blue-600' :
|
|
||||||
file.status === 'error' ? 'text-red-600' :
|
|
||||||
'text-gray-600'
|
|
||||||
}`}>
|
|
||||||
{
|
|
||||||
file.status === 'completed' ? t('completed') :
|
|
||||||
file.status === 'uploading' ? t('uploading') :
|
|
||||||
file.status === 'error' ? t('error') :
|
|
||||||
t('pending')
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
</p> */}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
file.error && (
|
file.error && (
|
||||||
<p className="text-red-600 text-sm mt-1" > {file.error} </p>
|
<p className="text-red-600 text-sm mt-1" > {file.error} </p>
|
||||||
@@ -534,32 +483,9 @@ export default function VerificationUploadSection({
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* <span className={
|
|
||||||
`upload-progress ${file.status === 'completed' ? 'text-green-600' :
|
|
||||||
file.status === 'uploading' ? 'text-blue-600' :
|
|
||||||
'text-gray-600'
|
|
||||||
}`
|
|
||||||
}>
|
|
||||||
{file.progress} %
|
|
||||||
</span> */}
|
|
||||||
<div
|
<div
|
||||||
className="selected-file-actions"
|
className="selected-file-actions"
|
||||||
>
|
>
|
||||||
{/* {
|
|
||||||
file.status === 'pending' && (
|
|
||||||
<button
|
|
||||||
className="btn btn-confirm"
|
|
||||||
onClick={() => uploadSingleFile(file, 'single')
|
|
||||||
}
|
|
||||||
type="button"
|
|
||||||
disabled={uploadingCount >= maxParallelUploads}
|
|
||||||
>
|
|
||||||
{t('start-upload')}
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
} */}
|
|
||||||
|
|
||||||
< button
|
< button
|
||||||
className="btn btn-cancel"
|
className="btn btn-cancel"
|
||||||
onClick={() => handleClearFile(file.uploadId || file.name)}
|
onClick={() => handleClearFile(file.uploadId || file.name)}
|
||||||
@@ -582,7 +508,7 @@ export default function VerificationUploadSection({
|
|||||||
type="button"
|
type="button"
|
||||||
disabled={uploadingCount > maxParallelUploads}
|
disabled={uploadingCount > maxParallelUploads}
|
||||||
>
|
>
|
||||||
выберете документ(ы) для загрузки
|
{t('select-document-to-upload')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,31 +6,48 @@ import VerificationUploadSection from '@/app/ui/settings/component/verification-
|
|||||||
import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo';
|
import { useUserProfile } from '@/app/hooks/react-query/useUserDataInfo';
|
||||||
|
|
||||||
export default function SettingUserVerification() {
|
export default function SettingUserVerification() {
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('User-status');
|
||||||
|
|
||||||
const { data: allFilesExtensions } = useAllFilesExtensions();
|
const { data: allFilesExtensions } = useAllFilesExtensions();
|
||||||
const { data: userData, isLoading, isError } = useUserProfile();
|
const { data: userData, isLoading, isError } = useUserProfile();
|
||||||
|
|
||||||
|
const getVerifiedStatusText = (status: string | undefined | null) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'VERIFICATION_IN_PROGRESS':
|
||||||
|
return t('under-moderation');
|
||||||
|
case 'NOT_VERIFIED':
|
||||||
|
return t('NOT_VERIFIED');
|
||||||
|
case 'VERIFIED':
|
||||||
|
return t('VERIFIED');
|
||||||
|
case 'VERIFICATION_FAILED':
|
||||||
|
return t('VERIFICATION_FAILED');
|
||||||
|
case 'unknow':
|
||||||
|
return t('unknow');
|
||||||
|
case undefined:
|
||||||
|
case null:
|
||||||
|
return t('NOT_VERIFIED');
|
||||||
|
default:
|
||||||
|
return t('NOT_VERIFIED');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="settings-form">
|
<div className="settings-form">
|
||||||
<div className="form-section verefication-section">
|
<div className="form-section verefication-section">
|
||||||
<h3 className="form-section-title">
|
<h3 className="form-section-title">
|
||||||
Верификация юзера
|
{t('user-verification')}
|
||||||
</h3>
|
</h3>
|
||||||
{userData?.verifiedStatus === 'NOT_VERIFIED' || userData?.verifiedStatus === null ? (
|
{userData?.verifiedStatus === 'NOT_VERIFIED' || userData?.verifiedStatus === null ? (
|
||||||
<VerificationUploadSection
|
<VerificationUploadSection
|
||||||
fileType="image"
|
fileType="image"
|
||||||
allowedExtensions={allFilesExtensions.images}
|
allowedExtensions={[...allFilesExtensions.images, 'pdf']}
|
||||||
maxFileSize={allFilesExtensions.maxFileSize}
|
maxFileSize={allFilesExtensions.maxFileSize}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<span>
|
<span>
|
||||||
{t('Status')}: {userData?.verifiedStatus === 'VERIFICATION_IN_PROGRESS'? t('under-moderation') : userData?.verifiedStatus}
|
{t('Status')}: {getVerifiedStatusText(userData?.verifiedStatus)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{/* <button type="submit" className="btn btn-primary">
|
|
||||||
загрузить
|
|
||||||
</button> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,462 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useHistoryView, HistoryViewItem } from '@/app/hooks/react-query/useHistoryView';
|
||||||
|
import { useEffect, useState, useMemo } from 'react';
|
||||||
|
import { IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconArrowUp, IconArrowDown, IconFilter } from '@/app/ui/icons/icons';
|
||||||
|
import DropDownList from '@/app/components/DropDownList';
|
||||||
|
import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, SortingState, ColumnDef } from '@tanstack/react-table';
|
||||||
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useViewport } from '@/app/hooks/useViewport';
|
||||||
|
import { useLocale } from 'next-intl';
|
||||||
|
|
||||||
|
export function TrackingHistoryView() {
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
const locale = useLocale();
|
||||||
|
|
||||||
|
const getSortParams = useMemo(() => {
|
||||||
|
if (sorting.length === 0) {
|
||||||
|
return { sortBy: '', sortOrder: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
const sort = sorting[0];
|
||||||
|
const sortByMap: Record<string, string> = {
|
||||||
|
'document': 'docName',
|
||||||
|
'view_date': 'createdAt',
|
||||||
|
'viewer': 'viewerName',
|
||||||
|
'location': locale === 'ru' ? 'geoNameRu' : 'geoNameEn'
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
sortBy: sortByMap[sort.id] || sort.id,
|
||||||
|
sortOrder: sort.desc ? 'desc' : 'asc' as 'desc' | 'asc'
|
||||||
|
};
|
||||||
|
}, [sorting]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: apiResponse,
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
refetch
|
||||||
|
} = useHistoryView(pagination.pageIndex, pagination.pageSize, locale, getSortParams.sortOrder, getSortParams.sortBy);
|
||||||
|
|
||||||
|
const totalElements = apiResponse?.totalElements || 0
|
||||||
|
const tableData = apiResponse?.history || [];
|
||||||
|
const totalPages = apiResponse?.totalPages || 0;
|
||||||
|
|
||||||
|
const viewport = useViewport();
|
||||||
|
|
||||||
|
const getMaxLengthByWidth = (): number => {
|
||||||
|
if (viewport.device === 'MOBILE') return 22;
|
||||||
|
if (viewport.device === 'TABLET') return 30;
|
||||||
|
if (viewport.device === 'DESKTOP') return 50;
|
||||||
|
return 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cutFileName = (fileName: string) => {
|
||||||
|
const MAX_FILE_NAME_LENGTH = getMaxLengthByWidth();
|
||||||
|
const lastDotIndex = fileName.lastIndexOf('.');
|
||||||
|
|
||||||
|
if (lastDotIndex <= 0) {
|
||||||
|
return fileName.length > MAX_FILE_NAME_LENGTH ? fileName.substring(0, MAX_FILE_NAME_LENGTH - 3) + '...' : fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameWithoutExtension = fileName.substring(0, lastDotIndex);
|
||||||
|
const extension = fileName.substring(lastDotIndex);
|
||||||
|
|
||||||
|
if (fileName.length <= MAX_FILE_NAME_LENGTH) {
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3;
|
||||||
|
|
||||||
|
if (maxNameLength <= 0) {
|
||||||
|
return '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
return nameWithoutExtension.substring(0, maxNameLength) + '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditLocationName({ location }: { location: string | undefined | null }) {
|
||||||
|
if (!location) return (
|
||||||
|
<div></div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const [city, country] = location.split('/');
|
||||||
|
|
||||||
|
const translatedCity = city === 'unknow' ? t('not-defined-city') : city;
|
||||||
|
const translatedCountry = country === 'unknow' ? t('not-defined-country') : country;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{t('city')}: <strong>{translatedCity}</strong>
|
||||||
|
<br />
|
||||||
|
{t('country')}: <strong>{translatedCountry}</strong>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = useMemo<ColumnDef<HistoryViewItem>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: 'document',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column">
|
||||||
|
<span>
|
||||||
|
{t('document')}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
column.getIsSorted() === 'asc' ?
|
||||||
|
<span>
|
||||||
|
<IconArrowUp />
|
||||||
|
</span>
|
||||||
|
: column.getIsSorted() === 'desc' ?
|
||||||
|
<span>
|
||||||
|
<IconArrowDown />
|
||||||
|
</span>
|
||||||
|
: <span>
|
||||||
|
<IconFilter />
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-center table-item">
|
||||||
|
{row.original.document}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'view_date',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column">
|
||||||
|
<span>
|
||||||
|
{t('date')} / {t('time')}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
column.getIsSorted() === 'asc' ?
|
||||||
|
<span>
|
||||||
|
<IconArrowUp />
|
||||||
|
</span>
|
||||||
|
: column.getIsSorted() === 'desc' ?
|
||||||
|
<span>
|
||||||
|
<IconArrowDown />
|
||||||
|
</span>
|
||||||
|
: <span>
|
||||||
|
<IconFilter />
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className="text-center table-item">
|
||||||
|
{row.original.view_date ? (
|
||||||
|
<>
|
||||||
|
{formatDate(row.original.view_date)}
|
||||||
|
<br />
|
||||||
|
{formatDateTime(row.original.view_date)}
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div>-</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
enableColumnFilter: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'viewer',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column">
|
||||||
|
<span>
|
||||||
|
{t('who-opened')}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
column.getIsSorted() === 'asc' ?
|
||||||
|
<span>
|
||||||
|
<IconArrowUp />
|
||||||
|
</span>
|
||||||
|
: column.getIsSorted() === 'desc' ?
|
||||||
|
<span>
|
||||||
|
<IconArrowDown />
|
||||||
|
</span>
|
||||||
|
: <span>
|
||||||
|
<IconFilter />
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className="text-center font-semibold table-item">
|
||||||
|
{row.original.viewer}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'location',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column column-location">
|
||||||
|
<span>
|
||||||
|
{t('location')}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
column.getIsSorted() === 'asc' ?
|
||||||
|
<span>
|
||||||
|
<IconArrowUp />
|
||||||
|
</span>
|
||||||
|
: column.getIsSorted() === 'desc' ?
|
||||||
|
<span>
|
||||||
|
<IconArrowDown />
|
||||||
|
</span>
|
||||||
|
: <span>
|
||||||
|
<IconFilter />
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return (
|
||||||
|
<div className="text-center font-semibold table-item">
|
||||||
|
{row.original.country === 'unknow/unknow' ? (
|
||||||
|
t('not-defined')
|
||||||
|
) : (
|
||||||
|
<EditLocationName location={row.original.country} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
/* `${editLocationName(row.original.country)}` */
|
||||||
|
// Создание таблицы
|
||||||
|
const table = useReactTable({
|
||||||
|
data: tableData,
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
pagination
|
||||||
|
},
|
||||||
|
pageCount: totalPages,
|
||||||
|
manualPagination: true,
|
||||||
|
manualSorting: true,
|
||||||
|
manualFiltering: true,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
onSortingChange: (updater) => {
|
||||||
|
const newSorting = typeof updater === 'function' ? updater(sorting) : updater;
|
||||||
|
setSorting(newSorting);
|
||||||
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
||||||
|
},
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (tableData.length === 0 && pagination.pageIndex > 0 && totalPages > 0) {
|
||||||
|
setPagination(prev => ({ ...prev, pageIndex: Math.max(0, totalPages - 1) }));
|
||||||
|
}
|
||||||
|
}, [tableData, pagination.pageIndex, totalPages]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="block-wrapper"
|
||||||
|
>
|
||||||
|
<div className="tanstak-table-wrapper">
|
||||||
|
|
||||||
|
{/* Фильтры */}
|
||||||
|
<div className="tanstak-table-filtres">
|
||||||
|
<h3
|
||||||
|
className="tanstak-table-title"
|
||||||
|
>
|
||||||
|
{t('history-of-opened')}
|
||||||
|
</h3>
|
||||||
|
<div className="table-filtres-wrapper text-filter-search end">
|
||||||
|
<div className="table-filtres-item">
|
||||||
|
<div className="table-filtres-label">{t('items-per-page')}:</div>
|
||||||
|
<DropDownList
|
||||||
|
value={table.getState().pagination.pageSize}
|
||||||
|
callBack={(newSize: string) => {
|
||||||
|
table.setPageSize(Number(newSize));
|
||||||
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{['5', '10', '20', '50', '100'].map(pageSize => (
|
||||||
|
<li key={pageSize} value={pageSize}>
|
||||||
|
{t('show')} {pageSize}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</DropDownList>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Таблица */}
|
||||||
|
<div className="tanstak-table-block">
|
||||||
|
<table className="tanstak-table">
|
||||||
|
<thead className="tanstak-table-head">
|
||||||
|
{table.getHeaderGroups().map(headerGroup => (
|
||||||
|
<tr key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map(header => (
|
||||||
|
<th
|
||||||
|
key={header.id}
|
||||||
|
>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: typeof header.column.columnDef.header === 'function'
|
||||||
|
? header.column.columnDef.header(header.getContext())
|
||||||
|
: header.column.columnDef.header as string}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody className={`tanstak-table-body ${isFetching ? 'loading' : ''}`}>
|
||||||
|
{isLoading && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={columns.length} className="text-center relative h-12.5">
|
||||||
|
<div
|
||||||
|
className="loading-animation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="global-spinner"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{table.getRowModel().rows.length > 0 ? (
|
||||||
|
table.getRowModel().rows.map(row => (
|
||||||
|
<tr key={row.id}>
|
||||||
|
{row.getVisibleCells().map(cell => (
|
||||||
|
<td key={cell.id}>
|
||||||
|
{typeof cell.column.columnDef.cell === 'function'
|
||||||
|
? cell.column.columnDef.cell(cell.getContext())
|
||||||
|
: cell.getValue() as string}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
{!isLoading && (
|
||||||
|
<td colSpan={columns.length} className="text-center">
|
||||||
|
{t('no-data-for-selected-filters')}
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Пагинация */}
|
||||||
|
<div className="tanstak-table-pagination">
|
||||||
|
<div className="pagination-info">
|
||||||
|
<span className="pagination-info-pages">
|
||||||
|
{t('page')}{' '}
|
||||||
|
<strong>
|
||||||
|
{pagination.pageIndex + 1} {t('out-of')} {totalPages || 1}
|
||||||
|
</strong> | {t('total-document-openings')}: {totalElements}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pagination-controls">
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.firstPage()}
|
||||||
|
disabled={!table.getCanPreviousPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconDoubleArrowLeft />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconArrowLeft />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="pagination-controls-pages">
|
||||||
|
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
|
||||||
|
let pageIndex;
|
||||||
|
if (totalPages <= 5) {
|
||||||
|
pageIndex = i;
|
||||||
|
} else if (pagination.pageIndex <= 2) {
|
||||||
|
pageIndex = i;
|
||||||
|
} else if (pagination.pageIndex >= totalPages - 3) {
|
||||||
|
pageIndex = totalPages - 5 + i;
|
||||||
|
} else {
|
||||||
|
pageIndex = pagination.pageIndex - 2 + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageIndex >= 0 && pageIndex < totalPages) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={pageIndex}
|
||||||
|
className={pagination.pageIndex === pageIndex ? 'current' : 'other'}
|
||||||
|
onClick={() => table.setPageIndex(pageIndex)}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{pageIndex + 1}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconArrowRight />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.lastPage()}
|
||||||
|
disabled={!table.getCanNextPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconDoubleArrowRight />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div >
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { IconLink, IconDocument, IconCheck, IconPerson, IconGlobe, IconCity } from '@/app/ui/icons/icons';
|
||||||
|
import { useTrackingStats } from '@/app/hooks/react-query/useTrackingStats';
|
||||||
|
|
||||||
|
export function TrackingStatistic() {
|
||||||
|
const { data, isLoading } = useTrackingStats();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="general-statistic"
|
||||||
|
>
|
||||||
|
<div className="general-statistic-top">
|
||||||
|
<div>
|
||||||
|
<div className="general-statistic-title">
|
||||||
|
PDF Трекинг
|
||||||
|
<span className="pulse"></span>
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-sub">
|
||||||
|
Аналитика открытий защищённых PDF документов
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-content">
|
||||||
|
<div className="general-statistic-item">
|
||||||
|
<div className="general-statistic-item-ico">
|
||||||
|
<IconDocument />
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-val">
|
||||||
|
<div
|
||||||
|
className="loading-placeholder start"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="loading-animation">
|
||||||
|
<div className="global-spinner" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data?.documentsCount ? data?.documentsCount : 0
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-lbl">
|
||||||
|
Документов
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item">
|
||||||
|
<div className="general-statistic-item-ico">
|
||||||
|
<IconLink />
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-val">
|
||||||
|
<div
|
||||||
|
className="loading-placeholder start"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="loading-animation">
|
||||||
|
<div className="global-spinner" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data?.totalViews ? data?.totalViews : 0
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-lbl">
|
||||||
|
Открытий
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* <div className="general-statistic-item">
|
||||||
|
<div className="general-statistic-item-ico">
|
||||||
|
<IconCheck />
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-val">
|
||||||
|
{data?.uniqueUsers ? data?.uniqueUsers : 0}
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-lbl">
|
||||||
|
Уникальных пользователей
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
<div className="general-statistic-item">
|
||||||
|
<div className="general-statistic-item-ico">
|
||||||
|
<IconPerson />
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-val">
|
||||||
|
<div
|
||||||
|
className="loading-placeholder start"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="loading-animation">
|
||||||
|
<div className="global-spinner" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data?.uniqueIps ? data?.uniqueIps : 0
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-lbl">
|
||||||
|
Уникальных IP
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item">
|
||||||
|
<div className="general-statistic-item-ico">
|
||||||
|
<IconGlobe />
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-val">
|
||||||
|
<div
|
||||||
|
className="loading-placeholder start"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="loading-animation">
|
||||||
|
<div className="global-spinner" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data?.uniqueCountryCount ? data?.uniqueCountryCount : 0
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-lbl">
|
||||||
|
Уникальных стран
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item">
|
||||||
|
<div className="general-statistic-item-ico">
|
||||||
|
<IconCity />
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-val">
|
||||||
|
<div
|
||||||
|
className="loading-placeholder start"
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<div className="loading-animation">
|
||||||
|
<div className="global-spinner" />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
data?.uniqueCountryCount ? data?.uniqueCountryCount : 0
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="general-statistic-item-lbl">
|
||||||
|
Уникальных городов
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,340 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState, useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
useReactTable,
|
||||||
|
getCoreRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
ColumnDef,
|
||||||
|
SortingState,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
|
import { IconArrowUp, IconArrowDown, IconFilter, IconArrowRight, IconArrowLeft, IconDoubleArrowLeft, IconDoubleArrowRight, IconEye } from '@/app/ui/icons/icons';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useLastCases, Case } from '@/app/hooks/react-query/useLastCases';
|
||||||
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
export function ViolationsMyClaimsTable() {
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 5,
|
||||||
|
});
|
||||||
|
const t = useTranslations("Global");
|
||||||
|
|
||||||
|
const getSortParams: {
|
||||||
|
sort_by: string;
|
||||||
|
sort_direction: 'asc' | 'desc'
|
||||||
|
} = useMemo(() => {
|
||||||
|
if (sorting.length === 0) {
|
||||||
|
return { sort_by: 'createdAt', sort_direction: 'desc' as const };
|
||||||
|
}
|
||||||
|
|
||||||
|
const sort = sorting[0];
|
||||||
|
const sortByMap: Record<string, string> = {
|
||||||
|
'caseName': 'name',
|
||||||
|
'createdAt': 'created_at',
|
||||||
|
'updatedAt': 'updated_at',
|
||||||
|
'status': 'type'
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
sort_by: sortByMap[sort.id] || sort.id,
|
||||||
|
sort_direction: sort.desc ? 'desc' : 'asc' as const
|
||||||
|
};
|
||||||
|
}, [sorting]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: apiResponse,
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
} = useLastCases({
|
||||||
|
page: pagination.pageIndex,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
sort_by: getSortParams.sort_by,
|
||||||
|
sort_direction: getSortParams.sort_direction ? getSortParams.sort_direction : 'desc',
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableData = apiResponse?.content || [];
|
||||||
|
const totalItems = apiResponse?.totalElements || 0;
|
||||||
|
const totalPages = apiResponse?.totalPages || 0;
|
||||||
|
|
||||||
|
const columns = useMemo<ColumnDef<Case>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: 'caseName',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column start">
|
||||||
|
<span>{t('case-name')}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button hidden"
|
||||||
|
>
|
||||||
|
{column.getIsSorted() === 'asc' ? (
|
||||||
|
<IconArrowUp />
|
||||||
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
|
<IconArrowDown />
|
||||||
|
) : (
|
||||||
|
<IconFilter />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="table-item" title={row.original.name}>
|
||||||
|
<span className="font-medium">
|
||||||
|
{row.original.name.length > 50
|
||||||
|
? `${row.original.name.substring(0, 50)}...`
|
||||||
|
: row.original.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
/* {
|
||||||
|
accessorKey: 'createdAt',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column">
|
||||||
|
<span>{t('date-of-creation')}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button hidden"
|
||||||
|
>
|
||||||
|
{column.getIsSorted() === 'asc' ? (
|
||||||
|
<IconArrowUp />
|
||||||
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
|
<IconArrowDown />
|
||||||
|
) : (
|
||||||
|
<IconFilter />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-center table-item">
|
||||||
|
{formatDate(new Date(row.original.created_at).getTime())}
|
||||||
|
<br />
|
||||||
|
{formatDateTime(new Date(row.original.created_at).getTime())}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}, */
|
||||||
|
{
|
||||||
|
accessorKey: 'updatedAt',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column">
|
||||||
|
<span>{t('date-of-update')}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button hidden"
|
||||||
|
>
|
||||||
|
{column.getIsSorted() === 'asc' ? (
|
||||||
|
<IconArrowUp />
|
||||||
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
|
<IconArrowDown />
|
||||||
|
) : (
|
||||||
|
<IconFilter />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-center table-item">
|
||||||
|
{formatDate(new Date(row.original.updated_at).getTime())}
|
||||||
|
<br />
|
||||||
|
{formatDateTime(new Date(row.original.updated_at).getTime())}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'status',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column">
|
||||||
|
<span>{t('status')}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button hidden"
|
||||||
|
>
|
||||||
|
{column.getIsSorted() === 'asc' ? (
|
||||||
|
<IconArrowUp />
|
||||||
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
|
<IconArrowDown />
|
||||||
|
) : (
|
||||||
|
<IconFilter />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const status = row.original.type;
|
||||||
|
const statusColor = status === 'ACTIVE' ? 'text-green-600' : 'text-gray-500';
|
||||||
|
return (
|
||||||
|
<div className={`text-center table-item font-semibold ${statusColor}`}>
|
||||||
|
{t(status.toLowerCase())}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
header: () => <div className="column">{t('actions')}</div>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="actions">
|
||||||
|
<div className="actions-group">
|
||||||
|
<Link
|
||||||
|
href={`/pages/file/${row.original.file_id}?violationId=${row.original.violation_id}&caseType=claim`}
|
||||||
|
className="bg-violet-500 hover:bg-violet-600"
|
||||||
|
title={t('view')}
|
||||||
|
>
|
||||||
|
<IconEye />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: tableData,
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
pageCount: totalPages,
|
||||||
|
manualPagination: true,
|
||||||
|
manualSorting: true,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
onSortingChange: (updater) => {
|
||||||
|
const newSorting = typeof updater === 'function' ? updater(sorting) : updater;
|
||||||
|
setSorting(newSorting);
|
||||||
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
||||||
|
},
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tanstak-table-wrapper block-wrapper">
|
||||||
|
<h3 className="tanstak-table-title">{t('claims')}</h3>
|
||||||
|
|
||||||
|
<div className="tanstak-table-block">
|
||||||
|
<table className="tanstak-table">
|
||||||
|
<thead className="tanstak-table-head">
|
||||||
|
{table.getHeaderGroups().map(headerGroup => (
|
||||||
|
<tr key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map(header => (
|
||||||
|
<th key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: typeof header.column.columnDef.header === 'function'
|
||||||
|
? header.column.columnDef.header(header.getContext())
|
||||||
|
: header.column.columnDef.header as string}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody className={`tanstak-table-body ${isFetching ? 'loading' : ''}`}>
|
||||||
|
{table.getRowModel().rows.length > 0 ? (
|
||||||
|
table.getRowModel().rows.map(row => (
|
||||||
|
<tr key={row.id}>
|
||||||
|
{row.getVisibleCells().map(cell => (
|
||||||
|
<td key={cell.id}>
|
||||||
|
{typeof cell.column.columnDef.cell === 'function'
|
||||||
|
? cell.column.columnDef.cell(cell.getContext())
|
||||||
|
: cell.getValue() as string}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={columns.length} className="text-center">
|
||||||
|
{t('no-data')}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<div className="tanstak-table-pagination">
|
||||||
|
<div className="pagination-info">
|
||||||
|
<span className="pagination-info-pages">
|
||||||
|
{t('page')}{' '}
|
||||||
|
<strong>
|
||||||
|
{pagination.pageIndex + 1} {t('out-of')} {totalPages || 1}
|
||||||
|
</strong> | {t('total-claims')}: {totalItems}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pagination-controls">
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.firstPage()}
|
||||||
|
disabled={!table.getCanPreviousPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconDoubleArrowLeft />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconArrowLeft />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="pagination-controls-pages">
|
||||||
|
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
|
||||||
|
let pageIndex;
|
||||||
|
if (totalPages <= 5) {
|
||||||
|
pageIndex = i;
|
||||||
|
} else if (pagination.pageIndex <= 2) {
|
||||||
|
pageIndex = i;
|
||||||
|
} else if (pagination.pageIndex >= totalPages - 3) {
|
||||||
|
pageIndex = totalPages - 5 + i;
|
||||||
|
} else {
|
||||||
|
pageIndex = pagination.pageIndex - 2 + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageIndex >= 0 && pageIndex < totalPages) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={pageIndex}
|
||||||
|
className={pagination.pageIndex === pageIndex ? 'current' : 'other'}
|
||||||
|
onClick={() => table.setPageIndex(pageIndex)}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{pageIndex + 1}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconArrowRight />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.lastPage()}
|
||||||
|
disabled={!table.getCanNextPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconDoubleArrowRight />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,341 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useLastComplaints, Complaint } from '@/app/hooks/react-query/useLastComplaints';
|
||||||
|
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
useReactTable,
|
||||||
|
getCoreRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
ColumnDef,
|
||||||
|
SortingState,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
|
import { IconArrowUp, IconArrowDown, IconFilter, IconArrowRight, IconArrowLeft, IconDoubleArrowLeft, IconDoubleArrowRight, IconEye } from '@/app/ui/icons/icons';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
export function ViolationsMyComplaintsTable() {
|
||||||
|
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const [pagination, setPagination] = useState({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 5,
|
||||||
|
});
|
||||||
|
const t = useTranslations("Global");
|
||||||
|
|
||||||
|
const getSortParams: {
|
||||||
|
sort_by: string;
|
||||||
|
sort_direction: 'asc' | 'desc'
|
||||||
|
} = useMemo(() => {
|
||||||
|
if (sorting.length === 0) {
|
||||||
|
return { sort_by: 'createdAt', sort_direction: 'desc' as const };
|
||||||
|
}
|
||||||
|
|
||||||
|
const sort = sorting[0];
|
||||||
|
const sortByMap: Record<string, string> = {
|
||||||
|
'caseName': 'name',
|
||||||
|
'createdAt': 'created_at',
|
||||||
|
'updatedAt': 'updated_at',
|
||||||
|
'status': 'type'
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
sort_by: sortByMap[sort.id] || sort.id,
|
||||||
|
sort_direction: sort.desc ? 'desc' : 'asc' as const
|
||||||
|
};
|
||||||
|
}, [sorting]);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: apiResponse,
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
} = useLastComplaints({
|
||||||
|
page: pagination.pageIndex,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
sort_by: getSortParams.sort_by,
|
||||||
|
sort_direction: getSortParams.sort_direction ? getSortParams.sort_direction : 'desc',
|
||||||
|
});
|
||||||
|
|
||||||
|
const tableData = apiResponse?.content || [];
|
||||||
|
const totalItems = apiResponse?.totalElements || 0;
|
||||||
|
const totalPages = apiResponse?.totalPages || 0;
|
||||||
|
|
||||||
|
const columns = useMemo<ColumnDef<Complaint>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: 'caseName',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column start">
|
||||||
|
<span>{t('complaint-name')}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button hidden"
|
||||||
|
>
|
||||||
|
{column.getIsSorted() === 'asc' ? (
|
||||||
|
<IconArrowUp />
|
||||||
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
|
<IconArrowDown />
|
||||||
|
) : (
|
||||||
|
<IconFilter />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="table-item" title={`COM-${row.original.id}`}>
|
||||||
|
<span className="font-medium">
|
||||||
|
{`COM-${row.original.id}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
/* {
|
||||||
|
accessorKey: 'createdAt',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column">
|
||||||
|
<span>{t('date-of-creation')}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button hidden"
|
||||||
|
>
|
||||||
|
{column.getIsSorted() === 'asc' ? (
|
||||||
|
<IconArrowUp />
|
||||||
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
|
<IconArrowDown />
|
||||||
|
) : (
|
||||||
|
<IconFilter />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-center table-item">
|
||||||
|
{formatDate(new Date(row.original.created_at).getTime())}
|
||||||
|
<br />
|
||||||
|
{formatDateTime(new Date(row.original.created_at).getTime())}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}, */
|
||||||
|
{
|
||||||
|
accessorKey: 'updatedAt',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column">
|
||||||
|
<span>{t('date-of-update')}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button hidden"
|
||||||
|
>
|
||||||
|
{column.getIsSorted() === 'asc' ? (
|
||||||
|
<IconArrowUp />
|
||||||
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
|
<IconArrowDown />
|
||||||
|
) : (
|
||||||
|
<IconFilter />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="text-center table-item">
|
||||||
|
{formatDate(new Date(row.original.updated_at).getTime())}
|
||||||
|
<br />
|
||||||
|
{formatDateTime(new Date(row.original.updated_at).getTime())}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'status',
|
||||||
|
header: ({ column }) => (
|
||||||
|
<div className="column">
|
||||||
|
<span>{t('status')}</span>
|
||||||
|
<button
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button hidden"
|
||||||
|
>
|
||||||
|
{column.getIsSorted() === 'asc' ? (
|
||||||
|
<IconArrowUp />
|
||||||
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
|
<IconArrowDown />
|
||||||
|
) : (
|
||||||
|
<IconFilter />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const status = row.original.status;
|
||||||
|
const statusColor = status === 'ACTIVE' ? 'text-green-600' : 'text-gray-500';
|
||||||
|
return (
|
||||||
|
<div className={`text-center table-item font-semibold ${statusColor}`}>
|
||||||
|
{/* {t(status.toLowerCase())} */}
|
||||||
|
{status}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
header: () => <div className="column">{t('actions')}</div>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="actions">
|
||||||
|
<div className="actions-group">
|
||||||
|
<Link
|
||||||
|
href={`/pages/file/${row.original.file_id}?violationId=${row.original.violation_id}&caseType=complaint`}
|
||||||
|
className="bg-violet-500 hover:bg-violet-600"
|
||||||
|
title={t('view')}
|
||||||
|
>
|
||||||
|
<IconEye />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: tableData,
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
pageCount: totalPages,
|
||||||
|
manualPagination: true,
|
||||||
|
manualSorting: true,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
onSortingChange: (updater) => {
|
||||||
|
const newSorting = typeof updater === 'function' ? updater(sorting) : updater;
|
||||||
|
setSorting(newSorting);
|
||||||
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
||||||
|
},
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tanstak-table-wrapper block-wrapper">
|
||||||
|
<h3 className="tanstak-table-title">{t('complaints')}</h3>
|
||||||
|
|
||||||
|
<div className="tanstak-table-block">
|
||||||
|
<table className="tanstak-table">
|
||||||
|
<thead className="tanstak-table-head">
|
||||||
|
{table.getHeaderGroups().map(headerGroup => (
|
||||||
|
<tr key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map(header => (
|
||||||
|
<th key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: typeof header.column.columnDef.header === 'function'
|
||||||
|
? header.column.columnDef.header(header.getContext())
|
||||||
|
: header.column.columnDef.header as string}
|
||||||
|
</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</thead>
|
||||||
|
<tbody className={`tanstak-table-body ${isFetching ? 'loading' : ''}`}>
|
||||||
|
{table.getRowModel().rows.length > 0 ? (
|
||||||
|
table.getRowModel().rows.map(row => (
|
||||||
|
<tr key={row.id}>
|
||||||
|
{row.getVisibleCells().map(cell => (
|
||||||
|
<td key={cell.id}>
|
||||||
|
{typeof cell.column.columnDef.cell === 'function'
|
||||||
|
? cell.column.columnDef.cell(cell.getContext())
|
||||||
|
: cell.getValue() as string}
|
||||||
|
</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={columns.length} className="text-center">
|
||||||
|
{t('no-data')}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pagination */}
|
||||||
|
<div className="tanstak-table-pagination">
|
||||||
|
<div className="pagination-info">
|
||||||
|
<span className="pagination-info-pages">
|
||||||
|
{t('page')}{' '}
|
||||||
|
<strong>
|
||||||
|
{pagination.pageIndex + 1} {t('out-of')} {totalPages || 1}
|
||||||
|
</strong> | {t('total-complaints')}: {totalItems}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pagination-controls">
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.firstPage()}
|
||||||
|
disabled={!table.getCanPreviousPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconDoubleArrowLeft />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconArrowLeft />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="pagination-controls-pages">
|
||||||
|
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
|
||||||
|
let pageIndex;
|
||||||
|
if (totalPages <= 5) {
|
||||||
|
pageIndex = i;
|
||||||
|
} else if (pagination.pageIndex <= 2) {
|
||||||
|
pageIndex = i;
|
||||||
|
} else if (pagination.pageIndex >= totalPages - 3) {
|
||||||
|
pageIndex = totalPages - 5 + i;
|
||||||
|
} else {
|
||||||
|
pageIndex = pagination.pageIndex - 2 + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageIndex >= 0 && pageIndex < totalPages) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={pageIndex}
|
||||||
|
className={pagination.pageIndex === pageIndex ? 'current' : 'other'}
|
||||||
|
onClick={() => table.setPageIndex(pageIndex)}
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{pageIndex + 1}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconArrowRight />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="arrow"
|
||||||
|
onClick={() => table.lastPage()}
|
||||||
|
disabled={!table.getCanNextPage() || isLoading}
|
||||||
|
>
|
||||||
|
<IconDoubleArrowRight />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useLastComplaints } from '@/app/hooks/react-query/useLastComplaints';
|
||||||
|
import { useLastCases } from '@/app/hooks/react-query/useLastCases';
|
||||||
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useEffect, useState, useRef } from 'react';
|
||||||
|
|
||||||
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
|
import { Navigation, Pagination } from 'swiper/modules';
|
||||||
|
import { IconEye } from '@/app/ui/icons/icons';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
type FilterState = {
|
||||||
|
complaints: boolean;
|
||||||
|
claims: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ViolationsLastCombinedCases() {
|
||||||
|
const swiperRef = useRef(null);
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
const [filters, setFilters] = useState<FilterState>({
|
||||||
|
complaints: true,
|
||||||
|
claims: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: dataComplaint } = useLastComplaints({
|
||||||
|
page: 1,
|
||||||
|
size: 5,
|
||||||
|
sort_by: 'createdAt',
|
||||||
|
sort_direction: 'desc'
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: dataCases } = useLastCases({
|
||||||
|
page: 1,
|
||||||
|
size: 5,
|
||||||
|
sort_by: 'createdAt',
|
||||||
|
sort_direction: 'desc'
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCombinedItems = () => {
|
||||||
|
const complaints = (dataComplaint?.content || []).map(item => ({
|
||||||
|
...item,
|
||||||
|
type: 'complaint' as const,
|
||||||
|
displayId: `COM-${item.id}`,
|
||||||
|
title: item.complaint_text,
|
||||||
|
status: item.status,
|
||||||
|
createdAt: item.created_at,
|
||||||
|
updatedAt: item.updated_at,
|
||||||
|
lawyer: null,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const claims = (dataCases?.content || []).map(item => ({
|
||||||
|
...item,
|
||||||
|
type: 'claim' as const,
|
||||||
|
displayId: item.name,
|
||||||
|
title: item.description,
|
||||||
|
status: item.type,
|
||||||
|
createdAt: item.created_at,
|
||||||
|
updatedAt: item.updated_at,
|
||||||
|
lawyer: item.lawyer,
|
||||||
|
}));
|
||||||
|
|
||||||
|
let combined = [...complaints, ...claims];
|
||||||
|
|
||||||
|
combined.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
||||||
|
|
||||||
|
return combined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFilteredItems = () => {
|
||||||
|
const allItems = getCombinedItems();
|
||||||
|
|
||||||
|
return allItems.filter(item => {
|
||||||
|
if (item.type === 'complaint') return filters.complaints;
|
||||||
|
if (item.type === 'claim') return filters.claims;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleFilter = (filterType: 'complaints' | 'claims') => {
|
||||||
|
if (filters.claims && !filters.complaints && filterType === 'claims') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!filters.claims && filters.complaints && filterType === 'complaints') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilters(prev => ({
|
||||||
|
...prev,
|
||||||
|
[filterType]: !prev[filterType]
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredItems = getFilteredItems();
|
||||||
|
const MAX_VISIBLE_ITEMS = 5;
|
||||||
|
|
||||||
|
const getSlidesPerView = () => {
|
||||||
|
if (!filteredItems || filteredItems.length === 0) return 1;
|
||||||
|
return Math.min(MAX_VISIBLE_ITEMS, filteredItems.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
const slidesPerViewCount = getSlidesPerView();
|
||||||
|
|
||||||
|
const swiperSettings = {
|
||||||
|
modules: [Navigation, Pagination],
|
||||||
|
breakpoints: {
|
||||||
|
0: {
|
||||||
|
slidesPerView: Math.min(slidesPerViewCount, 1),
|
||||||
|
spaceBetween: 20,
|
||||||
|
},
|
||||||
|
768: {
|
||||||
|
slidesPerView: Math.min(slidesPerViewCount, 2),
|
||||||
|
spaceBetween: 30,
|
||||||
|
},
|
||||||
|
1024: {
|
||||||
|
slidesPerView: Math.min(slidesPerViewCount, 3),
|
||||||
|
spaceBetween: 30,
|
||||||
|
},
|
||||||
|
1792: {
|
||||||
|
slidesPerView: slidesPerViewCount,
|
||||||
|
spaceBetween: 30,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loop: filteredItems.length > 3,
|
||||||
|
pagination: {
|
||||||
|
clickable: true,
|
||||||
|
dynamicBullets: false,
|
||||||
|
},
|
||||||
|
/* pagination: {
|
||||||
|
el: '.custom-pagination-container', // Кастомный контейнер для пагинации
|
||||||
|
clickable: true,
|
||||||
|
}, */
|
||||||
|
onSwiper: (swiper: any) => {
|
||||||
|
swiperRef.current = swiper;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isLoading = (!dataComplaint || !dataCases);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="violations-my-cases-section">
|
||||||
|
<div className="my-cases-header">
|
||||||
|
<h3 className="my-cases-title">
|
||||||
|
{t('recent-cases')}
|
||||||
|
</h3>
|
||||||
|
<div className="my-cases-tabs">
|
||||||
|
<button className="tab-button tab-button-complaints">
|
||||||
|
{t('complaints')}
|
||||||
|
</button>
|
||||||
|
<button className="tab-button tab-button-claims">
|
||||||
|
{t('claims')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="cases-carousel-track">
|
||||||
|
<div className="loading-animation">
|
||||||
|
<div className="global-spinner"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAllSelected = filters.complaints && filters.claims;
|
||||||
|
const isNothingSelected = !filters.complaints && !filters.claims;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="violations-my-cases-section">
|
||||||
|
<div className="my-cases-header">
|
||||||
|
<h3 className="my-cases-title">
|
||||||
|
{t('recent-cases')}
|
||||||
|
</h3>
|
||||||
|
<div className="my-cases-tabs">
|
||||||
|
<button
|
||||||
|
className={`tab-button tab-button-complaints ${filters.complaints ? 'active' : ''}`}
|
||||||
|
onClick={() => toggleFilter('complaints')}
|
||||||
|
>
|
||||||
|
{t('complaints')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`tab-button tab-button-claims ${filters.claims ? 'active' : ''}`}
|
||||||
|
onClick={() => toggleFilter('claims')}
|
||||||
|
>
|
||||||
|
{t('claims')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="cases-carousel-track">
|
||||||
|
{isNothingSelected ? (
|
||||||
|
<div className="empty-state">
|
||||||
|
{t('select-at-least-one-filter')}
|
||||||
|
</div>
|
||||||
|
) : filteredItems.length === 0 ? (
|
||||||
|
<div className="empty-state">
|
||||||
|
{t('no-claims-complaints-found')}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Swiper {...swiperSettings}>
|
||||||
|
{filteredItems.map((item) => (
|
||||||
|
<SwiperSlide key={`${item.type}-${item.id}`}>
|
||||||
|
<div className="case-card">
|
||||||
|
<div className="case-card-header">
|
||||||
|
<div className="case-number">
|
||||||
|
{item.displayId}
|
||||||
|
</div>
|
||||||
|
<span className="case-status-badge status-submitted">
|
||||||
|
{item.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="case-card-meta">
|
||||||
|
<div>
|
||||||
|
<strong>{t('date-of-creation')}:</strong> {item.createdAt ? `${formatDate(item.createdAt)} ${formatDateTime(item.createdAt)}` : '---'}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>{t('date-of-update')}:</strong> {item.updatedAt ? `${formatDate(item.updatedAt)} ${formatDateTime(item.updatedAt)}` : '---'}
|
||||||
|
</div>
|
||||||
|
{item.type === 'claim' && (
|
||||||
|
<div>
|
||||||
|
<strong>{t('lawyer')}: </strong>
|
||||||
|
<span style={{ color: '#f59e0b' }}>{item.lawyer ? item.lawyer : t('not-assigned')}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
href={`/pages/file/${item.file_id}?violationId=${item.violation_id}&caseType=${item.type}`}
|
||||||
|
className="mt-auto"
|
||||||
|
title={t('view')}
|
||||||
|
>
|
||||||
|
<IconEye />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
))}
|
||||||
|
</Swiper>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!isNothingSelected && filteredItems.length > 0 && (
|
||||||
|
<div className="swiper-pagination-custom-wrapper"></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
export default function ViolationsMyCases() {
|
|
||||||
return (
|
|
||||||
<div className="violations-my-cases-section">
|
|
||||||
<div className="my-cases-header">
|
|
||||||
<div className="my-cases-title">⚖️ Мои дела</div>
|
|
||||||
<a href="arbitration.php" className="btn-outline-small">Все дела →</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="cases-carousel-track">
|
|
||||||
<div className="case-card">
|
|
||||||
<div className="case-card-header">
|
|
||||||
<div className="case-number">№ARB-2025-000271</div>
|
|
||||||
<span className="case-status-badge status-submitted">ПОДАНО</span></div>
|
|
||||||
|
|
||||||
<div className="case-card-title">
|
|
||||||
Нарушение авторских прав: scale_1200 (2).jpeg</div>
|
|
||||||
|
|
||||||
<div className="case-card-meta">
|
|
||||||
<div><strong>Создано:</strong> 12.10.2025</div>
|
|
||||||
<div><strong>Юрист:</strong> <span style={{color: '#f59e0b'}}>Не назначен</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="case-card-actions">
|
|
||||||
<a href="arbitration.php?case_id=8" className="btn-primary-small">
|
|
||||||
👁️ Детали
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="case-card">
|
|
||||||
<div className="case-card-header">
|
|
||||||
<div className="case-number">№ARB-2025-4944</div>
|
|
||||||
<span className="case-status-badge status-assigned">НАЗНАЧЕН ЮРИСТ</span></div>
|
|
||||||
|
|
||||||
<div className="case-card-title">
|
|
||||||
Нарушение авторских прав: scale_1200 (2).jpeg</div>
|
|
||||||
|
|
||||||
<div className="case-card-meta">
|
|
||||||
<div><strong>Создано:</strong> 23.09.2025</div>
|
|
||||||
<div><strong>Юрист:</strong> Анна Иванова</div>
|
|
||||||
<div><strong>Ущерб:</strong> ₽0</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="case-card-actions">
|
|
||||||
<a href="arbitration.php?case_id=7" className="btn-primary-small">
|
|
||||||
👁️ Детали
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="case-card">
|
|
||||||
<div className="case-card-header">
|
|
||||||
<div className="case-number">№ARB-2025-0535</div>
|
|
||||||
<span className="case-status-badge status-assigned">НАЗНАЧЕН ЮРИСТ</span></div>
|
|
||||||
|
|
||||||
<div className="case-card-title">
|
|
||||||
Нарушение авторских прав: scale_1200 (2).jpeg</div>
|
|
||||||
|
|
||||||
<div className="case-card-meta">
|
|
||||||
<div><strong>Создано:</strong> 22.09.2025</div>
|
|
||||||
<div><strong>Юрист:</strong> Анна Иванова</div>
|
|
||||||
<div><strong>Ущерб:</strong> ₽100 000</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="case-card-actions">
|
|
||||||
<a href="arbitration.php?case_id=6" className="btn-primary-small">
|
|
||||||
👁️ Детали
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useLastCases } from '@/app/hooks/react-query/useLastCases';
|
||||||
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
|
import { Navigation, Pagination } from 'swiper/modules';
|
||||||
|
|
||||||
|
export default function ViolationsMyClaims() {
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
const { data: dataCases } = useLastCases({
|
||||||
|
page: 1,
|
||||||
|
size: 5,
|
||||||
|
sort_by: 'createdAt',
|
||||||
|
sort_direction: 'desc'
|
||||||
|
});
|
||||||
|
|
||||||
|
const MAX_VISIBLE_ITEMS = 5;
|
||||||
|
|
||||||
|
const getSlidesPerView = () => {
|
||||||
|
if (!dataCases || !dataCases.content) return 1;
|
||||||
|
return Math.min(MAX_VISIBLE_ITEMS, dataCases.content.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
const slidesPerViewCount = getSlidesPerView();
|
||||||
|
|
||||||
|
const swiperSettings = {
|
||||||
|
modules: [Navigation, Pagination],
|
||||||
|
breakpoints: {
|
||||||
|
0: {
|
||||||
|
slidesPerView: Math.min(slidesPerViewCount, 1),
|
||||||
|
spaceBetween: 20,
|
||||||
|
},
|
||||||
|
768: {
|
||||||
|
slidesPerView: Math.min(slidesPerViewCount, 2),
|
||||||
|
spaceBetween: 30,
|
||||||
|
},
|
||||||
|
1024: {
|
||||||
|
slidesPerView: Math.min(slidesPerViewCount, 3),
|
||||||
|
spaceBetween: 30,
|
||||||
|
},
|
||||||
|
1792: {
|
||||||
|
slidesPerView: slidesPerViewCount,
|
||||||
|
spaceBetween: 30,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loop: dataCases ? dataCases.content.length > 3 : false,
|
||||||
|
/* navigation: true, */
|
||||||
|
/* pagination: { clickable: true }, */
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!dataCases) {
|
||||||
|
return (
|
||||||
|
<div className="violations-my-cases-section">
|
||||||
|
<div className="my-cases-header">
|
||||||
|
<div className="my-cases-title">
|
||||||
|
{t('recent-claims')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="cases-carousel-track"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="loading-animation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="global-spinner"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="violations-my-cases-section">
|
||||||
|
<div className="my-cases-header">
|
||||||
|
<div className="my-cases-title">
|
||||||
|
{t('recent-claims')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="cases-carousel-track"
|
||||||
|
>
|
||||||
|
{dataCases?.content.length === 0 ? (
|
||||||
|
<div>
|
||||||
|
{t('no-claims')}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Swiper {...swiperSettings}>
|
||||||
|
{dataCases?.content.map((item) => {
|
||||||
|
return (
|
||||||
|
<SwiperSlide key={item.id}>
|
||||||
|
<div
|
||||||
|
className="case-card"
|
||||||
|
key={item.id}
|
||||||
|
>
|
||||||
|
<div className="case-card-header">
|
||||||
|
<div className="case-number">
|
||||||
|
{item.name}
|
||||||
|
</div>
|
||||||
|
<span className="case-status-badge status-submitted">
|
||||||
|
{item.type}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="case-card-content">
|
||||||
|
{t('text-of-the-case')}:
|
||||||
|
<br />
|
||||||
|
<span>
|
||||||
|
{item.description}
|
||||||
|
</span>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
<div className="case-card-meta">
|
||||||
|
<div><strong>{t('date-of-creation')}:</strong> {item.created_at ? `${formatDate(item.created_at)} ${formatDateTime(item.created_at)}` : '---'}</div>
|
||||||
|
<div><strong>{t('date-of-update')}:</strong> {item.updated_at ? `${formatDate(item.updated_at)} ${formatDateTime(item.updated_at)}` : '---'}</div>
|
||||||
|
<div><strong>{t('lawyer')}: </strong>
|
||||||
|
<span style={{ color: '#f59e0b' }}>{item.lawyer ? item.lawyer : t('not-assigned')}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Swiper>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useLastComplaints } from '@/app/hooks/react-query/useLastComplaints';
|
||||||
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
|
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||||
|
import { Navigation, Pagination } from 'swiper/modules';
|
||||||
|
|
||||||
|
export default function ViolationsMyComplaint() {
|
||||||
|
const t = useTranslations('Global');
|
||||||
|
const { data: dataComplaint } = useLastComplaints({
|
||||||
|
page: 1,
|
||||||
|
size: 5,
|
||||||
|
sort_by: 'createdAt',
|
||||||
|
sort_direction: 'desc'
|
||||||
|
});
|
||||||
|
|
||||||
|
const MAX_VISIBLE_ITEMS = 5;
|
||||||
|
|
||||||
|
const getSlidesPerView = () => {
|
||||||
|
if (!dataComplaint || !dataComplaint.content) return 1;
|
||||||
|
return Math.min(MAX_VISIBLE_ITEMS, dataComplaint.content.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
const slidesPerViewCount = getSlidesPerView();
|
||||||
|
|
||||||
|
const swiperSettings = {
|
||||||
|
modules: [Navigation, Pagination],
|
||||||
|
breakpoints: {
|
||||||
|
0: {
|
||||||
|
slidesPerView: Math.min(slidesPerViewCount, 1),
|
||||||
|
spaceBetween: 20,
|
||||||
|
},
|
||||||
|
768: {
|
||||||
|
slidesPerView: Math.min(slidesPerViewCount, 2),
|
||||||
|
spaceBetween: 30,
|
||||||
|
},
|
||||||
|
1024: {
|
||||||
|
slidesPerView: Math.min(slidesPerViewCount, 3),
|
||||||
|
spaceBetween: 30,
|
||||||
|
},
|
||||||
|
1792: {
|
||||||
|
slidesPerView: slidesPerViewCount,
|
||||||
|
spaceBetween: 30,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* navigation: true, */
|
||||||
|
/* pagination: { clickable: true }, */
|
||||||
|
loop: dataComplaint ? dataComplaint.content.length > 3 : false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!dataComplaint) {
|
||||||
|
return (
|
||||||
|
<div className="violations-my-cases-section">
|
||||||
|
<div className="my-cases-header">
|
||||||
|
<div className="my-cases-title">
|
||||||
|
{t('recent-complaints')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="cases-carousel-track"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="loading-animation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="global-spinner"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="violations-my-cases-section">
|
||||||
|
<div className="my-cases-header">
|
||||||
|
<div className="my-cases-title">
|
||||||
|
{t('recent-complaints')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="cases-carousel-track"
|
||||||
|
>
|
||||||
|
{dataComplaint?.content.length === 0 ? (
|
||||||
|
<div>
|
||||||
|
{t('no-complaints')}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Swiper {...swiperSettings}>
|
||||||
|
{dataComplaint?.content.map((item) => {
|
||||||
|
return (
|
||||||
|
<SwiperSlide key={item.id}>
|
||||||
|
<div className="case-card">
|
||||||
|
<div className="case-card-header">
|
||||||
|
<div className="case-number">
|
||||||
|
COM-{item.id}
|
||||||
|
</div>
|
||||||
|
<span className="case-status-badge status-submitted">
|
||||||
|
{item.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* <div className="case-card-content">
|
||||||
|
{t('text-of-the-complaint')}:
|
||||||
|
<br />
|
||||||
|
<span>
|
||||||
|
{item.complaint_text}
|
||||||
|
</span>
|
||||||
|
</div> */}
|
||||||
|
|
||||||
|
<div className="case-card-meta">
|
||||||
|
<div>
|
||||||
|
<strong>{t('date-of-creation')}:</strong> {item.created_at ? `${formatDate(item.created_at)} ${formatDateTime(item.created_at)}` : '---'}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>{t('date-of-update')}:</strong> {item.updated_at ? `${formatDate(item.updated_at)} ${formatDateTime(item.updated_at)}` : '---'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SwiperSlide>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Swiper>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState, useEffect } from 'react';
|
||||||
import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, getFilteredRowModel, ColumnDef, SortingState, ColumnFiltersState } from '@tanstack/react-table';
|
import { useReactTable, getCoreRowModel, getSortedRowModel, getPaginationRowModel, getFilteredRowModel, ColumnDef, SortingState, ColumnFiltersState } from '@tanstack/react-table';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { IconArrowUp, IconArrowDown, IconFilter, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconEye } from '@/app/ui/icons/icons';
|
import { IconArrowUp, IconArrowDown, IconFilter, IconDoubleArrowRight, IconArrowRight, IconDoubleArrowLeft, IconArrowLeft, IconEye } from '@/app/ui/icons/icons';
|
||||||
@@ -9,8 +9,10 @@ import { getViolationFilesArray } from '@/app/actions/violationActions';
|
|||||||
import { useViewport } from '@/app/hooks/useViewport';
|
import { useViewport } from '@/app/hooks/useViewport';
|
||||||
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
import DropDownList from '@/app/components/DropDownList';
|
||||||
|
import { useDebounce } from 'use-debounce';
|
||||||
|
|
||||||
interface ViolationFile {
|
interface FileViolation {
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
fileId: string;
|
fileId: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
@@ -20,34 +22,100 @@ interface ViolationFile {
|
|||||||
status: string;
|
status: string;
|
||||||
supportId: number;
|
supportId: number;
|
||||||
violationCount: number;
|
violationCount: number;
|
||||||
|
countComplaint: number;
|
||||||
|
countLawCase: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ViolationData {
|
||||||
|
content: FileViolation[];
|
||||||
|
totalPages: number;
|
||||||
|
totalElements: number;
|
||||||
|
size: number;
|
||||||
|
number: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ViolationsTable() {
|
export default function ViolationsTable() {
|
||||||
const {
|
|
||||||
data: violationData,
|
|
||||||
isLoading,
|
|
||||||
isError,
|
|
||||||
error,
|
|
||||||
} = useQuery<ViolationFile[]>({
|
|
||||||
queryKey: ['violationData'],
|
|
||||||
queryFn: () => getViolationFilesArray(),
|
|
||||||
|
|
||||||
select: (data) => {
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
/* refetchInterval: 30000 */
|
|
||||||
});
|
|
||||||
const t = useTranslations('Global');
|
const t = useTranslations('Global');
|
||||||
|
const viewport = useViewport();
|
||||||
|
|
||||||
// Состояния
|
const [fileNameFilter, setFileNameFilter] = useState<string>('');
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [dateFilter, setDateFilter] = useState<string>('all');
|
||||||
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
|
||||||
const [pagination, setPagination] = useState({
|
const [pagination, setPagination] = useState({
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
});
|
});
|
||||||
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
|
const [debouncedFileName] = useDebounce(fileNameFilter, 500);
|
||||||
|
|
||||||
|
const getDateRange = (filter: string): { start_date?: string; end_date?: string } => {
|
||||||
|
const now = new Date();
|
||||||
|
const end_date = formatDateTimeForAPI(now);
|
||||||
|
|
||||||
|
switch (filter) {
|
||||||
|
case 'today': {
|
||||||
|
const start_date = formatDateTimeForAPI(new Date(now.setHours(0, 0, 0, 0)));
|
||||||
|
return { start_date, end_date };
|
||||||
|
}
|
||||||
|
case 'week': {
|
||||||
|
const weekAgo = new Date(now);
|
||||||
|
weekAgo.setDate(weekAgo.getDate() - 7);
|
||||||
|
weekAgo.setHours(0, 0, 0, 0);
|
||||||
|
return { start_date: formatDateTimeForAPI(weekAgo), end_date };
|
||||||
|
}
|
||||||
|
case 'month': {
|
||||||
|
const monthAgo = new Date(now);
|
||||||
|
monthAgo.setMonth(monthAgo.getMonth() - 1);
|
||||||
|
monthAgo.setHours(0, 0, 0, 0);
|
||||||
|
return { start_date: formatDateTimeForAPI(monthAgo), end_date };
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDateTimeForAPI = (date: Date): string => {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||||
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const dateRange = getDateRange(dateFilter);
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: violationData,
|
||||||
|
isLoading,
|
||||||
|
isFetching,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
refetch,
|
||||||
|
} = useQuery<ViolationData>({
|
||||||
|
queryKey: ['violationData', pagination.pageIndex, pagination.pageSize, debouncedFileName, dateFilter],
|
||||||
|
queryFn: () => getViolationFilesArray({
|
||||||
|
page: pagination.pageIndex,
|
||||||
|
size: pagination.pageSize,
|
||||||
|
file_name: debouncedFileName || undefined,
|
||||||
|
start_date: dateRange.start_date,
|
||||||
|
end_date: dateRange.end_date,
|
||||||
|
}),
|
||||||
|
select: (data) => {
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
placeholderData: (previousData) => previousData,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (violationData) {
|
||||||
|
setPagination(prev => ({
|
||||||
|
...prev,
|
||||||
|
pageIndex: violationData.number || 0,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [violationData]);
|
||||||
|
|
||||||
const viewport = useViewport();
|
|
||||||
const getMaxLengthByWidth = (): number => {
|
const getMaxLengthByWidth = (): number => {
|
||||||
if (viewport.device === 'MOBILE') return 26;
|
if (viewport.device === 'MOBILE') return 26;
|
||||||
if (viewport.device === 'TABLET') return 32;
|
if (viewport.device === 'TABLET') return 32;
|
||||||
@@ -73,115 +141,83 @@ export default function ViolationsTable() {
|
|||||||
const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3;
|
const maxNameLength = MAX_FILE_NAME_LENGTH - extension.length - 3;
|
||||||
|
|
||||||
if (maxNameLength <= 0) {
|
if (maxNameLength <= 0) {
|
||||||
return '...' /* + extension */;
|
return '...';
|
||||||
}
|
}
|
||||||
|
|
||||||
return nameWithoutExtension.substring(0, maxNameLength) + '...' /* + extension */;
|
return nameWithoutExtension.substring(0, maxNameLength) + '...';
|
||||||
}
|
};
|
||||||
|
|
||||||
const columns = useMemo<ColumnDef<ViolationFile>[]>(
|
const baseColumns = useMemo<ColumnDef<FileViolation>[]>(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
accessorKey: 'supportId',
|
accessorKey: 'supportId',
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<span>
|
<span>ID</span>
|
||||||
ID
|
|
||||||
</span>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
className="sort-button"
|
className="sort-button"
|
||||||
>
|
>
|
||||||
{
|
{column.getIsSorted() === 'asc' ? (
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
<IconArrowUp />
|
||||||
</span>
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
<IconArrowDown />
|
||||||
</span>
|
) : (
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
<IconFilter />
|
||||||
</span>
|
)}
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => (
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="text-center table-item table-item-id">
|
<div className="text-center table-item table-item-id">
|
||||||
{row.original.supportId}
|
{row.original.supportId}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
),
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'fileName',
|
accessorKey: 'fileName',
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<div className="column start">
|
<div className="column start">
|
||||||
<span>
|
<span>{t('file')}</span>
|
||||||
{t('file')}
|
|
||||||
</span>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
className="sort-button"
|
className="sort-button"
|
||||||
>
|
>
|
||||||
{
|
{column.getIsSorted() === 'asc' ? (
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
<IconArrowUp />
|
||||||
</span>
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
<IconArrowDown />
|
||||||
</span>
|
) : (
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
<IconFilter />
|
||||||
</span>
|
)}
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => (
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div
|
<div
|
||||||
className="flex items-center space-x-2 table-item table-item-file-name"
|
className="flex items-center space-x-2 table-item table-item-file-name"
|
||||||
title={row.original.fileName}
|
title={row.original.fileName}
|
||||||
>
|
>
|
||||||
{cutFileName(row.original.fileName)}
|
{cutFileName(row.original.fileName)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
),
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'violationCount',
|
accessorKey: 'violationCount',
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<span>
|
<span>{t('matches')}</span>
|
||||||
{t('violation')}
|
|
||||||
</span>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
className="sort-button"
|
className="sort-button"
|
||||||
>
|
>
|
||||||
{
|
{column.getIsSorted() === 'asc' ? (
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
<IconArrowUp />
|
||||||
</span>
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
<IconArrowDown />
|
||||||
</span>
|
) : (
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
<IconFilter />
|
||||||
</span>
|
)}
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -192,66 +228,72 @@ export default function ViolationsTable() {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'latestViolationDate',
|
accessorKey: 'countComplaint',
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<span>
|
<span>{t('complaint')}</span>
|
||||||
{t('date')}
|
|
||||||
</span>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
className="sort-button"
|
className="sort-button"
|
||||||
>
|
>
|
||||||
{
|
{column.getIsSorted() === 'asc' ? (
|
||||||
column.getIsSorted() === 'asc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowUp />
|
<IconArrowUp />
|
||||||
</span>
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
: column.getIsSorted() === 'desc' ?
|
|
||||||
<span>
|
|
||||||
<IconArrowDown />
|
<IconArrowDown />
|
||||||
</span>
|
) : (
|
||||||
: <span>
|
|
||||||
<IconFilter />
|
<IconFilter />
|
||||||
</span>
|
)}
|
||||||
}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="text-center table-item">
|
<div className="text-center table-item">
|
||||||
{formatDate(row.original.latestViolationDate)}
|
{row.original.countComplaint}
|
||||||
<br />
|
|
||||||
{formatDateTime(row.original.latestViolationDate)}
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'actions',
|
accessorKey: 'countLawCase',
|
||||||
header: ({ column }) => (
|
header: ({ column }) => (
|
||||||
<div className="column">
|
<div className="column">
|
||||||
<span>
|
<span>{t('claims')}</span>
|
||||||
{t('actions')}
|
<button
|
||||||
</span>
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
className="sort-button"
|
||||||
|
>
|
||||||
|
{column.getIsSorted() === 'asc' ? (
|
||||||
|
<IconArrowUp />
|
||||||
|
) : column.getIsSorted() === 'desc' ? (
|
||||||
|
<IconArrowDown />
|
||||||
|
) : (
|
||||||
|
<IconFilter />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => (
|
||||||
return (
|
<div className="text-center table-item">
|
||||||
|
{row.original.countLawCase}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[t, viewport]
|
||||||
|
);
|
||||||
|
|
||||||
|
const actionsColumn = useMemo<ColumnDef<FileViolation>>(
|
||||||
|
() => ({
|
||||||
|
accessorKey: 'actions',
|
||||||
|
header: () => (
|
||||||
|
<div className="column">
|
||||||
|
<span>{t('actions')}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
<div className="actions">
|
<div className="actions">
|
||||||
<div
|
<div className="actions-group">
|
||||||
className="actions-group"
|
|
||||||
>
|
|
||||||
{/* <button
|
|
||||||
onClick={() => {
|
|
||||||
console.log(row.original.fileId);
|
|
||||||
}}
|
|
||||||
className="bg-violet-500 hover:bg-violet-600"
|
|
||||||
title={t('view')}
|
|
||||||
>
|
|
||||||
<IconEye />
|
|
||||||
</button> */}
|
|
||||||
<Link
|
<Link
|
||||||
href={`/pages/file/${row.original.fileId}`}
|
href={`/pages/file/${row.original.fileId}?startDate=${dateRange.start_date || ''}&endDate=${dateRange.end_date || ''}`}
|
||||||
className="bg-violet-500 hover:bg-violet-600"
|
className="bg-violet-500 hover:bg-violet-600"
|
||||||
title={t('view')}
|
title={t('view')}
|
||||||
>
|
>
|
||||||
@@ -259,62 +301,58 @@ export default function ViolationsTable() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
),
|
||||||
}
|
}),
|
||||||
},
|
[t, dateRange.start_date, dateRange.end_date]
|
||||||
],
|
);
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const columns = useMemo(() => [...baseColumns, actionsColumn], [baseColumns, actionsColumn]);
|
||||||
let result = violationData;
|
|
||||||
if (!result) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}, [violationData])
|
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: filteredData,
|
data: violationData?.content || [],
|
||||||
columns,
|
columns,
|
||||||
state: {
|
state: {
|
||||||
sorting,
|
sorting,
|
||||||
columnFilters,
|
pagination,
|
||||||
pagination
|
|
||||||
},
|
},
|
||||||
|
pageCount: violationData?.totalPages || -1,
|
||||||
autoResetPageIndex: false,
|
autoResetPageIndex: false,
|
||||||
|
manualPagination: true, // Пагинация с бека
|
||||||
|
manualSorting: false, // сортировка локально так как эндпоинт не поддерживает сортировку.
|
||||||
|
manualFiltering: true, // Фильтрация с бека
|
||||||
onPaginationChange: setPagination,
|
onPaginationChange: setPagination,
|
||||||
onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
onColumnFiltersChange: setColumnFilters,
|
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getSortedRowModel: getSortedRowModel(),
|
getSortedRowModel: getSortedRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
|
||||||
initialState: {
|
|
||||||
pagination: {
|
|
||||||
pageSize: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleFileNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFileNameFilter(e.target.value);
|
||||||
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDateFilterChange = (value: string) => {
|
||||||
|
setDateFilter(value);
|
||||||
|
setPagination(prev => ({ ...prev, pageIndex: 0 }));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
return <div className="block-wrapper">Error: {error?.message || t('error')}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="block-wrapper">
|
||||||
className="block-wrapper"
|
|
||||||
>
|
|
||||||
<div className="tab-content">
|
<div className="tab-content">
|
||||||
<div
|
<div className="tanstak-table-wrapper">
|
||||||
className="tanstak-table-wrapper"
|
<h3 className="tanstak-table-title">
|
||||||
>
|
{t('matches-detected')}
|
||||||
<h3
|
|
||||||
className="tanstak-table-title"
|
|
||||||
>
|
|
||||||
{t('violations-detected')}
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
{/* Фильтры */}
|
{/* Фильтры */}
|
||||||
{/* <div className="tanstak-table-filtres">
|
<div className="tanstak-table-filtres">
|
||||||
<div className="table-filtres-wrapper">
|
<div className="table-filtres-wrapper">
|
||||||
|
{/* Фильтр по дате */}
|
||||||
<div className="table-filtres-item">
|
<div className="table-filtres-item">
|
||||||
<div className="table-filtres-label">{t('date-filter')}:</div>
|
<div className="table-filtres-label">{t('date-filter')}:</div>
|
||||||
<DropDownList
|
<DropDownList
|
||||||
@@ -322,48 +360,47 @@ export default function ViolationsTable() {
|
|||||||
translatedValue={(() => {
|
translatedValue={(() => {
|
||||||
switch (dateFilter) {
|
switch (dateFilter) {
|
||||||
case 'all':
|
case 'all':
|
||||||
return t('all-dates')
|
return t('all-dates');
|
||||||
case 'week':
|
case 'week':
|
||||||
return t('for-a-week')
|
return t('for-a-week');
|
||||||
case 'month':
|
case 'month':
|
||||||
return t('for-a-month')
|
return t('for-a-month');
|
||||||
case 'older':
|
|
||||||
return t('older-than-a-month')
|
|
||||||
default:
|
default:
|
||||||
return t('today')
|
return t('today');
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
callBack={setDateFilter}
|
callBack={handleDateFilterChange}
|
||||||
>
|
>
|
||||||
<li value="all">
|
<li value="all">{t('all-dates')}</li>
|
||||||
{t('all-dates')}
|
<li value="today">{t('today')}</li>
|
||||||
</li>
|
<li value="week">{t('for-a-week')}</li>
|
||||||
<li value="today">
|
<li value="month">{t('for-a-month')}</li>
|
||||||
{t('today')}
|
|
||||||
</li>
|
|
||||||
<li value="week">
|
|
||||||
{t('for-a-week')}
|
|
||||||
</li>
|
|
||||||
<li value="month">
|
|
||||||
{t('for-a-month')}
|
|
||||||
</li>
|
|
||||||
<li value="older">
|
|
||||||
{t('older-than-a-month')}
|
|
||||||
</li>
|
|
||||||
</DropDownList>
|
</DropDownList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Фильтр по имени файла */}
|
||||||
|
<div className="table-filtres-item">
|
||||||
|
<div className="table-filtres-label">{t('file-name')}:</div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={fileNameFilter}
|
||||||
|
onChange={handleFileNameChange}
|
||||||
|
placeholder={t('search')}
|
||||||
|
className="table-filtres-text-filter"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className="table-filtres-wrapper">
|
||||||
className="table-filtres-wrapper"
|
|
||||||
>
|
|
||||||
<div className="table-filtres-item">
|
<div className="table-filtres-item">
|
||||||
<div className="table-filtres-label">{t('items-per-page')}:</div>
|
<div className="table-filtres-label">{t('items-per-page')}:</div>
|
||||||
<DropDownList
|
<DropDownList
|
||||||
value={table.getState().pagination.pageSize}
|
value={table.getState().pagination.pageSize}
|
||||||
//@ts-ignore сделал так потому что для table.setPageSize нужна цифра
|
//@ts-ignore
|
||||||
callBack={table.setPageSize}
|
callBack={(value: number) => {
|
||||||
|
table.setPageSize(value);
|
||||||
|
setPagination(prev => ({ ...prev, pageSize: value, pageIndex: 0 }));
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{[5, 10, 20, 50, 100].map(pageSize => (
|
{[5, 10, 20, 50, 100].map(pageSize => (
|
||||||
<li key={pageSize} value={pageSize}>
|
<li key={pageSize} value={pageSize}>
|
||||||
@@ -373,8 +410,7 @@ export default function ViolationsTable() {
|
|||||||
</DropDownList>
|
</DropDownList>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div> */}
|
|
||||||
|
|
||||||
{/* Таблица */}
|
{/* Таблица */}
|
||||||
<div className="tanstak-table-block">
|
<div className="tanstak-table-block">
|
||||||
@@ -383,20 +419,32 @@ export default function ViolationsTable() {
|
|||||||
{table.getHeaderGroups().map(headerGroup => (
|
{table.getHeaderGroups().map(headerGroup => (
|
||||||
<tr key={headerGroup.id}>
|
<tr key={headerGroup.id}>
|
||||||
{headerGroup.headers.map(header => (
|
{headerGroup.headers.map(header => (
|
||||||
<th
|
<th key={header.id}>
|
||||||
key={header.id}
|
|
||||||
>
|
|
||||||
{header.isPlaceholder
|
{header.isPlaceholder
|
||||||
? null
|
? null
|
||||||
: typeof header.column.columnDef.header === 'function'
|
: typeof header.column.columnDef.header === 'function'
|
||||||
? header.column.columnDef.header(header.getContext())
|
? header.column.columnDef.header(header.getContext())
|
||||||
: header.column.columnDef.header as string}
|
: (header.column.columnDef.header as string)}
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="tanstak-table-body">
|
<tbody className={`tanstak-table-body ${isFetching ? 'loading' : ''}`}>
|
||||||
|
{isLoading && (
|
||||||
|
<tr>
|
||||||
|
<td colSpan={columns.length} className="text-center relative h-12.5">
|
||||||
|
<div
|
||||||
|
className="loading-animation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="global-spinner"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
{table.getRowModel().rows.length > 0 ? (
|
{table.getRowModel().rows.length > 0 ? (
|
||||||
table.getRowModel().rows.map(row => (
|
table.getRowModel().rows.map(row => (
|
||||||
<tr key={row.id}>
|
<tr key={row.id}>
|
||||||
@@ -404,16 +452,18 @@ export default function ViolationsTable() {
|
|||||||
<td key={cell.id}>
|
<td key={cell.id}>
|
||||||
{typeof cell.column.columnDef.cell === 'function'
|
{typeof cell.column.columnDef.cell === 'function'
|
||||||
? cell.column.columnDef.cell(cell.getContext())
|
? cell.column.columnDef.cell(cell.getContext())
|
||||||
: cell.getValue() as string}
|
: (cell.getValue() as string)}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<tr>
|
<tr>
|
||||||
|
{!isLoading && (
|
||||||
<td colSpan={columns.length} className="text-center">
|
<td colSpan={columns.length} className="text-center">
|
||||||
{t('no-data-for-selected-filters')}
|
{t('no-data-for-selected-filters')}
|
||||||
</td>
|
</td>
|
||||||
|
)}
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -426,16 +476,12 @@ export default function ViolationsTable() {
|
|||||||
<span className="pagination-info-pages">
|
<span className="pagination-info-pages">
|
||||||
{t('page')}{' '}
|
{t('page')}{' '}
|
||||||
<strong>
|
<strong>
|
||||||
{table.getState().pagination.pageIndex + 1} {t('out-of')} {table.getPageCount() ? table.getPageCount() : 1}
|
{table.getState().pagination.pageIndex + 1} {t('out-of')} {violationData?.totalPages || 1}
|
||||||
</strong>
|
</strong> | {t('total-matches')}: {violationData?.totalElements}
|
||||||
</span>
|
|
||||||
<span className="pagination-info-files">
|
|
||||||
| {t('shown')} {table.getRowModel().rows.length} {t('out-of')} {filteredData.length}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="pagination-controls">
|
<div className="pagination-controls">
|
||||||
{table.getCanPreviousPage() && (
|
|
||||||
<button
|
<button
|
||||||
className="arrow"
|
className="arrow"
|
||||||
onClick={() => table.firstPage()}
|
onClick={() => table.firstPage()}
|
||||||
@@ -443,8 +489,6 @@ export default function ViolationsTable() {
|
|||||||
>
|
>
|
||||||
<IconDoubleArrowLeft />
|
<IconDoubleArrowLeft />
|
||||||
</button>
|
</button>
|
||||||
)}
|
|
||||||
{table.getCanPreviousPage() && (
|
|
||||||
<button
|
<button
|
||||||
className="arrow"
|
className="arrow"
|
||||||
onClick={() => table.previousPage()}
|
onClick={() => table.previousPage()}
|
||||||
@@ -452,26 +496,28 @@ export default function ViolationsTable() {
|
|||||||
>
|
>
|
||||||
<IconArrowLeft />
|
<IconArrowLeft />
|
||||||
</button>
|
</button>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="pagination-controls-pages">
|
<div className="pagination-controls-pages">
|
||||||
{Array.from({ length: Math.min(5, table.getPageCount()) }, (_, i) => {
|
{Array.from({ length: Math.min(5, violationData?.totalPages || 0) }, (_, i) => {
|
||||||
const pageIndex = Math.max(
|
const totalPages = violationData?.totalPages || 0;
|
||||||
0,
|
const currentPage = table.getState().pagination.pageIndex;
|
||||||
Math.min(
|
let pageIndex = 0;
|
||||||
table.getPageCount() - 5,
|
|
||||||
table.getState().pagination.pageIndex - 2
|
|
||||||
)
|
|
||||||
) + i;
|
|
||||||
|
|
||||||
if (pageIndex < table.getPageCount()) {
|
if (totalPages <= 5) {
|
||||||
|
pageIndex = i;
|
||||||
|
} else if (currentPage <= 2) {
|
||||||
|
pageIndex = i;
|
||||||
|
} else if (currentPage >= totalPages - 3) {
|
||||||
|
pageIndex = totalPages - 5 + i;
|
||||||
|
} else {
|
||||||
|
pageIndex = currentPage - 2 + i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageIndex < totalPages) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={pageIndex}
|
key={pageIndex}
|
||||||
className={`${table.getState().pagination.pageIndex === pageIndex
|
className={table.getState().pagination.pageIndex === pageIndex ? 'current' : 'other'}
|
||||||
? 'current'
|
|
||||||
: 'other'
|
|
||||||
}`}
|
|
||||||
onClick={() => table.setPageIndex(pageIndex)}
|
onClick={() => table.setPageIndex(pageIndex)}
|
||||||
>
|
>
|
||||||
{pageIndex + 1}
|
{pageIndex + 1}
|
||||||
@@ -482,7 +528,6 @@ export default function ViolationsTable() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{table.getCanNextPage() && (
|
|
||||||
<button
|
<button
|
||||||
className="arrow"
|
className="arrow"
|
||||||
onClick={() => table.nextPage()}
|
onClick={() => table.nextPage()}
|
||||||
@@ -490,8 +535,6 @@ export default function ViolationsTable() {
|
|||||||
>
|
>
|
||||||
<IconArrowRight />
|
<IconArrowRight />
|
||||||
</button>
|
</button>
|
||||||
)}
|
|
||||||
{table.getCanNextPage() && (
|
|
||||||
<button
|
<button
|
||||||
className="arrow"
|
className="arrow"
|
||||||
onClick={() => table.lastPage()}
|
onClick={() => table.lastPage()}
|
||||||
@@ -499,11 +542,10 @@ export default function ViolationsTable() {
|
|||||||
>
|
>
|
||||||
<IconDoubleArrowRight />
|
<IconDoubleArrowRight />
|
||||||
</button>
|
</button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
+107
-7
@@ -188,7 +188,8 @@
|
|||||||
"table-description": "Table of all files",
|
"table-description": "Table of all files",
|
||||||
"protected-image": "Protected images",
|
"protected-image": "Protected images",
|
||||||
"protected-video": "Protected videos",
|
"protected-video": "Protected videos",
|
||||||
"protected-audio": "Protected audio",
|
"protected-audio": "Protected audios",
|
||||||
|
"protected-document": "Protected documents",
|
||||||
"image-protected": "Images Protected",
|
"image-protected": "Images Protected",
|
||||||
"video-protected": "Videos Protected",
|
"video-protected": "Videos Protected",
|
||||||
"audio-protected": "Audios Protected",
|
"audio-protected": "Audios Protected",
|
||||||
@@ -204,6 +205,7 @@
|
|||||||
"violations-found": "Violations found",
|
"violations-found": "Violations found",
|
||||||
"total-violations": "Total violations",
|
"total-violations": "Total violations",
|
||||||
"violations-registered": "Violations registered",
|
"violations-registered": "Violations registered",
|
||||||
|
"claims-registered": "Claims registered",
|
||||||
"new": "new",
|
"new": "new",
|
||||||
"in-progress": "In progress",
|
"in-progress": "In progress",
|
||||||
"it-decided": "It's decided",
|
"it-decided": "It's decided",
|
||||||
@@ -309,6 +311,7 @@
|
|||||||
"file-file-name-already-exists": "The file {fileName} already exists on the account.",
|
"file-file-name-already-exists": "The file {fileName} already exists on the account.",
|
||||||
"file-file-name-does-not-exist": "The file {fileName} does not exist on the account.",
|
"file-file-name-does-not-exist": "The file {fileName} does not exist on the account.",
|
||||||
"violations-detected": "Violations detected",
|
"violations-detected": "Violations detected",
|
||||||
|
"matches-detected": "Matches detected",
|
||||||
"content-verification-status": "Content verification status",
|
"content-verification-status": "Content verification status",
|
||||||
"violation-data-updated": "Violation data updated",
|
"violation-data-updated": "Violation data updated",
|
||||||
"open": "Open",
|
"open": "Open",
|
||||||
@@ -323,7 +326,7 @@
|
|||||||
"general-indicators-of-the-protection-system": "General indicators of the protection system",
|
"general-indicators-of-the-protection-system": "General indicators of the protection system",
|
||||||
"total-number-of-checks": "Total number of checks",
|
"total-number-of-checks": "Total number of checks",
|
||||||
"protected-content": "Protected content",
|
"protected-content": "Protected content",
|
||||||
"geography-of-violations": "Geography of violations",
|
"geography-of-matches": "Geography of matches",
|
||||||
"activity-by-month": "Activity by month",
|
"activity-by-month": "Activity by month",
|
||||||
"january": "January",
|
"january": "January",
|
||||||
"february": "February",
|
"february": "February",
|
||||||
@@ -350,6 +353,7 @@
|
|||||||
"violation-count": "Violation count",
|
"violation-count": "Violation count",
|
||||||
"your-files": "Your files",
|
"your-files": "Your files",
|
||||||
"files-under-protection": "Files under protection",
|
"files-under-protection": "Files under protection",
|
||||||
|
"matches": "Matches",
|
||||||
"matches-found": "Matches found",
|
"matches-found": "Matches found",
|
||||||
"violations-in-progress": "Violations in progress",
|
"violations-in-progress": "Violations in progress",
|
||||||
"sum-of-pay-is-required": "Sum of payout is required",
|
"sum-of-pay-is-required": "Sum of payout is required",
|
||||||
@@ -381,7 +385,9 @@
|
|||||||
"violation-id": "Violation ID",
|
"violation-id": "Violation ID",
|
||||||
"no-information-about-the-complaint": "No information about the complaint",
|
"no-information-about-the-complaint": "No information about the complaint",
|
||||||
"the-complaint-has-not-been-registered": "The complaint has not been registered",
|
"the-complaint-has-not-been-registered": "The complaint has not been registered",
|
||||||
|
"the-claims-has-not-been-registered": "The claims has not been registered",
|
||||||
"the-complaint-has-been-registered": "The complaint has been registered",
|
"the-complaint-has-been-registered": "The complaint has been registered",
|
||||||
|
"the-claims-has-been-registered": "The claims has been registered",
|
||||||
"all-statuses": "All statuses",
|
"all-statuses": "All statuses",
|
||||||
"all": "All",
|
"all": "All",
|
||||||
"mark-all-as-read": "Mark all as read",
|
"mark-all-as-read": "Mark all as read",
|
||||||
@@ -390,6 +396,7 @@
|
|||||||
"deselect": "Deselect",
|
"deselect": "Deselect",
|
||||||
"select-all": "Select all",
|
"select-all": "Select all",
|
||||||
"text-area-error-fill-complaint-text": "Fill out the complaint text",
|
"text-area-error-fill-complaint-text": "Fill out the complaint text",
|
||||||
|
"text-area-error-complaint-text-too-long": "The complaint text must not exceed 2000 characters",
|
||||||
"to-view-a-file-match-click-on-the-card-from-the-list": "To view a file match, click on the card from the list",
|
"to-view-a-file-match-click-on-the-card-from-the-list": "To view a file match, click on the card from the list",
|
||||||
"insufficient-funds-to-receive-payment": "Insufficient funds to receive payment",
|
"insufficient-funds-to-receive-payment": "Insufficient funds to receive payment",
|
||||||
"not-found-payout-method": "Not found payout method",
|
"not-found-payout-method": "Not found payout method",
|
||||||
@@ -409,22 +416,84 @@
|
|||||||
"type": "Type",
|
"type": "Type",
|
||||||
"lawyer": "Lawyer",
|
"lawyer": "Lawyer",
|
||||||
"not-assigned": "Not assigned",
|
"not-assigned": "Not assigned",
|
||||||
|
"case": "Case",
|
||||||
"create-case": "Create case",
|
"create-case": "Create case",
|
||||||
"case-name": "Case name",
|
"case-name": "Case name",
|
||||||
"amount-of-damage": "Amount of damage",
|
"amount-of-damage": "Amount of damage",
|
||||||
"text-of-the-case": "Text of the case",
|
"text-of-the-case": "Text of the case",
|
||||||
"fill-text-of-the-case": "Fill the case text",
|
"fill-text-of-the-case": "Fill the case text",
|
||||||
"fill-title-of-the-case": "Fill the case title",
|
"fill-title-of-the-case": "Fill the case title",
|
||||||
|
"claim-title-must-not-exceed-300-characters": "The claim title must not exceed 300 characters",
|
||||||
|
"claim-text-must-not-exceed-2000-characters": "The text of the claim must not exceed 2000 characters",
|
||||||
"enter-the-amount-of-damage": "Enter the amount of damage",
|
"enter-the-amount-of-damage": "Enter the amount of damage",
|
||||||
"download-all": "Download all",
|
"download-all": "Download all",
|
||||||
"selected-files": "Selected files",
|
"selected-files": "Selected files",
|
||||||
"protected-files": "Protected files",
|
"protected-files": "Protected files",
|
||||||
"storage": "Storage",
|
"storage": "Storage",
|
||||||
"Status": "Status",
|
"Status": "Status",
|
||||||
"under-moderation": "Under moderation",
|
|
||||||
"full-link": "Full link",
|
"full-link": "Full link",
|
||||||
"follow-the-link": "Follow the link",
|
"follow-the-link": "Follow the link",
|
||||||
"date-of-discovery": "Date of discovery"
|
"date-of-discovery": "Date of discovery",
|
||||||
|
"file-a-claim": "File a claim",
|
||||||
|
"file-a-complaint": "File a complaint",
|
||||||
|
"you-can": "You can",
|
||||||
|
"complaint": "Complaint",
|
||||||
|
"complaint-name": "Complaint name",
|
||||||
|
"recent-complaints": "Recent complaints",
|
||||||
|
"recent-claims": "Recent claims",
|
||||||
|
"no-complaints": "No complaints",
|
||||||
|
"no-claims": "No claims",
|
||||||
|
"in": "in",
|
||||||
|
"last-content-check": "Last content check",
|
||||||
|
"day": "day",
|
||||||
|
"week_accusative": "week",
|
||||||
|
"month": "month",
|
||||||
|
"every": "every",
|
||||||
|
"country": "Country",
|
||||||
|
"claims": "Claims",
|
||||||
|
"complaints": "Complaints",
|
||||||
|
"recent-cases": "Recent cases",
|
||||||
|
"select-at-least-one-filter": "Select at least one filter",
|
||||||
|
"select-document-to-upload": "Select document(s) to upload",
|
||||||
|
"uploading-id-images": "Uploading ID images",
|
||||||
|
"tracking": "Tracking",
|
||||||
|
"save-template": "Save template",
|
||||||
|
"save": "Save",
|
||||||
|
"template-name": "Template name",
|
||||||
|
"enter-template-name": "Enter template name",
|
||||||
|
"complaint-text-preview": "Complaint text preview",
|
||||||
|
"no-text": "No text",
|
||||||
|
"please-enter-complaint-text-first": "Please enter complaint text first",
|
||||||
|
"please-enter-template-name": "Please enter template name",
|
||||||
|
"template-saved-successfully": "Template saved successfully",
|
||||||
|
"no-claims-complaints-found": "No complaints/claims found",
|
||||||
|
"no-notifications": "No notifications",
|
||||||
|
"who-opened": "Who opened",
|
||||||
|
"time": "Time",
|
||||||
|
"location": "Location",
|
||||||
|
"history-of-opened": "History of opened",
|
||||||
|
"not-defined": "Not defined",
|
||||||
|
"not-defined-city": "Not defined",
|
||||||
|
"not-defined-country": "Not defined",
|
||||||
|
"total-document-openings": "Total document openings",
|
||||||
|
"total-matches": "Total matches",
|
||||||
|
"total-complaints": "Total complaints",
|
||||||
|
"total-claims": "Total claims",
|
||||||
|
"city": "City",
|
||||||
|
"closed": "Closed",
|
||||||
|
"blocked": "Blocked",
|
||||||
|
"pending-moderation": "Pending moderation",
|
||||||
|
"file-an-appeal": "File an appeal",
|
||||||
|
"please-enter-appeal-reason": "Please enter the reason for appeal",
|
||||||
|
"appeal-submitted-successfully": "Appeal submitted successfully",
|
||||||
|
"appeal-submission-failed": "Failed to submit appeal",
|
||||||
|
"appeal-reason": "Reason for appeal",
|
||||||
|
"enter-appeal-reason": "Describe the reason for your appeal...",
|
||||||
|
"additional-info": "Additional information",
|
||||||
|
"enter-additional-info": "Any additional information that may help...",
|
||||||
|
"submit-appeal": "Submit appeal",
|
||||||
|
"submitting": "Submitting...",
|
||||||
|
"active-appeal-already-exists-for-this-file": "Active appeal already exists for this file"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "and",
|
"and": "and",
|
||||||
@@ -534,7 +603,8 @@
|
|||||||
"COMPLAINT_IN_WORK": "Complaint in work",
|
"COMPLAINT_IN_WORK": "Complaint in work",
|
||||||
"COMPLAINT_AND_LEGAL_IN_WORK": "Complaint and legal in work",
|
"COMPLAINT_AND_LEGAL_IN_WORK": "Complaint and legal in work",
|
||||||
"AUTHORIZED_USE": "Authorized use",
|
"AUTHORIZED_USE": "Authorized use",
|
||||||
"CREATED": "New"
|
"CREATED": "New",
|
||||||
|
"NOT_OWNER_FILE": "Not my file"
|
||||||
},
|
},
|
||||||
"Notifications": {
|
"Notifications": {
|
||||||
"notification-search-result": "Search result",
|
"notification-search-result": "Search result",
|
||||||
@@ -554,6 +624,36 @@
|
|||||||
"notification-token-not-found": "Token not found",
|
"notification-token-not-found": "Token not found",
|
||||||
"notification-file-moderation": "File moderation event",
|
"notification-file-moderation": "File moderation event",
|
||||||
"notification-file-added": "File added to system",
|
"notification-file-added": "File added to system",
|
||||||
"notification-search-operation-filed": "Search operation failed"
|
"notification-search-operation-filed": "Search operation failed",
|
||||||
}
|
"not-specified": "not specified",
|
||||||
|
"not-specified-date": "not specified",
|
||||||
|
"not-specified-file-name": "not specified",
|
||||||
|
"message-missing": "message missing",
|
||||||
|
"reason-not-specified": "reason not specified",
|
||||||
|
"no-comment": "no comment",
|
||||||
|
"unknown-user": "unknown user",
|
||||||
|
"error-not-described": "error not described",
|
||||||
|
"not-scheduled": "not scheduled",
|
||||||
|
"notification-without-text": "Notification without text",
|
||||||
|
"user-verified": "Status: {status}. Update date: {updateDate}",
|
||||||
|
"monitoring-result": "Monitoring result: {message}",
|
||||||
|
"complaint-status-changed": "Complaint status changed from {oldStatus} to {newStatus}. Violation: {violation}. File: {file}",
|
||||||
|
"file-added-to-system": "Status: {status}. File name: {fileName}",
|
||||||
|
"file-moderation-event": "File moderation for '{fileName}'. Status changed from {oldStatus} to {newStatus}. Comment: {comment}",
|
||||||
|
"payment-result": "Payment result: {status}. Amount: {amount}",
|
||||||
|
"referral-registered": "Referral registered: {referralUser}",
|
||||||
|
"search-result": "Search results from {startDate} to {endDate}. Type: {searchType}. Files found: {count}",
|
||||||
|
"search-operation-failed": "Status: {status}. Message: {message}. Reason: {reason}. Next run: {nextRun}",
|
||||||
|
"token-not-found": "Current: {currentTokens}, needed: {needTokens}. Message: {message}. Next run: {nextRun}"
|
||||||
|
},
|
||||||
|
"User-status": {
|
||||||
|
"under-moderation": "Under moderation",
|
||||||
|
"NOT_VERIFIED": "Not verified",
|
||||||
|
"VERIFIED": "Verified",
|
||||||
|
"VERIFICATION_FAILED": "Verification failed",
|
||||||
|
"unknow": "Unknown",
|
||||||
|
"user-verification": "User verification",
|
||||||
|
"Status": "Status"
|
||||||
|
},
|
||||||
|
"Countryes": {}
|
||||||
}
|
}
|
||||||
+110
-10
@@ -189,6 +189,7 @@
|
|||||||
"protected-image": "Защищенные изображения",
|
"protected-image": "Защищенные изображения",
|
||||||
"protected-video": "Защищенные видео",
|
"protected-video": "Защищенные видео",
|
||||||
"protected-audio": "Защищенные аудио",
|
"protected-audio": "Защищенные аудио",
|
||||||
|
"protected-document": "Защищенные документы",
|
||||||
"image-protected": "Изображений защищено",
|
"image-protected": "Изображений защищено",
|
||||||
"video-protected": "Видео защищено",
|
"video-protected": "Видео защищено",
|
||||||
"audio-protected": "Аудио защищено",
|
"audio-protected": "Аудио защищено",
|
||||||
@@ -203,7 +204,8 @@
|
|||||||
"document-occupy": "Занимают документы",
|
"document-occupy": "Занимают документы",
|
||||||
"violations-found": "Найдено нарушений",
|
"violations-found": "Найдено нарушений",
|
||||||
"total-violations": "Всего нарушений",
|
"total-violations": "Всего нарушений",
|
||||||
"violations-registered": "Нарушений зарегистрированно",
|
"violations-registered": "Жалоб зарегистрировано",
|
||||||
|
"claims-registered": "Претензий зарегистрировано",
|
||||||
"new": "Новые",
|
"new": "Новые",
|
||||||
"in-progress": "В работе",
|
"in-progress": "В работе",
|
||||||
"it-decided": "Решено",
|
"it-decided": "Решено",
|
||||||
@@ -309,6 +311,7 @@
|
|||||||
"file-file-name-already-exists": "Файл {fileName} уже есть на аккаунте.",
|
"file-file-name-already-exists": "Файл {fileName} уже есть на аккаунте.",
|
||||||
"file-file-name-does-not-exist": "Файла {fileName} на аккаунте нет.",
|
"file-file-name-does-not-exist": "Файла {fileName} на аккаунте нет.",
|
||||||
"violations-detected": "Обнаруженные нарушения",
|
"violations-detected": "Обнаруженные нарушения",
|
||||||
|
"matches-detected": "Обнаруженные совпадения",
|
||||||
"content-verification-status": "Статус проверки контента",
|
"content-verification-status": "Статус проверки контента",
|
||||||
"violation-data-updated": "Данные нарушений обновлены",
|
"violation-data-updated": "Данные нарушений обновлены",
|
||||||
"open": "Открыть",
|
"open": "Открыть",
|
||||||
@@ -323,7 +326,7 @@
|
|||||||
"general-indicators-of-the-protection-system": "Общие показатели системы защиты",
|
"general-indicators-of-the-protection-system": "Общие показатели системы защиты",
|
||||||
"total-number-of-checks": "Всего проверок",
|
"total-number-of-checks": "Всего проверок",
|
||||||
"protected-content": "Защищенного контента",
|
"protected-content": "Защищенного контента",
|
||||||
"geography-of-violations": "География нарушений",
|
"geography-of-matches": "География совпадений",
|
||||||
"activity-by-month": "Активность по месяцам",
|
"activity-by-month": "Активность по месяцам",
|
||||||
"january": "Январь",
|
"january": "Январь",
|
||||||
"february": "Февраль",
|
"february": "Февраль",
|
||||||
@@ -350,6 +353,7 @@
|
|||||||
"violation-count": "Количество нарушений",
|
"violation-count": "Количество нарушений",
|
||||||
"your-files": "Ваши файлы",
|
"your-files": "Ваши файлы",
|
||||||
"files-under-protection": "Файлов под защитой",
|
"files-under-protection": "Файлов под защитой",
|
||||||
|
"matches": "Совпадения",
|
||||||
"matches-found": "Найдено совпадений",
|
"matches-found": "Найдено совпадений",
|
||||||
"violations-in-progress": "Нарушений в работе",
|
"violations-in-progress": "Нарушений в работе",
|
||||||
"sum-of-pay-is-required": "Введите сумму выплаты",
|
"sum-of-pay-is-required": "Введите сумму выплаты",
|
||||||
@@ -381,15 +385,18 @@
|
|||||||
"violation-id": "ID нарушения",
|
"violation-id": "ID нарушения",
|
||||||
"no-information-about-the-complaint": "Нет информации о жалобе",
|
"no-information-about-the-complaint": "Нет информации о жалобе",
|
||||||
"the-complaint-has-not-been-registered": "Жалоба не зарегистрирована",
|
"the-complaint-has-not-been-registered": "Жалоба не зарегистрирована",
|
||||||
|
"the-claims-has-not-been-registered": "Претензия не зарегистрирована",
|
||||||
"the-complaint-has-been-registered": "Жалоба зарегистрирована",
|
"the-complaint-has-been-registered": "Жалоба зарегистрирована",
|
||||||
|
"the-claims-has-been-registered": "Претензия зарегистрирована",
|
||||||
"all-statuses": "Все статусы",
|
"all-statuses": "Все статусы",
|
||||||
"all": "Все",
|
"all": "Все",
|
||||||
"mark-all-as-read": "Отметить все как прочитанное",
|
"mark-all-as-read": "Отметить все как прочитанные",
|
||||||
"mark-as-read": "Отметить как прочитанное",
|
"mark-as-read": "Отметить как прочитанное",
|
||||||
"all-notifications": "Все уведомления",
|
"all-notifications": "Все уведомления",
|
||||||
"deselect": "Снять выделение",
|
"deselect": "Снять выделение",
|
||||||
"select-all": "Выбрать все",
|
"select-all": "Выбрать все",
|
||||||
"text-area-error-fill-complaint-text": "Заполните текст жалобы",
|
"text-area-error-fill-complaint-text": "Заполните текст жалобы",
|
||||||
|
"text-area-error-complaint-text-too-long": "Текст жалобы не должен превышать 2000 символов",
|
||||||
"to-view-a-file-match-click-on-the-card-from-the-list": "Для просмотра совпадения по файлу нажмите на карточку из списка",
|
"to-view-a-file-match-click-on-the-card-from-the-list": "Для просмотра совпадения по файлу нажмите на карточку из списка",
|
||||||
"insufficient-funds-to-receive-payment": "Недостаточно средств для получения выплаты",
|
"insufficient-funds-to-receive-payment": "Недостаточно средств для получения выплаты",
|
||||||
"not-found-payout-method": "Метод выплаты не найден",
|
"not-found-payout-method": "Метод выплаты не найден",
|
||||||
@@ -407,24 +414,86 @@
|
|||||||
"description": "Описание",
|
"description": "Описание",
|
||||||
"priority": "Приоритет",
|
"priority": "Приоритет",
|
||||||
"type": "Тип",
|
"type": "Тип",
|
||||||
"lawyer": "Адвокат",
|
"lawyer": "Юрист",
|
||||||
"not-assigned": "Не назначено",
|
"not-assigned": "Не назначен",
|
||||||
|
"case": "Претензия",
|
||||||
"create-case": "Создать претензию",
|
"create-case": "Создать претензию",
|
||||||
"case-name": "Название претензии",
|
"case-name": "Название претензии",
|
||||||
"amount-of-damage": "Сумма ущерба",
|
"amount-of-damage": "Сумма ущерба",
|
||||||
"text-of-the-case": "Текст претензии",
|
"text-of-the-case": "Текст претензии",
|
||||||
"fill-text-of-the-case": "Заполните текст претензии",
|
"fill-text-of-the-case": "Заполните текст претензии",
|
||||||
"fill-title-of-the-case": "Заполните название претензии",
|
"fill-title-of-the-case": "Заполните название претензии",
|
||||||
|
"claim-title-must-not-exceed-300-characters": "Название претензии не должно превышать 300 символов",
|
||||||
|
"claim-text-must-not-exceed-2000-characters": "Текст претензии не должен превышать 2000 символов",
|
||||||
"enter-the-amount-of-damage": "Введите сумму ущерба",
|
"enter-the-amount-of-damage": "Введите сумму ущерба",
|
||||||
"download-all": "Скачать все",
|
"download-all": "Скачать все",
|
||||||
"selected-files": "Выбрано файлов",
|
"selected-files": "Выбрано файлов",
|
||||||
"protected-files": "Защищено файлов",
|
"protected-files": "Защищено файлов",
|
||||||
"storage": "Хранилище",
|
"storage": "Хранилище",
|
||||||
"Status": "Статус",
|
"Status": "Статус",
|
||||||
"under-moderation": "На модерации",
|
|
||||||
"full-link": "Полная ссылка",
|
"full-link": "Полная ссылка",
|
||||||
"follow-the-link": "Перейти",
|
"follow-the-link": "Перейти",
|
||||||
"date-of-discovery": "Дата обнаружения"
|
"date-of-discovery": "Дата обнаружения",
|
||||||
|
"file-a-claim": "Подать претензию",
|
||||||
|
"file-a-complaint": "Подать жалобу",
|
||||||
|
"you-can": "Вы можете",
|
||||||
|
"complaint": "Жалоба",
|
||||||
|
"complaint-name": "Название жалобы",
|
||||||
|
"recent-complaints": "Недавние жалобы",
|
||||||
|
"recent-claims": "Недавние претензии",
|
||||||
|
"no-complaints": "Жалоб нет",
|
||||||
|
"no-claims": "Претензии нет",
|
||||||
|
"in": "в",
|
||||||
|
"last-content-check": "Последняя проверка контента",
|
||||||
|
"day": "день",
|
||||||
|
"week_accusative": "неделю",
|
||||||
|
"month": "месяц",
|
||||||
|
"every": "каждый",
|
||||||
|
"country": "Страна",
|
||||||
|
"claims": "Претензии",
|
||||||
|
"complaints": "Жалобы",
|
||||||
|
"recent-cases": "Недавние дела",
|
||||||
|
"select-at-least-one-filter": "Выберите хотя бы один фильтр",
|
||||||
|
"select-document-to-upload": "Выберите документ(ы) для загрузки",
|
||||||
|
"uploading-id-images": "Загрузка изображений удостоверения личности",
|
||||||
|
"tracking": "Отслеживание",
|
||||||
|
"save-template": "Сохранить шаблон",
|
||||||
|
"save": "Сохранить",
|
||||||
|
"template-name": "Название шаблона",
|
||||||
|
"enter-template-name": "Введите название шаблона",
|
||||||
|
"complaint-text-preview": "Предварительный просмотр текста жалобы",
|
||||||
|
"no-text": "Нет текста",
|
||||||
|
"please-enter-complaint-text-first": "Пожалуйста, сначала введите текст жалобы",
|
||||||
|
"please-enter-template-name": "Пожалуйста, введите название шаблона",
|
||||||
|
"template-saved-successfully": "Шаблон успешно сохранен",
|
||||||
|
"no-claims-complaints-found": "Жалобы / претензии не найдены",
|
||||||
|
"no-notifications": "Нет уведомлений",
|
||||||
|
"who-opened": "Кто открыл",
|
||||||
|
"time": "Время",
|
||||||
|
"location": "Местоположение",
|
||||||
|
"history-of-opened": "История открытий",
|
||||||
|
"not-defined": "Не определено",
|
||||||
|
"not-defined-city": "Не определен",
|
||||||
|
"not-defined-country": "Не определена",
|
||||||
|
"total-document-openings": "Всего открытий документов",
|
||||||
|
"total-matches": "Всего совпадений",
|
||||||
|
"total-complaints": "Всего нарушений",
|
||||||
|
"total-claims": "Всего претензий",
|
||||||
|
"city": "Город",
|
||||||
|
"closed": "Закрыта",
|
||||||
|
"blocked": "Заблокирован",
|
||||||
|
"pending-moderation": "На модерации",
|
||||||
|
"file-an-appeal": "Подать апелляцию",
|
||||||
|
"please-enter-appeal-reason": "Пожалуйста, укажите причину апелляции",
|
||||||
|
"appeal-submitted-successfully": "Апелляция успешно отправлена",
|
||||||
|
"appeal-submission-failed": "Не удалось отправить апелляцию",
|
||||||
|
"appeal-reason": "Причина апелляции",
|
||||||
|
"enter-appeal-reason": "Опишите причину вашей апелляции...",
|
||||||
|
"additional-info": "Дополнительная информация",
|
||||||
|
"enter-additional-info": "Любая дополнительная информация, которая может помочь...",
|
||||||
|
"submit-appeal": "Отправить апелляцию",
|
||||||
|
"submitting": "Отправка...",
|
||||||
|
"active-appeal-already-exists-for-this-file": "По данному делу уже подана активная апелляция"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "и",
|
"and": "и",
|
||||||
@@ -534,7 +603,8 @@
|
|||||||
"COMPLAINT_IN_WORK": "Жалоба в работе",
|
"COMPLAINT_IN_WORK": "Жалоба в работе",
|
||||||
"COMPLAINT_AND_LEGAL_IN_WORK": "Дело и претензия в работе",
|
"COMPLAINT_AND_LEGAL_IN_WORK": "Дело и претензия в работе",
|
||||||
"AUTHORIZED_USE": "Разрешенное использование",
|
"AUTHORIZED_USE": "Разрешенное использование",
|
||||||
"CREATED": "Новый"
|
"CREATED": "Новый",
|
||||||
|
"NOT_OWNER_FILE": "Не мой файл"
|
||||||
},
|
},
|
||||||
"Notifications": {
|
"Notifications": {
|
||||||
"notification-search-result": "Результат поиска",
|
"notification-search-result": "Результат поиска",
|
||||||
@@ -554,6 +624,36 @@
|
|||||||
"notification-token-not-found": "Токен не найден",
|
"notification-token-not-found": "Токен не найден",
|
||||||
"notification-file-moderation": "Модерация файла",
|
"notification-file-moderation": "Модерация файла",
|
||||||
"notification-file-added": "Файл добавлен в систему",
|
"notification-file-added": "Файл добавлен в систему",
|
||||||
"notification-search-operation-filed": "Ошибка операции поиска"
|
"notification-search-operation-filed": "Ошибка операции поиска",
|
||||||
}
|
"not-specified": "не указан",
|
||||||
|
"not-specified-date": "не указана",
|
||||||
|
"not-specified-file-name": "не указано",
|
||||||
|
"message-missing": "сообщение отсутствует",
|
||||||
|
"reason-not-specified": "причина не указана",
|
||||||
|
"no-comment": "без комментария",
|
||||||
|
"unknown-user": "неизвестный пользователь",
|
||||||
|
"error-not-described": "ошибка не описана",
|
||||||
|
"not-scheduled": "не запланирован",
|
||||||
|
"notification-without-text": "Уведомление без текста",
|
||||||
|
"user-verified": "Статус: {status}. Дата обновления: {updateDate}",
|
||||||
|
"monitoring-result": "Результат мониторинга: {message}",
|
||||||
|
"complaint-status-changed": "Статус жалобы изменён с {oldStatus} на {newStatus}. Нарушение: {violation}. Файл: {file}",
|
||||||
|
"file-added-to-system": "Статус: {status}. Имя файла: {fileName}",
|
||||||
|
"file-moderation-event": "Модерация файла '{fileName}'. Статус изменён с {oldStatus} на {newStatus}. Комментарий: {comment}",
|
||||||
|
"payment-result": "Результат платежа: {status}. Сумма: {amount}",
|
||||||
|
"referral-registered": "Зарегистрирован реферал: {referralUser}",
|
||||||
|
"search-result": "Результаты поиска с {startDate} по {endDate}. Тип: {searchType}. Найдено файлов: {count}",
|
||||||
|
"search-operation-failed": "Статус: {status}. Сообщение: {message}. Причина: {reason}. Следующий запуск: {nextRun}",
|
||||||
|
"token-not-found": "Текущие: {currentTokens}, необходимо: {needTokens}. Сообщение: {message}. Следующий запуск: {nextRun}"
|
||||||
|
},
|
||||||
|
"User-status": {
|
||||||
|
"under-moderation": "На модерации",
|
||||||
|
"NOT_VERIFIED": "Не верифицирован",
|
||||||
|
"VERIFIED": "Верифицирован",
|
||||||
|
"VERIFICATION_FAILED": "Верификация не удалась",
|
||||||
|
"unknow": "Неизвестно",
|
||||||
|
"user-verification": "Верификация пользователя",
|
||||||
|
"Status": "Статус"
|
||||||
|
},
|
||||||
|
"Countryes": {}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -37,7 +37,7 @@ export default async function proxy(request: NextRequest) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
const cookie = (await cookies()).get('session')?.value;
|
const cookie = (await cookies()).get('session_nc_db')?.value;
|
||||||
const session = await decrypt(cookie);
|
const session = await decrypt(cookie);
|
||||||
|
|
||||||
if (isProtectedRoute && !session?.token) {
|
if (isProtectedRoute && !session?.token) {
|
||||||
|
|||||||
Reference in New Issue
Block a user