Удален файл CSS_FIX_GUIDE.md, который содержал инструкции по исправлению проблем со стилями в Docker. Обновлены зависимости в package-lock.json и package.json для улучшения совместимости с Tailwind CSS и PostCSS. Внесены изменения в конфигурацию PostCSS и удален устаревший файл tailwind.config.js. Обновлены стили в globals.css для улучшения структуры и добавлены новые переменные. Добавлен новый элемент в боковое меню для тестирования стилей.

This commit is contained in:
Bivekich
2025-07-01 22:25:01 +03:00
parent 249a07fc2b
commit 80b699ff4e
13 changed files with 801 additions and 452 deletions

View File

@ -1,127 +0,0 @@
# 🎨 Руководство по исправлению проблем со стилями CMS в Docker
## 🔍 Проблема
Стили Tailwind CSS не загружаются в Docker контейнере, но работают в локальной разработке.
## ✅ Решение
### 1. Исправленные файлы:
#### `src/app/globals.css`
```css
@import "tailwindcss/preflight";
@import "tailwindcss";
```
**Вместо:** `@import "tailwindcss";` и `@import "tw-animate-css";`
#### `next.config.ts`
```typescript
const nextConfig = {
output: 'standalone',
// Исключаем favicon из обработки как страницу
async rewrites() {
return [];
},
// CSS настройки (optimizeCss отключен из-за проблем с critters)
// experimental: {
// optimizeCss: true,
// },
// Настройки для изображений
images: {
unoptimized: true,
domains: ['localhost'],
},
// Настройки webpack для CSS
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
if (!dev && !isServer) {
config.optimization.splitChunks.cacheGroups.styles = {
name: 'styles',
test: /\.(css|scss)$/,
chunks: 'all',
enforce: true,
};
}
return config;
},
};
```
### 2. Используйте оптимизированный Dockerfile:
#### `Dockerfile.optimized`
- Multi-stage build для лучшей оптимизации
- Правильная обработка CSS на этапе сборки
- Проверка создания CSS файлов
### 3. Команды для пересборки:
```bash
# Остановка и полная пересборка
cd protekauto-cms
./rebuild-cms.sh
# Или вручную:
docker-compose down protekauto-cms
docker rmi protekauto-cms_protekauto-cms
docker-compose up --build -d protekauto-cms
```
## 🔧 Диагностика
### Проверка локальной сборки:
```bash
npm run build
ls -la .next/static/css/
```
### Проверка в контейнере:
```bash
docker exec -it protekauto-cms ls -la .next/static/css/
```
## 🚨 Частые проблемы
### 1. **Tailwind CSS v4 синтаксис**
-`@tailwind base;`
-`@import "tailwindcss/preflight";`
### 2. **Экспериментальные функции**
-`optimizeCss: true` (требует critters)
- ✅ Отключено для стабильности
### 3. **Standalone режим**
- ✅ Включен для Docker оптимизации
- Может вызывать проблемы при сборке
### 4. **CSS не загружается в браузере**
- Проверьте Network tab в DevTools
- Убедитесь, что CSS файлы доступны по пути `/_next/static/css/`
## 📋 Чек-лист исправления
- [ ] Обновлен `globals.css` с правильными импортами
- [ ] Обновлен `next.config.ts` с правильными настройками
- [ ] Отключен `optimizeCss` (если есть проблемы с critters)
- [ ] Используется `Dockerfile.optimized`
- [ ] Локальная сборка проходит успешно
- [ ] CSS файлы создаются в `.next/static/css/`
- [ ] Контейнер пересобран с новыми настройками
## 🎯 Результат
После применения исправлений:
1. ✅ Локальная сборка проходит без ошибок
2. ✅ CSS файлы создаются корректно
3. ✅ Стили загружаются в Docker контейнере
4. ✅ Админка отображается с правильными стилями
## 📞 Поддержка
Если проблема не решена:
1. Проверьте логи контейнера: `docker-compose logs protekauto-cms`
2. Убедитесь, что все зависимости установлены
3. Попробуйте полную очистку: `docker system prune -f`

188
package-lock.json generated
View File

@ -28,7 +28,7 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@tailwindcss/postcss": "^4",
"@tailwindcss/postcss": "^4.1.11",
"@types/bcryptjs": "^2.4.6",
"@types/js-cookie": "^3.0.6",
"@types/jsonwebtoken": "^9.0.9",
@ -55,6 +55,7 @@
"lucide-react": "^0.513.0",
"next": "15.3.3",
"pdfkit": "^0.17.1",
"postcss": "^8.5.6",
"prisma": "^6.9.0",
"puppeteer": "^24.10.2",
"qrcode": "^1.5.4",
@ -63,8 +64,8 @@
"react-hook-form": "^7.57.0",
"react-hot-toast": "^2.5.2",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.0",
"tailwindcss": "^4",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.11",
"tailwindcss-animate": "^1.0.7",
"ttf2woff2": "^8.0.0",
"uuid": "^11.1.0",
@ -2271,17 +2272,13 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.8",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
"integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.11.tgz",
"integrity": "sha512-C512c1ytBTio4MrpWKlJpyFHT6+qfFL8SZ58zBzJ1OOzUEjHeF1BtjY2fH7n4x/g2OV/KiiMLAivOp1DXmiMMw==",
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@ -2293,25 +2290,16 @@
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.3.tgz",
"integrity": "sha512-AiR5uKpFxP3PjO4R19kQGIMwxyRyPuXmKEEy301V1C0+1rVjS94EZQXf1QKZYN8Q0YM+estSPhmx5JwNftv6nw==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"version": "0.3.28",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.28.tgz",
"integrity": "sha512-KNNHHwW3EIp4EDYOvYFGyIFfx36R2dNJYH4knnZlF8T5jdbD5Wx8xmSaQ2gP9URkJ04LGEtlcCtwArKcmFcwKw==",
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@ -4426,9 +4414,9 @@
}
},
"node_modules/@tailwindcss/node": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.8.tgz",
"integrity": "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.11.tgz",
"integrity": "sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.3.0",
@ -4437,13 +4425,13 @@
"lightningcss": "1.30.1",
"magic-string": "^0.30.17",
"source-map-js": "^1.2.1",
"tailwindcss": "4.1.8"
"tailwindcss": "4.1.11"
}
},
"node_modules/@tailwindcss/oxide": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.8.tgz",
"integrity": "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.11.tgz",
"integrity": "sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@ -4454,24 +4442,24 @@
"node": ">= 10"
},
"optionalDependencies": {
"@tailwindcss/oxide-android-arm64": "4.1.8",
"@tailwindcss/oxide-darwin-arm64": "4.1.8",
"@tailwindcss/oxide-darwin-x64": "4.1.8",
"@tailwindcss/oxide-freebsd-x64": "4.1.8",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.8",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.8",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.8",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.8",
"@tailwindcss/oxide-linux-x64-musl": "4.1.8",
"@tailwindcss/oxide-wasm32-wasi": "4.1.8",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.8",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.8"
"@tailwindcss/oxide-android-arm64": "4.1.11",
"@tailwindcss/oxide-darwin-arm64": "4.1.11",
"@tailwindcss/oxide-darwin-x64": "4.1.11",
"@tailwindcss/oxide-freebsd-x64": "4.1.11",
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.11",
"@tailwindcss/oxide-linux-arm64-gnu": "4.1.11",
"@tailwindcss/oxide-linux-arm64-musl": "4.1.11",
"@tailwindcss/oxide-linux-x64-gnu": "4.1.11",
"@tailwindcss/oxide-linux-x64-musl": "4.1.11",
"@tailwindcss/oxide-wasm32-wasi": "4.1.11",
"@tailwindcss/oxide-win32-arm64-msvc": "4.1.11",
"@tailwindcss/oxide-win32-x64-msvc": "4.1.11"
}
},
"node_modules/@tailwindcss/oxide-android-arm64": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.8.tgz",
"integrity": "sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.11.tgz",
"integrity": "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==",
"cpu": [
"arm64"
],
@ -4485,9 +4473,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-arm64": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.8.tgz",
"integrity": "sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.11.tgz",
"integrity": "sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==",
"cpu": [
"arm64"
],
@ -4501,9 +4489,9 @@
}
},
"node_modules/@tailwindcss/oxide-darwin-x64": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.8.tgz",
"integrity": "sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.11.tgz",
"integrity": "sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==",
"cpu": [
"x64"
],
@ -4517,9 +4505,9 @@
}
},
"node_modules/@tailwindcss/oxide-freebsd-x64": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.8.tgz",
"integrity": "sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.11.tgz",
"integrity": "sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==",
"cpu": [
"x64"
],
@ -4533,9 +4521,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.8.tgz",
"integrity": "sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.11.tgz",
"integrity": "sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==",
"cpu": [
"arm"
],
@ -4549,9 +4537,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.8.tgz",
"integrity": "sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.11.tgz",
"integrity": "sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==",
"cpu": [
"arm64"
],
@ -4565,9 +4553,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.8.tgz",
"integrity": "sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.11.tgz",
"integrity": "sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==",
"cpu": [
"arm64"
],
@ -4581,9 +4569,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.8.tgz",
"integrity": "sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.11.tgz",
"integrity": "sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==",
"cpu": [
"x64"
],
@ -4597,9 +4585,9 @@
}
},
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.8.tgz",
"integrity": "sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.11.tgz",
"integrity": "sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==",
"cpu": [
"x64"
],
@ -4613,9 +4601,9 @@
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.8.tgz",
"integrity": "sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.11.tgz",
"integrity": "sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==",
"bundleDependencies": [
"@napi-rs/wasm-runtime",
"@emnapi/core",
@ -4633,7 +4621,7 @@
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@emnapi/wasi-threads": "^1.0.2",
"@napi-rs/wasm-runtime": "^0.2.10",
"@napi-rs/wasm-runtime": "^0.2.11",
"@tybys/wasm-util": "^0.9.0",
"tslib": "^2.8.0"
},
@ -4642,9 +4630,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.8.tgz",
"integrity": "sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",
"integrity": "sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==",
"cpu": [
"arm64"
],
@ -4658,9 +4646,9 @@
}
},
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.8.tgz",
"integrity": "sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.11.tgz",
"integrity": "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==",
"cpu": [
"x64"
],
@ -4674,16 +4662,16 @@
}
},
"node_modules/@tailwindcss/postcss": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.8.tgz",
"integrity": "sha512-vB/vlf7rIky+w94aWMw34bWW1ka6g6C3xIOdICKX2GC0VcLtL6fhlLiafF0DVIwa9V6EHz8kbWMkS2s2QvvNlw==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.11.tgz",
"integrity": "sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==",
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"@tailwindcss/node": "4.1.8",
"@tailwindcss/oxide": "4.1.8",
"@tailwindcss/node": "4.1.11",
"@tailwindcss/oxide": "4.1.11",
"postcss": "^8.4.41",
"tailwindcss": "4.1.8"
"tailwindcss": "4.1.11"
}
},
"node_modules/@tootallnate/quickjs-emscripten": {
@ -7044,9 +7032,9 @@
}
},
"node_modules/enhanced-resolve": {
"version": "5.18.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
"integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
"version": "5.18.2",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz",
"integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==",
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@ -10822,9 +10810,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.4",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz",
"integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==",
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
@ -12339,9 +12327,9 @@
}
},
"node_modules/tailwind-merge": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.0.tgz",
"integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz",
"integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==",
"license": "MIT",
"funding": {
"type": "github",
@ -12349,9 +12337,9 @@
}
},
"node_modules/tailwindcss": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.8.tgz",
"integrity": "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og==",
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
"integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
"license": "MIT"
},
"node_modules/tailwindcss-animate": {

View File

@ -38,7 +38,7 @@
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@tailwindcss/postcss": "^4",
"@tailwindcss/postcss": "^4.1.11",
"@types/bcryptjs": "^2.4.6",
"@types/js-cookie": "^3.0.6",
"@types/jsonwebtoken": "^9.0.9",
@ -65,6 +65,7 @@
"lucide-react": "^0.513.0",
"next": "15.3.3",
"pdfkit": "^0.17.1",
"postcss": "^8.5.6",
"prisma": "^6.9.0",
"puppeteer": "^24.10.2",
"qrcode": "^1.5.4",
@ -73,8 +74,8 @@
"react-hook-form": "^7.57.0",
"react-hot-toast": "^2.5.2",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.0",
"tailwindcss": "^4",
"tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.11",
"tailwindcss-animate": "^1.0.7",
"ttf2woff2": "^8.0.0",
"uuid": "^11.1.0",

View File

@ -1,6 +1,7 @@
/** @type {import('postcss-load-config').Config} */
export default {
const config = {
plugins: {
'@tailwindcss/postcss': {},
"@tailwindcss/postcss": {},
},
};
};
export default config;

View File

@ -0,0 +1,69 @@
import { NextRequest, NextResponse } from 'next/server'
import { laximoUnitService } from '@/lib/laximo-service'
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const catalogCode = searchParams.get('catalogCode') || 'KIA202404'
const vehicleId = searchParams.get('vehicleId') || '2095869513'
const unitId = searchParams.get('unitId') || '1842820926'
const ssd = searchParams.get('ssd') || '$*KwGhjK205_fnwvL-5sH4hIPO7Nv15cSn9L68mOiw9rapsNDZ6Oj64LCvtt2w6OmfqrHnxKf35-rV7Oi3-qmwpqWgp6TV-_X67uzD8fvEp_S-vJjosPa2qbDH0p-WiNuip6Wxvrfy6P762NvUpqOgoaf-5vSx_7eusdqlxP6P7KWj07a_sOa18Oaesb634rGot83z8JuhpqTV0d_Hpf7x4aWkt-ntzdXzn6aLp6PEx_DNzLa0m4fTqqaio6ulpf_wpszi5uDTxNDfg4eU1tvR0d3GxsOYjZbU7Mrk4OTVzfPwm6GmpNXR38el_vHhpaTr_f71wOWmkurT8fTgvKO63OWWncbAxdyjoKS4-fXrpqPXpaIAAAAADaPhQw==$'
console.log('🔍 Debug Unit API - Параметры:', { catalogCode, vehicleId, unitId, ssd: ssd ? `${ssd.substring(0, 30)}...` : 'отсутствует' })
try {
const results: any = {
testParams: { catalogCode, vehicleId, unitId, ssdLength: ssd?.length },
timestamp: new Date().toISOString()
}
// 1. Тестируем GetUnitInfo
console.log('🔍 Тестируем GetUnitInfo...')
const unitInfo = await laximoUnitService.getUnitInfo(catalogCode, vehicleId, unitId, ssd)
results.unitInfo = {
success: !!unitInfo,
data: unitInfo,
hasImage: !!(unitInfo?.imageurl || unitInfo?.largeimageurl),
attributesCount: unitInfo?.attributes?.length || 0
}
// 2. Тестируем ListDetailByUnit
console.log('🔍 Тестируем ListDetailByUnit...')
const unitDetails = await laximoUnitService.getUnitDetails(catalogCode, vehicleId, unitId, ssd)
results.unitDetails = {
success: Array.isArray(unitDetails),
detailsCount: unitDetails?.length || 0,
data: unitDetails || [],
sampleDetail: unitDetails?.[0] || null
}
// 3. Тестируем ListImageMapByUnit
console.log('🔍 Тестируем ListImageMapByUnit...')
const imageMap = await laximoUnitService.getUnitImageMap(catalogCode, vehicleId, unitId, ssd)
results.imageMap = {
success: !!imageMap,
coordinatesCount: imageMap?.coordinates?.length || 0,
data: imageMap,
sampleCoordinate: imageMap?.coordinates?.[0] || null
}
// Суммарная статистика
results.summary = {
hasUnitInfo: !!unitInfo,
hasImage: !!(unitInfo?.imageurl || unitInfo?.largeimageurl),
detailsCount: unitDetails?.length || 0,
coordinatesCount: imageMap?.coordinates?.length || 0,
allDataPresent: !!unitInfo && (unitDetails?.length || 0) > 0 && !!imageMap
}
console.log('✅ Debug Unit API результаты:', results.summary)
return NextResponse.json(results)
} catch (error) {
console.error('❌ Ошибка в Debug Unit API:', error)
return NextResponse.json({
error: 'Ошибка тестирования API узлов',
details: error instanceof Error ? error.message : String(error),
testParams: { catalogCode, vehicleId, unitId, ssdLength: ssd?.length }
}, { status: 500 })
}
}

View File

@ -0,0 +1,250 @@
"use client"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
AlertCircle,
CheckCircle,
Info,
ShoppingCart,
Users,
Settings
} from 'lucide-react'
export default function TestStylesPage() {
return (
<div className="p-6 space-y-8">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground mb-2">
Тест стилей Tailwind CSS + Shadcn/ui
</h1>
<p className="text-muted-foreground">
Проверка всех основных компонентов и цветов
</p>
</div>
{/* Color Test */}
<Card>
<CardHeader>
<CardTitle>Цветовая палитра</CardTitle>
<CardDescription>Проверка всех основных цветов</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="p-4 bg-primary text-primary-foreground rounded-lg">
<p className="font-semibold">Primary</p>
<p className="text-sm">Основной цвет</p>
</div>
<div className="p-4 bg-secondary text-secondary-foreground rounded-lg">
<p className="font-semibold">Secondary</p>
<p className="text-sm">Вторичный цвет</p>
</div>
<div className="p-4 bg-accent text-accent-foreground rounded-lg">
<p className="font-semibold">Accent</p>
<p className="text-sm">Акцентный цвет</p>
</div>
<div className="p-4 bg-muted text-muted-foreground rounded-lg">
<p className="font-semibold">Muted</p>
<p className="text-sm">Приглушенный</p>
</div>
</div>
</CardContent>
</Card>
{/* Buttons Test */}
<Card>
<CardHeader>
<CardTitle>Кнопки</CardTitle>
<CardDescription>Различные варианты кнопок</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-wrap gap-4">
<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
<Button variant="destructive">Destructive</Button>
</div>
<div className="flex flex-wrap gap-4">
<Button size="sm">Small</Button>
<Button size="default">Default</Button>
<Button size="lg">Large</Button>
<Button size="icon">
<Settings className="h-4 w-4" />
</Button>
</div>
</CardContent>
</Card>
{/* Badges Test */}
<Card>
<CardHeader>
<CardTitle>Бейджи</CardTitle>
<CardDescription>Различные статусы и метки</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex flex-wrap gap-4">
<Badge variant="default">Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="destructive">Destructive</Badge>
</div>
</CardContent>
</Card>
{/* Form Elements Test */}
<Card>
<CardHeader>
<CardTitle>Элементы формы</CardTitle>
<CardDescription>Поля ввода и лейблы</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" placeholder="email@example.com" />
</div>
<div className="space-y-2">
<Label htmlFor="password">Пароль</Label>
<Input id="password" type="password" placeholder="Введите пароль" />
</div>
</div>
</CardContent>
</Card>
{/* Icons Test */}
<Card>
<CardHeader>
<CardTitle>Иконки</CardTitle>
<CardDescription>Lucide React иконки</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-4">
<div className="flex items-center gap-2 p-2 bg-green-50 text-green-700 rounded-lg">
<CheckCircle className="h-5 w-5" />
<span>Успех</span>
</div>
<div className="flex items-center gap-2 p-2 bg-red-50 text-red-700 rounded-lg">
<AlertCircle className="h-5 w-5" />
<span>Ошибка</span>
</div>
<div className="flex items-center gap-2 p-2 bg-blue-50 text-blue-700 rounded-lg">
<Info className="h-5 w-5" />
<span>Информация</span>
</div>
<div className="flex items-center gap-2 p-2 bg-purple-50 text-purple-700 rounded-lg">
<ShoppingCart className="h-5 w-5" />
<span>Заказы</span>
</div>
<div className="flex items-center gap-2 p-2 bg-orange-50 text-orange-700 rounded-lg">
<Users className="h-5 w-5" />
<span>Клиенты</span>
</div>
</div>
</CardContent>
</Card>
{/* Cards Test */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<ShoppingCart className="h-5 w-5" />
Заказы
</CardTitle>
<CardDescription>Всего заказов</CardDescription>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-blue-600">1,234</div>
<p className="text-xs text-muted-foreground">
+20.1% с прошлого месяца
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Users className="h-5 w-5" />
Клиенты
</CardTitle>
<CardDescription>Активные клиенты</CardDescription>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-green-600">456</div>
<p className="text-xs text-muted-foreground">
+15.3% с прошлого месяца
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<AlertCircle className="h-5 w-5" />
Проблемы
</CardTitle>
<CardDescription>Требуют внимания</CardDescription>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold text-red-600">12</div>
<p className="text-xs text-muted-foreground">
-5.2% с прошлого месяца
</p>
</CardContent>
</Card>
</div>
{/* Dark/Light Mode Test */}
<Card>
<CardHeader>
<CardTitle>Тема</CardTitle>
<CardDescription>Проверка адаптации к темной/светлой теме</CardDescription>
</CardHeader>
<CardContent>
<div className="p-4 border rounded-lg">
<p className="text-foreground mb-2">
Этот текст должен адаптироваться к теме
</p>
<p className="text-muted-foreground">
А этот текст должен быть приглушенным
</p>
</div>
</CardContent>
</Card>
{/* Status Message */}
<Card>
<CardHeader>
<CardTitle className="text-green-600"> Статус интеграции</CardTitle>
<CardDescription>Результат исправления Tailwind CSS</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
<p className="flex items-center gap-2">
<CheckCircle className="h-5 w-5 text-green-600" />
<span>Tailwind CSS v4 правильно интегрирован</span>
</p>
<p className="flex items-center gap-2">
<CheckCircle className="h-5 w-5 text-green-600" />
<span>Shadcn/ui компоненты работают корректно</span>
</p>
<p className="flex items-center gap-2">
<CheckCircle className="h-5 w-5 text-green-600" />
<span>Цветовая схема применяется правильно</span>
</p>
<p className="flex items-center gap-2">
<CheckCircle className="h-5 w-5 text-green-600" />
<span>Поддержка темной/светлой темы активна</span>
</p>
</div>
</CardContent>
</Card>
</div>
)
}

View File

@ -1,50 +1,11 @@
@import "tailwindcss/preflight";
@import "tailwindcss";
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
}
@custom-variant dark (&:is(.dark *));
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
/* Основные цвета в HSL формате */
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
@ -58,6 +19,7 @@
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: 210 40% 98%;
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
@ -74,6 +36,8 @@
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
}
.dark {
@ -92,6 +56,7 @@
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--destructive-foreground: 210 40% 98%;
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
@ -110,6 +75,53 @@
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
@layer base {
* {
@apply border-border outline-ring/50;

View File

@ -12,7 +12,8 @@ import {
Package,
UserCheck,
ShoppingCart,
Receipt
Receipt,
Palette
} from 'lucide-react'
import { Button } from '@/components/ui/button'
import { useAuth } from '@/components/providers/AuthProvider'
@ -62,6 +63,11 @@ const navigationItems = [
href: '/dashboard/settings',
icon: Settings,
},
{
title: 'Тест стилей',
href: '/dashboard/test-styles',
icon: Palette,
},
]
export const Sidebar = ({ className }: SidebarProps) => {

View File

@ -1678,9 +1678,36 @@ export const resolvers = {
laximoVehicleInfo: async (_: unknown, { catalogCode, vehicleId, ssd, localized }: { catalogCode: string; vehicleId: string; ssd?: string; localized: boolean }) => {
try {
return await laximoService.getVehicleInfo(catalogCode, vehicleId, ssd, localized)
console.log('🔍 GraphQL laximoVehicleInfo resolver - входные параметры:', {
catalogCode,
vehicleId,
ssd: ssd ? `${ssd.substring(0, 50)}...` : 'отсутствует',
localized,
ssdLength: ssd?.length
})
const result = await laximoService.getVehicleInfo(catalogCode, vehicleId, ssd, localized)
console.log('📋 GraphQL laximoVehicleInfo resolver - результат:', {
inputVehicleId: vehicleId,
returnedVehicleId: result?.vehicleid,
vehicleName: result?.name,
brand: result?.brand,
catalog: result?.catalog,
hasResult: !!result,
vehicleIdChanged: result?.vehicleid !== vehicleId
})
if (result && result.vehicleid !== vehicleId) {
console.log('🚨 BACKEND: Vehicle ID изменился!')
console.log(`📍 Запрошенный: ${vehicleId}`)
console.log(`📍 Полученный: ${result.vehicleid}`)
console.log(`📍 SSD: ${ssd?.substring(0, 50)}...`)
}
return result
} catch (error) {
console.error('Ошибка получения информации об автомобиле:', error)
console.error('Ошибка получения информации об автомобиле:', error)
return null
}
},

View File

@ -1428,6 +1428,12 @@ export const typeDefs = gql`
description: String
applicablemodels: String
note: String
amount: String
range: String
codeonimage: String
match: Boolean
dateRange: String
ssd: String
attributes: [LaximoDetailAttribute!]
}

View File

@ -138,6 +138,12 @@ export interface LaximoDetail {
description?: string
applicablemodels?: string
note?: string
amount?: string
range?: string
codeonimage?: string
match?: boolean
dateRange?: string
ssd?: string
attributes?: LaximoDetailAttribute[]
}
@ -1079,12 +1085,18 @@ class LaximoService {
if (vehicleId) {
command += `|VehicleId=${vehicleId}`
}
if (ssd && ssd.trim() !== '') {
// 🎯 ИСПРАВЛЕНИЕ: Для категорий каталога НЕ используем SSD
// SSD используется только для QuickGroup'ов, но не для категорий
if (categoryId) {
// Если указана категория, НЕ добавляем SSD - показываем ВСЕ узлы категории
command += `|CategoryId=${categoryId}`
console.log('🔧 Режим "От производителя": используем CategoryId БЕЗ SSD для получения всех узлов категории')
} else if (ssd && ssd.trim() !== '') {
// SSD добавляем только если НЕТ CategoryId (для QuickGroup'ов)
const escapedSsd = this.escapeSsdForXML(ssd)
command += `|ssd=${escapedSsd}`
}
if (categoryId) {
command += `|CategoryId=${categoryId}`
console.log('🔧 Режим "Общие": используем SSD для получения узлов автомобиля')
}
const hmac = this.createHMAC(command)
@ -1452,6 +1464,9 @@ class LaximoService {
const xmlText = await response.text()
console.log('📥 RAW XML ответ длиной:', xmlText.length, 'символов')
console.log('📄 ПОЛНЫЙ XML ОТВЕТ:')
console.log(xmlText)
console.log('📄 ======= КОНЕЦ XML =======')
console.log('📄 Первые 1000 символов XML:')
console.log(xmlText.substring(0, 1000))
@ -1748,20 +1763,50 @@ class LaximoService {
* Парсит ответ информации об автомобиле
*/
private parseVehicleInfoResponse(xmlText: string): LaximoVehicleInfo | null {
console.log('🔍 parseVehicleInfoResponse - начинаем парсинг...')
console.log('📄 XML длина:', xmlText.length)
console.log('📄 XML первые 500 символов:', xmlText.substring(0, 500))
const resultData = this.extractResultData(xmlText)
if (!resultData) return null
if (!resultData) {
console.log('❌ Не удалось извлечь resultData')
return null
}
console.log('📋 resultData первые 500 символов:', resultData.substring(0, 500))
const rowMatch = resultData.match(/<row([^>]*)>([\s\S]*?)<\/row>/)
if (!rowMatch) return null
if (!rowMatch) {
console.log('❌ Не найден тег <row>')
return null
}
console.log('✅ Найден тег <row>')
const attributes = rowMatch[1]
const content = rowMatch[2]
console.log('📋 Атрибуты row:', attributes)
const getAttribute = (name: string): string => {
const match = attributes.match(new RegExp(`${name}="([^"]*)"`, 'i'))
return match ? match[1] : ''
}
const vehicleid = getAttribute('vehicleid')
const name = getAttribute('name')
const ssd = getAttribute('ssd')
const brand = getAttribute('brand')
const catalog = getAttribute('catalog')
console.log('🔍 Извлеченные атрибуты:', {
vehicleid,
name,
brand,
catalog,
ssd: ssd ? `${ssd.substring(0, 50)}...` : 'отсутствует',
ssdLength: ssd?.length
})
// Парсим атрибуты автомобиля
const vehicleAttributes: LaximoVehicleAttribute[] = []
const attributeMatches = content.match(/<attribute[^>]*\/?>|<attribute[^>]*>[\s\S]*?<\/attribute>/g)
@ -2087,7 +2132,7 @@ class LaximoService {
throw new Error('SSD parameter is required for ListQuickDetail')
}
const command = `ListQuickDetail:Locale=ru_RU|Catalog=${catalogCode}|VehicleId=${vehicleId}|QuickGroupId=${quickGroupId}|ssd=${ssd}`
const command = `ListQuickDetail:Locale=ru_RU|Catalog=${catalogCode}|VehicleId=${vehicleId}|QuickGroupId=${quickGroupId}|ssd=${ssd}|Localized=true|All=1`
const hmac = this.createHMAC(command)
console.log('📝 ListQuickDetail Command:', command)
@ -2115,9 +2160,19 @@ class LaximoService {
return null
}
// Ищем секцию ListQuickDetail
const quickDetailMatch = resultData.match(/<ListQuickDetail[^>]*>([\s\S]*?)<\/ListQuickDetail>/) ||
resultData.match(/<response[^>]*>([\s\S]*?)<\/response>/)
// Декодируем HTML entities в XML для правильного парсинга
const decodedXML = resultData
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
// Ищем секцию ListQuickDetail в декодированном XML
const quickDetailMatch = decodedXML.match(/<ListQuickDetail[^>]*>([\s\S]*?)<\/ListQuickDetail>/) ||
decodedXML.match(/<response[^>]*>([\s\S]*?)<\/response>/)
if (!quickDetailMatch) {
console.log('❌ Не найдена секция ListQuickDetail')
@ -2174,30 +2229,116 @@ class LaximoService {
details: []
}
// В каждом узле ищем детали (Detail)
const detailPattern = /<Detail([^>]*?)(?:\s*\/>|>([\s\S]*?)<\/Detail>)/g
let detailMatch
console.log('🔍 Содержимое узла (первые 1000 символов):')
console.log(unitContent.substring(0, 1000))
console.log('🔍 Ищем детали в содержимом узла...')
while ((detailMatch = detailPattern.exec(unitContent)) !== null) {
const detailAttributes = detailMatch[1]
const detailContent = detailMatch[2] || ''
// ВАЖНО: Детали могут быть как внутри Unit content, так и в атрибутах самого Unit
// Сначала ищем в содержимом узла
const detailTagCount = (unitContent.match(/<Detail/g) || []).length
console.log(`🔍 Общее количество тегов <Detail в содержимом: ${detailTagCount}`)
// Также проверяем весь блок Unit на предмет деталей
const fullUnitBlock = unitMatch[0] // Полный блок Unit включая все его содержимое
const fullUnitDetailCount = (fullUnitBlock.match(/<Detail/g) || []).length
console.log(`🔍 Общее количество тегов <Detail в полном блоке Unit: ${fullUnitDetailCount}`)
// Будем искать детали в полном блоке Unit, а не только в unitContent
const searchContent = fullUnitBlock
console.log('🔍 Поиск деталей в полном блоке Unit (первые 500 символов):')
console.log(searchContent.substring(0, 500))
const detailMatches: Array<{attributes: string, content: string, fullMatch: string}> = []
// ИСПРАВЛЕНО: Ищем все теги Detail (самозакрывающиеся и полные) в одном регулярном выражении
const detailPattern = /<Detail([^>]*?)(?:\s*\/>|>([\s\S]*?)<\/Detail>)/g
let match
while ((match = detailPattern.exec(searchContent)) !== null) {
detailMatches.push({
attributes: match[1] || '',
content: match[2] || '',
fullMatch: match[0]
})
}
console.log(`🔍 Найдено ${detailMatches.length} элементов Detail в узле`)
let detailCount = 0
for (const detailMatch of detailMatches) {
detailCount++
const detailAttributes = detailMatch.attributes || ''
const detailContent = detailMatch.content || ''
console.log(`🔍 Raw detail match #${detailCount}:`)
console.log('Attributes:', detailAttributes.substring(0, 200))
if (detailContent) {
console.log('Content:', detailContent.substring(0, 200))
}
const detailId = this.extractAttribute(detailAttributes, 'detailid')
const detailName = this.extractAttribute(detailAttributes, 'name')
const oem = this.extractAttribute(detailAttributes, 'oem')
const brand = this.extractAttribute(detailAttributes, 'brand')
const codeonimage = this.extractAttribute(detailAttributes, 'codeonimage')
console.log('🔩 Найдена деталь:', { detailId, detailName, oem, brand })
console.log(`🔩 Найдена деталь #${detailCount}: ${detailName} (${oem})`)
const detail: LaximoDetail = {
detailid: detailId,
detailid: detailId || codeonimage || oem,
name: detailName,
oem: oem,
brand: brand,
attributes: []
}
// Парсим атрибуты детали
// Парсим атрибуты детали из тега Detail
const amount = this.extractAttribute(detailAttributes, 'amount')
const dateRange = this.extractAttribute(detailAttributes, 'date_range')
const matchAttr = this.extractAttribute(detailAttributes, 'match')
const range = this.extractAttribute(detailAttributes, 'range')
const ssdAttr = this.extractAttribute(detailAttributes, 'ssd')
// Добавляем все найденные атрибуты в деталь
if (amount) {
detail.amount = amount
detail.attributes?.push({
key: 'amount',
name: 'Количество',
value: amount
})
}
if (dateRange) {
detail.dateRange = dateRange
detail.attributes?.push({
key: 'date_range',
name: 'date_range',
value: dateRange
})
}
if (matchAttr) {
detail.match = matchAttr === 't' || matchAttr === 'true'
}
if (range) {
detail.range = range
detail.attributes?.push({
key: 'range',
name: 'Диапазон',
value: range
})
}
if (ssdAttr) {
detail.ssd = ssdAttr
}
if (codeonimage) {
detail.codeonimage = codeonimage
}
// Парсим дополнительные атрибуты из содержимого детали
const attributePattern = /<attribute([^>]*?)(?:\s*\/>)/g
let attrMatch
@ -2224,6 +2365,60 @@ class LaximoService {
unit.details!.push(detail)
}
console.log(`📊 Найдено деталей в узле "${unitName}": ${detailCount}`)
// Если детали не найдены, попробуем альтернативный подход
if (detailCount === 0) {
console.log('🔍 Детали не найдены стандартным способом, пробуем альтернативный парсинг...')
// Поиск деталей в исходном XML блоке (до декодирования)
const originalUnitBlock = unitMatch[0]
console.log('🔍 Исходный XML блок (первые 1000 символов):')
console.log(originalUnitBlock.substring(0, 1000))
// Ищем детали в исходном формате
const originalDetailPattern = /<Detail[^>]*(?:\s*\/>|>[\s\S]*?<\/Detail>)/g
let originalMatch
let altDetailCount = 0
while ((originalMatch = originalDetailPattern.exec(originalUnitBlock)) !== null) {
altDetailCount++
const detailTag = originalMatch[0]
console.log(`🔍 Альтернативный парсинг - найдена деталь #${altDetailCount}:`)
console.log(detailTag.substring(0, 300))
// Простой парсинг атрибутов из тега
const nameMatch = detailTag.match(/name="([^"]*)"/)
const oemMatch = detailTag.match(/oem="([^"]*)"/)
const codeMatch = detailTag.match(/codeonimage="([^"]*)"/)
const amountMatch = detailTag.match(/amount="([^"]*)"/)
if (nameMatch || oemMatch) {
const altDetail: LaximoDetail = {
detailid: codeMatch?.[1] || oemMatch?.[1] || `alt_${altDetailCount}`,
name: nameMatch?.[1] || 'Неизвестная деталь',
oem: oemMatch?.[1] || '',
attributes: []
}
if (amountMatch) {
altDetail.attributes?.push({
key: 'amount',
name: 'Количество',
value: amountMatch[1]
})
}
unit.details!.push(altDetail)
console.log(`🔩 Альтернативный парсинг - добавлена деталь: ${altDetail.name} (${altDetail.oem})`)
}
}
if (altDetailCount > 0) {
console.log(`✅ Альтернативный парсинг нашел ${altDetailCount} деталей`)
}
}
quickDetail.units!.push(unit)
}
}
@ -2232,7 +2427,7 @@ class LaximoService {
if (quickDetail.units!.length === 0) {
console.log('🔄 Пробуем альтернативный формат парсинга...')
// Ищем узлы напрямую
// Ищем узлы напрямую в декодированном XML
const directUnitPattern = /<row([^>]*?)(?:\s*\/>|>([\s\S]*?)<\/row>)/g
let directUnitMatch
@ -2261,6 +2456,31 @@ class LaximoService {
console.log(`✅ Обработано ${quickDetail.units!.length} узлов в группе ${quickGroupId}`)
// Подсчитываем общее количество деталей
const totalDetails = quickDetail.units!.reduce((total, unit) => total + (unit.details?.length || 0), 0)
console.log(`📊 Общее количество деталей: ${totalDetails}`)
// Выводим детальную информацию о каждом узле
quickDetail.units!.forEach((unit, index) => {
console.log(`📦 Узел #${index + 1}: ${unit.name} (${unit.details?.length || 0} деталей)`)
unit.details?.forEach((detail, detailIndex) => {
const amountAttr = detail.attributes?.find(attr => attr.key === 'amount')
const amountStr = amountAttr ? ` - ${amountAttr.value}` : ''
console.log(` 🔩 Деталь #${detailIndex + 1}: ${detail.name} (${detail.oem})${amountStr}`)
})
})
// Дополнительная диагностика
if (totalDetails === 0) {
console.log('⚠️ НЕ НАЙДЕНО НИ ОДНОЙ ДЕТАЛИ!')
console.log('🔍 Оригинальные данные (первые 1000 символов):')
console.log(resultData?.substring(0, 1000))
console.log('🔍 Декодированные данные (первые 1000 символов):')
console.log(decodedXML.substring(0, 1000))
} else {
console.log(`✅ Успешно найдено ${totalDetails} деталей`)
}
if (quickDetail.units!.length === 0) {
return null
}
@ -2479,52 +2699,58 @@ class LaximoService {
details: []
}
// В каждом узле ищем детали (Detail)
const detailPattern = /<Detail([^>]*?)(?:\s*\/>|>([\s\S]*?)<\/Detail>)/g
// В каждом узле ищем детали (Detail) - поддерживаем как самозакрывающиеся, так и полные теги
// Используем более простой подход: сначала найдем все вхождения тега Detail
console.log('🔍 Поиск деталей в полном блоке Unit (первые 500 символов):')
console.log(unitMatch[0].substring(0, 500))
// Ищем все теги Detail внутри текущего Unit
const detailPattern = /<Detail[^>]*(?:\s*\/>|>[^<]*<\/Detail>)/g
let detailMatch
let detailCount = 0
while ((detailMatch = detailPattern.exec(unitContent)) !== null) {
// Получаем содержимое Unit для поиска деталей
const unitContentForDetails = unitMatch[2] || ''
// Ищем все детали в содержимом Unit
const allDetailMatches = [...unitContentForDetails.matchAll(/<Detail([^>]*?)(?:\s*\/>)/g)]
console.log(`🔍 Найдено ${allDetailMatches.length} самозакрывающихся тегов Detail в Unit`)
for (const detailMatch of allDetailMatches) {
const detailAttributes = detailMatch[1]
const detailContent = detailMatch[2] || ''
console.log(`🔍 Raw detail match #${detailCount + 1}:`)
console.log('Attributes:', detailAttributes.substring(0, 150))
const detailId = this.extractAttribute(detailAttributes, 'detailid')
const detailName = this.extractAttribute(detailAttributes, 'name')
// Извлекаем основные атрибуты детали
const name = this.extractAttribute(detailAttributes, 'name')
const oem = this.extractAttribute(detailAttributes, 'oem')
const brand = this.extractAttribute(detailAttributes, 'brand')
const amount = this.extractAttribute(detailAttributes, 'amount')
const range = this.extractAttribute(detailAttributes, 'range')
const codeonimage = this.extractAttribute(detailAttributes, 'codeonimage')
const match = this.extractAttribute(detailAttributes, 'match')
const dateRange = this.extractAttribute(detailAttributes, 'date_range')
const ssd = this.extractAttribute(detailAttributes, 'ssd')
console.log('🔩 Найдена деталь:', { detailId, detailName, oem, brand })
const detail: LaximoOEMDetail = {
detailid: detailId,
name: detailName,
oem: oem,
brand: brand,
amount: amount,
range: range,
if (name && oem) {
const detail: LaximoDetail = {
detailid: `${unitId}_${detailCount}`,
name,
oem,
brand: '',
amount: amount || '1pcs',
range: dateRange || '',
codeonimage: codeonimage || '',
match: match === 't',
dateRange: dateRange || '',
ssd: ssd || '',
applicablemodels: '',
note: '',
attributes: []
}
// Парсим атрибуты детали
const attributePattern = /<attribute([^>]*?)(?:\s*\/>)/g
let attrMatch
while ((attrMatch = attributePattern.exec(detailContent)) !== null) {
const attrAttributes = attrMatch[1]
const key = this.extractAttribute(attrAttributes, 'key')
const name = this.extractAttribute(attrAttributes, 'name')
const value = this.extractAttribute(attrAttributes, 'value')
detail.attributes?.push({
key,
name: name || key,
value
})
console.log(`🔩 Найдена деталь #${detailCount + 1}: ${detail.name} (${detail.oem})`)
unit.details!.push(detail)
detailCount++
}
unit.details.push(detail)
}
category.units.push(unit)
@ -3026,7 +3252,7 @@ export class LaximoUnitService extends LaximoService {
console.log('📋 Параметры:', { catalogCode, vehicleId, unitId, ssd: ssd ? `${ssd.substring(0, 30)}...` : 'отсутствует' })
// Используем GetUnitInfo согласно документации Laximo
let command = `GetUnitInfo:Locale=ru_RU|Catalog=${catalogCode}|UnitId=${unitId}`
let command = `GetUnitInfo:Locale=ru_RU|Catalog=${catalogCode}|VehicleId=${vehicleId}|UnitId=${unitId}`
if (ssd && ssd.trim() !== '') {
command += `|ssd=${ssd}`
@ -3061,7 +3287,7 @@ export class LaximoUnitService extends LaximoService {
console.log('📋 Параметры:', { catalogCode, vehicleId, unitId, ssd: ssd ? `${ssd.substring(0, 30)}...` : 'отсутствует' })
// Используем ListDetailByUnit согласно документации Laximo
let command = `ListDetailByUnit:Locale=ru_RU|Catalog=${catalogCode}|UnitId=${unitId}`
let command = `ListDetailByUnit:Locale=ru_RU|Catalog=${catalogCode}|VehicleId=${vehicleId}|UnitId=${unitId}`
if (ssd && ssd.trim() !== '') {
command += `|ssd=${ssd}`
@ -3099,7 +3325,7 @@ export class LaximoUnitService extends LaximoService {
console.log('📋 Параметры:', { catalogCode, vehicleId, unitId, ssd: ssd ? `${ssd.substring(0, 30)}...` : 'отсутствует' })
// Используем ListImageMapByUnit согласно документации Laximo
let command = `ListImageMapByUnit:Catalog=${catalogCode}|UnitId=${unitId}`
let command = `ListImageMapByUnit:Catalog=${catalogCode}|VehicleId=${vehicleId}|UnitId=${unitId}`
if (ssd && ssd.trim() !== '') {
command += `|ssd=${ssd}`
@ -3107,6 +3333,9 @@ export class LaximoUnitService extends LaximoService {
command += `|ssd=`
}
// Добавляем WithLinks=true согласно документации
command += `|WithLinks=true`
const hmac = this.createHMAC(command)
console.log('📝 ListImageMapByUnit Command:', command)

View File

@ -4,59 +4,3 @@ import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// Утилиты для работы с датами
export const formatDate = (dateString: string | Date, options?: Intl.DateTimeFormatOptions): string => {
try {
const date = typeof dateString === 'string' ? new Date(dateString) : dateString
if (isNaN(date.getTime())) {
return 'Неизвестно'
}
const defaultOptions: Intl.DateTimeFormatOptions = {
year: 'numeric',
month: 'long',
day: 'numeric'
}
return date.toLocaleDateString('ru-RU', options || defaultOptions)
} catch {
return 'Неизвестно'
}
}
export const formatDateTime = (dateString: string | Date): string => {
return formatDate(dateString, {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
})
}
export const formatRelativeTime = (dateString: string | Date): string => {
try {
const date = typeof dateString === 'string' ? new Date(dateString) : dateString
const now = new Date()
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)
if (diffInSeconds < 60) {
return 'только что'
} else if (diffInSeconds < 3600) {
const minutes = Math.floor(diffInSeconds / 60)
return `${minutes} мин. назад`
} else if (diffInSeconds < 86400) {
const hours = Math.floor(diffInSeconds / 3600)
return `${hours} ч. назад`
} else if (diffInSeconds < 2592000) {
const days = Math.floor(diffInSeconds / 86400)
return `${days} дн. назад`
} else {
return formatDate(date)
}
} catch {
return 'Неизвестно'
}
}

View File

@ -1,57 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
fontFamily: {
sans: ["var(--font-geist-sans)", "sans-serif"],
mono: ["var(--font-geist-mono)", "monospace"],
},
},
},
plugins: [],
}