Initial commit: Merakomis portal, Docker stack and user-reader API.
Co-authored-by: Cursor <cursoragent@cursor.com>
19
.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
# Локальные данные Docker (не коммитить)
|
||||
data/db/
|
||||
data/backup/
|
||||
data/dumps/
|
||||
data/mysql-init/*.sql
|
||||
|
||||
# Полные дампы и крупные архивы (восстановить локально / из scripts)
|
||||
/date-db-mysql_*/
|
||||
date-db-mysql_*.sql.zip
|
||||
www.zip
|
||||
meraproject-test.zip
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
|
||||
# Локальные IDE (не обязательны в репозитории)
|
||||
.cursor/
|
||||
226
.htaccess
Normal file
@ -0,0 +1,226 @@
|
||||
<IfModule mod_headers.c>
|
||||
Header append Vary Accept env=REDIRECT_accept
|
||||
</IfModule>
|
||||
<IfModule mod_mime.c>
|
||||
AddType image/webp .webp
|
||||
</IfModule>
|
||||
|
||||
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP_ACCEPT} image/webp
|
||||
RewriteCond %{REQUEST_URI} (.*)(\.(jpe?g|png))$
|
||||
RewriteCond %{DOCUMENT_ROOT}/%1\_webp.webp -f
|
||||
RewriteRule .* %1_webp\.webp [L,T=image/webp]
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_URI} (.*)(\.(jpg))$
|
||||
RewriteCond %{DOCUMENT_ROOT}/%1_jpg\.jpg -f
|
||||
RewriteRule .* %1_jpg\.jpg [L,T=image/jpg]
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_URI} (.*)(\.(png))$
|
||||
RewriteCond %{DOCUMENT_ROOT}/%1_png\.png -f
|
||||
RewriteRule .* %1_png\.png [L,T=image/png]
|
||||
</IfModule>
|
||||
|
||||
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
RewriteEngine on
|
||||
Options +FollowSymlinks
|
||||
|
||||
RewriteRule ^(.*).md index.php [L]
|
||||
|
||||
RewriteRule ^config.php$ index.php [L]
|
||||
RewriteRule ^robots.txt$ index.php [L]
|
||||
RewriteRule ^sitemap.xml$ index.php [L]
|
||||
RewriteRule ^yml.xml$ index.php [L]
|
||||
|
||||
RewriteCond %{HTTP_HOST} ^www\.(.*)$
|
||||
RewriteRule ^(.*)$ https://%1/$1 [L,R=301]
|
||||
|
||||
|
||||
#RewriteCond %{REQUEST_FILENAME} !-f
|
||||
#RewriteCond %{REQUEST_URI} !(.*)/$
|
||||
#RewriteCond %{REQUEST_FILENAME} !-f
|
||||
#RewriteRule ^(.+[^/])$ /$1/ [R=301,L]
|
||||
|
||||
#RewriteRule ^api/?([^/]+)?/?([^/]+)?/$ module/api.php?module=$1&action=$2&%{QUERY_STRING} [L]
|
||||
RewriteRule ^api/([^/]+)?/?([^/]+)?/$ index.php?api=1&module=$1&action=$2&%{QUERY_STRING} [L]
|
||||
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^.*$ index.php?%{QUERY_STRING} [L]
|
||||
|
||||
</ifModule>
|
||||
|
||||
AddType application/x-javascript .js
|
||||
|
||||
#AddType application/octet-stream .docx .mov .pdf .xlsx
|
||||
|
||||
AddType application/vnd.ms-word.document.macroEnabled.12 docm
|
||||
AddType application/vnd.openxmlformats-officedocument.wordprocessingml.document docx
|
||||
AddType application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx
|
||||
AddType application/vnd.ms-powerpoint.template.macroEnabled.12 potm
|
||||
AddType application/vnd.openxmlformats-officedocument.presentationml.template potx
|
||||
AddType application/vnd.ms-powerpoint.addin.macroEnabled.12 ppam
|
||||
AddType application/vnd.ms-powerpoint.slideshow.macroEnabled.12 ppsm
|
||||
AddType application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx
|
||||
AddType application/vnd.ms-powerpoint.presentation.macroEnabled.12 pptm
|
||||
AddType application/vnd.openxmlformats-officedocument.presentationml.presentation pptx
|
||||
AddType application/vnd.ms-excel.addin.macroEnabled.12 xlam
|
||||
AddType application/vnd.ms-excel.sheet.binary.macroEnabled.12 xlsb
|
||||
AddType application/vnd.ms-excel.sheet.macroEnabled.12 xlsm
|
||||
AddType application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx
|
||||
AddType application/vnd.ms-excel.template.macroEnabled.12 xltm
|
||||
AddType application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#--------------------
|
||||
|
||||
<IfModule mod_deflate.c>
|
||||
|
||||
|
||||
# force deflate for mangled headers
|
||||
# developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/
|
||||
<IfModule mod_setenvif.c>
|
||||
<IfModule mod_headers.c>
|
||||
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
|
||||
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
|
||||
<IfModule !mod_filter.c>
|
||||
# Legacy versions of Apache
|
||||
AddOutputFilterByType DEFLATE text/html text/plain text/css application/json
|
||||
AddOutputFilterByType DEFLATE application/javascript
|
||||
AddOutputFilterByType DEFLATE text/xml application/xml text/x-component
|
||||
AddOutputFilterByType DEFLATE application/xhtml+xml application/rss+xml application/atom+xml
|
||||
AddOutputFilterByType DEFLATE image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
# FileETag None is not enough for every server.
|
||||
<IfModule mod_headers.c>
|
||||
Header unset ETag
|
||||
</IfModule>
|
||||
|
||||
|
||||
# Since we`re sending far-future expires, we don't need ETags for static content.
|
||||
# developer.yahoo.com/performance/rules.html#etags
|
||||
FileETag None
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Кроссдоменные изображения (CORS-enabled images) (@crossorigin)
|
||||
# ----------------------------------------------------------------------
|
||||
# Отсылать CORS заголовки, если браузер требует их, для изображений по умолчанию включено.
|
||||
# http://developer.mozilla.org/en/CORS_Enabled_Image
|
||||
# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
|
||||
# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
|
||||
# wiki.mozilla.org/Security/Reviews/crossoriginAttribute
|
||||
|
||||
<IfModule mod_setenvif.c>
|
||||
<IfModule mod_headers.c>
|
||||
# mod_headers, ну почему ты не определяешь Content-Type?!
|
||||
<FilesMatch "\.(gif|png|jpe?g|svg|svgz|ico|webp)$">
|
||||
SetEnvIf Origin ":" IS_CORS
|
||||
Header set Access-Control-Allow-Origin "*" env=IS_CORS
|
||||
</FilesMatch>
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Задаем Expires заголовки (срок актуальности файла) (для лучшего кэширования)
|
||||
# ----------------------------------------------------------------------
|
||||
# Указываются заголовки с большим сроком "годности",
|
||||
# предполагается, что вы используете кэш на основе названий файлов (all.css?v001).
|
||||
# В дополнение, учтите, что старые прокси могут кэшировать не верно
|
||||
# http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
|
||||
|
||||
# Если вы не используете названия файлов для контроля версий, измените кэш для CSS и JS, например на
|
||||
# "access plus 1 week".
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive on
|
||||
|
||||
# Задаем значение по умолчанию (для всех файлов)
|
||||
ExpiresDefault "access plus 1 month"
|
||||
|
||||
# cache.appcache нельзя кэшировать в FF 3.6 (спасибо Remy ~Introducing HTML5)
|
||||
ExpiresByType text/cache-manifest "access plus 0 seconds"
|
||||
|
||||
# Ваш html документ
|
||||
<FilesMatch \.(html|xhtml|xml|shtml|phtml|php|txt)$>
|
||||
ExpiresDefault "access plus 0 seconds"
|
||||
</FilesMatch>
|
||||
ExpiresByType text/html "access plus 0 seconds"
|
||||
|
||||
# Данные
|
||||
ExpiresByType text/xml "access plus 0 seconds"
|
||||
ExpiresByType application/xml "access plus 0 seconds"
|
||||
ExpiresByType application/json "access plus 0 seconds"
|
||||
|
||||
# Рассылка
|
||||
ExpiresByType application/rss+xml "access plus 1 hour"
|
||||
ExpiresByType application/atom+xml "access plus 1 hour"
|
||||
|
||||
# Favicon (не может быть переименован)
|
||||
<FilesMatch \.(ico)$>
|
||||
ExpiresDefault "access plus 1 week"
|
||||
</FilesMatch>
|
||||
ExpiresByType image/x-icon "access plus 1 week"
|
||||
|
||||
# Медиа: изображения, видео, аудио
|
||||
<FilesMatch \.(gif|png|jpg|jpeg|ogg|mp4|mkv|flv|swf|wmv|asf|asx|wma|wax|wmx|wm)$>
|
||||
ExpiresDefault "access plus 1 year"
|
||||
</FilesMatch>
|
||||
ExpiresByType image/gif "access plus 1 month"
|
||||
ExpiresByType image/png "access plus 1 month"
|
||||
ExpiresByType image/jpeg "access plus 1 month"
|
||||
ExpiresByType video/ogg "access plus 1 month"
|
||||
ExpiresByType audio/ogg "access plus 1 month"
|
||||
ExpiresByType video/mp4 "access plus 1 month"
|
||||
ExpiresByType video/webm "access plus 1 month"
|
||||
|
||||
# HTC файлы (css3pie)
|
||||
ExpiresByType text/x-component "access plus 1 month"
|
||||
|
||||
# Веб-шрифты
|
||||
<FilesMatch \.(eot|ttf|otf|svg|woff)$>
|
||||
ExpiresDefault "access plus 1 year"
|
||||
</FilesMatch>
|
||||
ExpiresByType application/x-font-ttf "access plus 1 month"
|
||||
ExpiresByType font/opentype "access plus 1 month"
|
||||
ExpiresByType application/x-font-woff "access plus 1 month"
|
||||
ExpiresByType image/svg+xml "access plus 1 month"
|
||||
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
|
||||
|
||||
# CSS и JavaScript
|
||||
<FilesMatch \.(css|js)$>
|
||||
ExpiresDefault "access plus 1 year"
|
||||
</FilesMatch>
|
||||
ExpiresByType text/css "access plus 1 year"
|
||||
ExpiresByType application/javascript "access plus 1 year"
|
||||
|
||||
# Статичные ресурсы
|
||||
<FilesMatch \.(swf|pdf|doc|rtf|xls|ppt)$>
|
||||
ExpiresDefault "access plus 1 year"
|
||||
</FilesMatch>
|
||||
ExpiresByType application/x-shockwave-flash "access plus 1 year"
|
||||
ExpiresByType application/pdf "access plus 1 year"
|
||||
ExpiresByType application/msword "access plus 1 year"
|
||||
ExpiresByType application/rtf "access plus 1 year"
|
||||
ExpiresByType application/vnd.ms-excel "access plus 1 year"
|
||||
ExpiresByType application/vnd.ms-powerpoint "access plus 1 year"
|
||||
</IfModule>
|
||||
816
DEVELOPERS.md
Normal file
@ -0,0 +1,816 @@
|
||||
# User Reader — документация для разработчиков
|
||||
|
||||
HTTP-сервис на **FastAPI**, читает данные Merakomis из **MySQL** и отдаёт JSON. Предназначен для потребления **другими микросервисами и внешними приложениями**: синхронизация каталога сотрудников, справочники проектов, сводки трудозатрат, запись табеля.
|
||||
|
||||
Интерактивная схема: **`/docs`** (Swagger UI), **`/redoc`**.
|
||||
|
||||
**Справочник только по API, доступу и работе с эндпоинтами:** [docs/user-reader-api.md](docs/user-reader-api.md).
|
||||
|
||||
---
|
||||
|
||||
## Содержание
|
||||
|
||||
1. [Быстрый старт для интеграции](#быстрый-старт-для-интеграции)
|
||||
2. [Сеть и развёртывание](#сеть-и-развёртывание)
|
||||
3. [Аутентификация](#аутентификация)
|
||||
4. [Общие соглашения API](#общие-соглашения-api)
|
||||
5. [Модель данных Merakomis](#модель-данных-merakomis)
|
||||
6. [Справочник эндпоинтов](#справочник-эндпоинтов)
|
||||
7. [Сотрудники](#сотрудники)
|
||||
8. [Трудозатраты (read)](#трудозатраты-read)
|
||||
9. [Табель (read / write)](#табель-read--write)
|
||||
10. [Сценарии интеграции](#сценарии-интеграции)
|
||||
11. [Коды ошибок](#коды-ошибок)
|
||||
12. [Примеры запросов](#примеры-запросов)
|
||||
13. [Запуск и сборка](#запуск-и-сборка)
|
||||
|
||||
---
|
||||
|
||||
## Быстрый старт для интеграции
|
||||
|
||||
Минимальный цикл подключения другого сервиса:
|
||||
|
||||
1. **Проверить доступность** — `GET /api/health` (без ключа).
|
||||
2. **Узнать схему сотрудников** — `GET /api/meta` с API-ключом.
|
||||
3. **Синхронизировать сотрудников** — `GET /api/employees` или инкремент `GET /api/employees/delta`.
|
||||
4. **Получить сводку часов за период** — `GET /api/work-report?date_from=…&date_to=…`.
|
||||
5. **(Опционально) Записать часы** — `PUT /api/time-entries` с заголовками `X-Api-Key` и `X-Acting-Emp-Id`.
|
||||
|
||||
```text
|
||||
Ваш сервис user-reader (:8090) MySQL
|
||||
| X-Api-Key: … | |
|
||||
| GET /api/work-report -------->| SELECT tMerakomisTime … |
|
||||
|<-------- JSON items -----------| |
|
||||
```
|
||||
|
||||
**Рекомендуемые эндпоинты для интеграции:**
|
||||
|
||||
| Задача | Эндпоинт |
|
||||
|--------|----------|
|
||||
| Каталог сотрудников | `/api/employees`, `/api/employees/delta` |
|
||||
| Сводка часов (основной контракт) | `/api/work-report` |
|
||||
| Сводка по проектам | `/api/project-report` |
|
||||
| Участники проектов | `/api/project-members` |
|
||||
| Запись в состав проекта | `PUT /api/project-members` |
|
||||
| Разделы проекта (для формы) | `/api/project-sections` |
|
||||
| Запись часов | `PUT /api/time-entries` |
|
||||
|
||||
---
|
||||
|
||||
## Сеть и развёртывание
|
||||
|
||||
### Docker Compose (этот репозиторий)
|
||||
|
||||
| Сервис | Контейнер | Порт на хосте | Внутри сети compose |
|
||||
|--------|-----------|---------------|---------------------|
|
||||
| user-reader | `meraproject-user-reader` | **8090** | `http://user-reader:8090` |
|
||||
| MySQL | `meraproject-mysqldb` | 3307 | `db:3306` |
|
||||
|
||||
Переменные окружения user-reader заданы в `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
MYSQL_HOST: db
|
||||
MYSQL_DATABASE: j7508239_tracker
|
||||
USER_READER_API_KEY: "local-dev-key-change-in-prod" # сменить на проде
|
||||
```
|
||||
|
||||
**С другого контейнера в той же compose-сети** обращайтесь к `http://user-reader:8090`.
|
||||
|
||||
**С хоста или другого сервера** — `http://<IP_хоста>:8090`.
|
||||
|
||||
**CORS:** включён для всех origin (`*`). Браузерные клиенты с другого домена могут вызывать API при наличии ключа.
|
||||
|
||||
### uvicorn локально
|
||||
|
||||
```bash
|
||||
cd services/user-reader
|
||||
pip install -r requirements.txt
|
||||
# задать MYSQL_* и USER_READER_API_KEY
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8090
|
||||
```
|
||||
|
||||
### Статические страницы-превью (без отдельного фронтенда)
|
||||
|
||||
| URL | Назначение |
|
||||
|-----|------------|
|
||||
| `GET /` | Сотрудники |
|
||||
| `GET /labor` | Проекты, разделы, сырые записи |
|
||||
| `GET /summary` | UI над `/api/work-report` |
|
||||
| `GET /project-report` | UI над `/api/project-report` |
|
||||
| `GET /time-entry` | Форма записи часов (`PUT /api/time-entries`) |
|
||||
| `GET /project-member` | Форма записи состава (`PUT /api/project-members`) |
|
||||
|
||||
---
|
||||
|
||||
## Аутентификация
|
||||
|
||||
Два независимых уровня.
|
||||
|
||||
### 1. Service-to-service: API-ключ
|
||||
|
||||
Если задан непустой **`USER_READER_API_KEY`**, все эндпоинты с пометкой «защищённый» требуют:
|
||||
|
||||
- заголовок **`X-Api-Key: <ключ>`**, или
|
||||
- заголовок **`Authorization: Bearer <ключ>`**
|
||||
|
||||
Если ключ **не задан** (пустая строка) — проверки нет (только для локальной отладки).
|
||||
|
||||
Ответ при ошибке: **`401`**, тело FastAPI `{"detail": "…"}`.
|
||||
|
||||
### 2. Табель: идентичность сотрудника
|
||||
|
||||
Эндпоинты записи и чтения табеля (кроме справочников) дополнительно требуют:
|
||||
|
||||
| Заголовок / поле | Назначение |
|
||||
|------------------|------------|
|
||||
| **`X-Acting-Emp-Id`** | Кто действует (аналог PHP-сессии `fg_emp_id`) |
|
||||
| **`emp_id`** в query/body | За кого операция; если опущен → равен `acting` |
|
||||
|
||||
Права проверяются по правилам Merakomis (админ, РП, делегат и т.д.) — см. `GET /api/labor/permissions`.
|
||||
|
||||
---
|
||||
|
||||
## Общие соглашения API
|
||||
|
||||
### Формат ответов
|
||||
|
||||
- Кодировка: **UTF-8**, тело — **JSON**.
|
||||
- Даты в query: **`YYYY-MM-DD`**.
|
||||
- Пагинация: `limit` + `offset`; в ответе обычно есть `total`, `count`.
|
||||
- Числа с плавающей точкой (часы): до 2 знаков в агрегатах.
|
||||
|
||||
### Сериализация значений из MySQL
|
||||
|
||||
| Тип MySQL | JSON |
|
||||
|-----------|------|
|
||||
| `datetime` | ISO-строка с пробелом и секундами |
|
||||
| `date` | `YYYY-MM-DD` |
|
||||
| `Decimal` | `float` |
|
||||
| `bytes` | UTF-8 с заменой ошибок |
|
||||
|
||||
### Секретные поля
|
||||
|
||||
В выдаче сотрудников **никогда** не попадают колонки с паролями: суффикс `_pass`, подстроки `password`, `secret`.
|
||||
|
||||
### Имена полей
|
||||
|
||||
- В таблицах MySQL колонки имеют префикс `tMerakomisProject_code` и т.п.
|
||||
- В JSON API — **короткие алиасы**: `code`, `project_name`, `step_name`.
|
||||
|
||||
---
|
||||
|
||||
## Модель данных Merakomis
|
||||
|
||||
Ключевые таблицы (физические имена в MySQL обычно в нижнем регистре).
|
||||
|
||||
| Логическое имя | Таблица | Назначение |
|
||||
|----------------|---------|------------|
|
||||
| Сотрудник | `tMerakomisEmp` | Каталог сотрудников |
|
||||
| Проект | `tMerakomisProject` | Проекты компании |
|
||||
| Стадия проекта | `tMerakomisDStep` | Справочник стадий (ПД, РД, К…) |
|
||||
| Раздел | `tMerakomisDSection` | Разделы внутри проекта (ОВ1, ТХ…) |
|
||||
| Участник команды | `tMerakomisTeamMember` | Сотрудник ↔ команда ↔ раздел |
|
||||
| Запись времени | `tMerakomisTime` | Часы по дням |
|
||||
| Отсутствие | `tMerakomisTimeAbsence` | Отпуск, больничный… |
|
||||
| Тип отсутствия | `tMerakomisDAbsence` | Справочник типов |
|
||||
| Должность / отдел | `tMerakomisPost` → `tMerakomisDDepartment` | Орготдел сотрудника (АР, ОВ…) |
|
||||
|
||||
### Стадия проекта (`step` / `step_name`)
|
||||
|
||||
- В проекте: колонка **`tMerakomisProject_step`** → в API поле **`step`** (целое id).
|
||||
- Название стадии: справочник **`tMerakomisDStep`** (`id`, `name`, `text`).
|
||||
- `name` — краткое обозначение: **ПД**, **РД**, **К**, **АН**…
|
||||
- `text` — полное описание («Проектная документация»).
|
||||
- В эндпоинтах, где есть **`project_name`**, дополнительно отдаётся **`step_name`** (краткое название из справочника; пустая строка, если стадия не задана).
|
||||
|
||||
### Статус проекта (`status` / `status_name` / `archive`)
|
||||
|
||||
- В проекте: **`tMerakomisProject_status`** → в API **`status`** (целое 0…3), **`status_name`** — подпись из портала Merakomis (`eStatus.php`).
|
||||
- Архив: **`tMerakomisProject_archive`** (`0`/`1`), **`tMerakomisProject_archive_date`** → **`archive_date`** (`YYYY-MM-DD` или `null`).
|
||||
- В эндпоинтах с **`project_name`** (сводки, состав команды) поля **`status`**, **`status_name`**, **`archive`**, **`archive_date`** идут рядом со стадией.
|
||||
|
||||
| `status` | `status_name` |
|
||||
|----------|---------------|
|
||||
| `0` | Не определён |
|
||||
| `1` | В работе |
|
||||
| `2` | Завершён |
|
||||
| `3` | Пауза |
|
||||
|
||||
> **Не путать:** поле `step` в **`tMerakomisDSection`** — фильтр разделов по стадии, это другая сущность.
|
||||
|
||||
### Переработки (`is_over`)
|
||||
|
||||
| `is_over` | Смысл |
|
||||
|-----------|--------|
|
||||
| `0` | Рабочие часы |
|
||||
| `1` | Переработка |
|
||||
|
||||
В агрегатах:
|
||||
|
||||
- **`over1`** — переработка в рабочий день;
|
||||
- **`over2`** — переработка в выходной/праздник или день отсутствия;
|
||||
- **`over`** = `over1` + `over2`;
|
||||
- **`total`** = `hours` + `over`.
|
||||
|
||||
Раздел проекта в сводках берётся из **членства в команде** (`TeamMember.section`), а не из строки времени.
|
||||
|
||||
---
|
||||
|
||||
## Справочник эндпоинтов
|
||||
|
||||
| Метод | Путь | Ключ | Acting | Назначение |
|
||||
|-------|------|------|--------|------------|
|
||||
| GET | `/api/health` | — | — | Живость и БД |
|
||||
| GET | `/api/meta` | ✓ | — | Мета сотрудников |
|
||||
| GET | `/api/employees` | ✓ | — | Список сотрудников |
|
||||
| GET | `/api/employees/delta` | ✓ | — | Инкремент сотрудников |
|
||||
| GET | `/api/labor/meta` | ✓ | — | Имена таблиц трудозатрат |
|
||||
| GET | `/api/projects` | ✓ | — | Справочник проектов |
|
||||
| GET | `/api/sections` | ✓ | — | Справочник разделов |
|
||||
| GET | `/api/project-members` | ✓ | — | Участники проектов |
|
||||
| GET | `/api/project-sections` | ✓ | — | Разделы, допустимые на проекте |
|
||||
| GET | `/api/member-roles` | ✓ | — | Справочник ролей в команде |
|
||||
| GET | `/api/time-entries` | ✓ | — | Сырые записи времени |
|
||||
| GET | `/api/labor-summary` | ✓ | — | Агрегат (внутренние поля) |
|
||||
| GET | `/api/work-report` | ✓ | — | **Сводка для интеграции** |
|
||||
| GET | `/api/project-report` | ✓ | — | Сводка по проектам |
|
||||
| GET | `/api/calendar-days` | ✓ | — | Производственный календарь |
|
||||
| GET | `/api/absence-types` | ✓ | — | Типы отсутствий |
|
||||
| GET | `/api/labor/permissions` | ✓ | ✓ | Проверка прав |
|
||||
| GET | `/api/time-summary` | ✓ | ✓ | Сводка табеля (месяц/год) |
|
||||
| GET | `/api/time-calendar` | ✓ | ✓ | Данные календаря табеля |
|
||||
| PUT | `/api/time-entries` | ✓ | ✓ | Запись часов |
|
||||
| PUT | `/api/project-members` | ✓ | ✓ | Запись состава проекта |
|
||||
| POST | `/api/batch` | ✓ | — | Пакет до 25 GET-запросов |
|
||||
| PUT | `/api/absences` | ✓ | ✓ | Отсутствия по датам |
|
||||
| PUT | `/api/absences/range` | ✓ | ✓ | Отсутствия за диапазон |
|
||||
|
||||
✓ в колонке «Ключ» = нужен `USER_READER_API_KEY`, если он задан в окружении.
|
||||
✓ в колонке «Acting» = обязателен заголовок `X-Acting-Emp-Id`.
|
||||
|
||||
---
|
||||
|
||||
## Переменные окружения
|
||||
|
||||
| Переменная | Назначение |
|
||||
|------------|------------|
|
||||
| `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_USER`, `MYSQL_PASSWORD`, `MYSQL_DATABASE` | Подключение к MySQL |
|
||||
| `USER_READER_API_KEY` | Service-to-service ключ; пусто = без проверки |
|
||||
| `EMP_TABLE` | Явное имя таблицы сотрудников |
|
||||
| `USER_TABLE` / `USER_READER_FALLBACK_TABLE` | Резервная таблица, если нет `tMerakomisEmp` |
|
||||
| `DEBUG` | `1` — в ответе 500 может быть traceback |
|
||||
|
||||
---
|
||||
|
||||
## Сотрудники
|
||||
|
||||
### Режимы схемы таблицы
|
||||
|
||||
Сервис определяет таблицу через `resolve_emp_table()`:
|
||||
|
||||
1. **`merakomis_emp`** — колонки `tMerakomisEmp_*`; в JSON поля без префикса. Фильтр `removed = 0`, если колонка есть.
|
||||
2. **`generic_user_table`** — произвольная таблица; все несекретные колонки.
|
||||
|
||||
PHP-ориентир: **`php_model`**: `"themes/merakomis/emp/model.php"`.
|
||||
|
||||
### `GET /api/health`
|
||||
|
||||
Без API-ключа.
|
||||
|
||||
```json
|
||||
{ "ok": true, "db": true, "database": "j7508239_tracker" }
|
||||
```
|
||||
|
||||
### `GET /api/meta` — защищённый
|
||||
|
||||
Метаданные: физическая таблица, режим схемы, поля выдачи, колонка для delta.
|
||||
|
||||
| Поле | Описание |
|
||||
|------|----------|
|
||||
| `schema_mode` | `merakomis_emp` или `generic_user_table` |
|
||||
| `delta_field` | Поле для инкрементальной синхронизации (`updated` или эвристика) |
|
||||
| `selected_fields` | Имена полей в `/api/employees` |
|
||||
| `where_removed` | Колонка «удалён» в БД |
|
||||
|
||||
### `GET /api/employees` — защищённый
|
||||
|
||||
| Параметр | По умолчанию | Ограничения |
|
||||
|----------|--------------|-------------|
|
||||
| `limit` | `100` | 1…500 |
|
||||
| `offset` | `0` | ≥ 0 |
|
||||
|
||||
**Ответ:** `total`, `limit`, `offset`, `items[]`.
|
||||
|
||||
Дополнительные поля (Merakomis):
|
||||
|
||||
| Поле | Описание |
|
||||
|------|----------|
|
||||
| `department` | Коды отдела через запятую: **АР**, **ГП**, **ОВ**… |
|
||||
| `department_codes` | Массив кодов |
|
||||
| `departments` | `department_id`, `name`, `short`, `code` |
|
||||
| `staffing_ids`, `staffing_names`, `staffing_title`, `staffings` | Должность (Архитектор, ГИП, ГАП…) из `tMerakomisDStaffing` |
|
||||
| `staffing` | Сырой JSON-массив id должностей в БД |
|
||||
|
||||
Источник отделов: `tMerakomisPost` → `tMerakomisDDepartment`. Поле `roles` — не должность, а служебный фильтр подразделения в портале.
|
||||
|
||||
### `GET /api/employees/delta` — защищённый
|
||||
|
||||
Инкремент: строки, у которых время изменения **строго больше** `since_updated` (Unix, секунды).
|
||||
|
||||
| Параметр | По умолчанию |
|
||||
|----------|--------------|
|
||||
| `since_updated` | `0` |
|
||||
| `limit` | `500` |
|
||||
|
||||
**Ответ:** `since_updated`, `max_updated`, `delta_column`, `count`, `items`.
|
||||
|
||||
**Цикл синхронизации:**
|
||||
|
||||
```text
|
||||
since = 0
|
||||
loop:
|
||||
GET /api/employees/delta?since_updated={since}&limit=500
|
||||
обработать items
|
||||
since = max_updated
|
||||
пока count == limit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Трудозатраты (read)
|
||||
|
||||
Мета-информация: **`GET /api/labor/meta`** — физические имена таблиц.
|
||||
|
||||
### `GET /api/projects` — защищённый
|
||||
|
||||
Справочник `tMerakomisProject`.
|
||||
|
||||
| Параметр | По умолчанию | Описание |
|
||||
|----------|--------------|----------|
|
||||
| `limit` | `100` | 1…500 |
|
||||
| `offset` | `0` | |
|
||||
| `include_removed` | `false` | Включать `removed = 1` |
|
||||
| `include_archive` | `true` | Не фильтровать архивные |
|
||||
|
||||
**Поля `items`:** `id`, `code`, `name`, `director`, `step` (id стадии), `status`, `status_name`, `team`, `archive`, `archive_date`, `removed`, `date`, `date_end`.
|
||||
|
||||
Для человекочитаемой стадии и статуса используйте join через `/api/project-members` или агрегаты ниже (`step_name`, `status_name`).
|
||||
|
||||
### `GET /api/sections` — защищённый
|
||||
|
||||
Справочник `tMerakomisDSection`: `id`, `name`, `parent`, `step`.
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `step` | Фильтр разделов по стадии (id из `tMerakomisDSection.step`) |
|
||||
|
||||
### `GET /api/project-members` — защищённый
|
||||
|
||||
Связь сотрудник ↔ проект ↔ раздел команды.
|
||||
|
||||
| Параметр | По умолчанию | Описание |
|
||||
|----------|--------------|----------|
|
||||
| `project_id`, `emp_id` | — | Фильтры |
|
||||
| `active_only` | `true` | Только активные в команде |
|
||||
| `limit` | `100` | 1…500 |
|
||||
| `offset` | `0` | |
|
||||
|
||||
**Поля `items`:**
|
||||
|
||||
| Поле | Описание |
|
||||
|------|----------|
|
||||
| `member_id` | id записи TeamMember |
|
||||
| `emp_id`, `emp_name` | Сотрудник |
|
||||
| `project_id`, `project_code`, `project_name` | Проект |
|
||||
| **`step_name`** | Краткое название стадии (ПД, РД…) |
|
||||
| **`status`**, **`status_name`** | Статус проекта |
|
||||
| **`archive`**, **`archive_date`** | Архив и дата архивации |
|
||||
| `section_id`, `section_name` | Раздел в команде |
|
||||
| `role` | Роль в команде |
|
||||
| `active` | Активен в команде |
|
||||
|
||||
### `GET /api/project-sections` — защищённый
|
||||
|
||||
Разделы, допустимые для проекта (`tMerakomisProjectSection` → `tMerakomisDSection`).
|
||||
|
||||
| Параметр | Обязательно |
|
||||
|----------|:-----------:|
|
||||
| `project_id` | да |
|
||||
|
||||
**Ответ:** `project_id`, `total`, `items[]` с `section_id`, `section_name`.
|
||||
|
||||
### `GET /api/member-roles` — защищённый
|
||||
|
||||
Справочник ролей в команде (порт `eMemberRole`).
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `role` | Включить архивную роль при редактировании |
|
||||
|
||||
**Ответ:** `total`, `items[]` с `id`, `title`.
|
||||
|
||||
### `PUT /api/project-members` — защищённый + Acting
|
||||
|
||||
Upsert участника команды: если `(team, emp)` уже есть — обновление, иначе вставка.
|
||||
|
||||
**Тело:**
|
||||
|
||||
```json
|
||||
{
|
||||
"project_id": 86,
|
||||
"emp_id": 46,
|
||||
"section_id": 12,
|
||||
"role": 22,
|
||||
"active": true,
|
||||
"text": ""
|
||||
}
|
||||
```
|
||||
|
||||
| Поле | Обязательно | Описание |
|
||||
|------|:-----------:|----------|
|
||||
| `project_id` | да | id проекта |
|
||||
| `emp_id` | да | id сотрудника |
|
||||
| `section_id` | да | id из `GET /api/project-sections` |
|
||||
| `role` | да | id из `GET /api/member-roles` |
|
||||
| `active` | | По умолчанию `true` |
|
||||
| `text` | | Комментарий |
|
||||
|
||||
Права: порт `Rules::isRwMember` (админ, РП, ГИП, директор отдела).
|
||||
|
||||
**Ответ `200`:** `ok: true` и поля как у элемента `GET /api/project-members`.
|
||||
|
||||
### `GET /api/time-entries` — защищённый
|
||||
|
||||
Сырые строки `tMerakomisTime`.
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `date_from`, `date_to` | `YYYY-MM-DD` |
|
||||
| `emp_id`, `project_id` | Фильтры |
|
||||
| `is_over` | `0` — рабочие, `1` — переработка |
|
||||
| `limit` | 1…2000 (по умолчанию 500) |
|
||||
|
||||
**Поля:** `id`, `emp`, `project`, `date`, `duration`, `is_over`.
|
||||
|
||||
### `GET /api/work-report` — защищённый ⭐ основной контракт
|
||||
|
||||
Агрегат за период: **сотрудник × проект** с орготделом, разделом, стадией и часами.
|
||||
|
||||
**Обязательно:** `date_from`, `date_to` (`YYYY-MM-DD`, включительно).
|
||||
|
||||
| Параметр | По умолчанию | Описание |
|
||||
|----------|--------------|----------|
|
||||
| `emp_id` | — | Фильтр по сотруднику |
|
||||
| `project_id` | — | Фильтр по проекту |
|
||||
| `limit` | `500` | 1…2000 |
|
||||
| `offset` | `0` | Пагинация |
|
||||
|
||||
**Поля элемента `items`:**
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|------|-----|----------|
|
||||
| `id` | int | ID сотрудника |
|
||||
| `employee` | string | ФИО |
|
||||
| `department` | string \| null | Орготдел (коды через запятую) |
|
||||
| `staffing_title` | string \| null | Должность (Архитектор, ГИП…) |
|
||||
| `section` | string \| null | Раздел в проекте |
|
||||
| `project_code` | string | Код проекта |
|
||||
| **`step_name`** | string | Стадия (ПД, РД…) |
|
||||
| **`status`** | int \| null | Код статуса проекта |
|
||||
| **`status_name`** | string \| null | Подпись статуса |
|
||||
| **`archive`** | int | `0` / `1` |
|
||||
| **`archive_date`** | string \| null | Дата архивации |
|
||||
| `project_name` | string | Название проекта |
|
||||
| `hours`, `over`, `over1`, `over2`, `total` | number | Часы (см. модель данных) |
|
||||
|
||||
**Пример строки:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 46,
|
||||
"employee": "Агапова Анастасия Михайловна",
|
||||
"department": "ОВ",
|
||||
"staffing_title": "Инженер",
|
||||
"section": "ОВ1",
|
||||
"project_code": "2025-0016",
|
||||
"step_name": "РД",
|
||||
"status": 1,
|
||||
"status_name": "В работе",
|
||||
"archive": 0,
|
||||
"archive_date": null,
|
||||
"project_name": "ЖК Машкова",
|
||||
"hours": 8.0,
|
||||
"over": 0.0,
|
||||
"over1": 0.0,
|
||||
"over2": 0.0,
|
||||
"total": 8.0
|
||||
}
|
||||
```
|
||||
|
||||
**Пагинация длинного периода:** увеличивайте `offset` шагами до `limit` пока `offset + count < total`. Delta-режима нет — каждый запрос пересчитывает период.
|
||||
|
||||
### `GET /api/project-report` — защищённый
|
||||
|
||||
Суммарные часы по **проекту** с разбивкой по орготделу и разделу (все сотрудники).
|
||||
|
||||
**Обязательно:** `date_from`, `date_to`.
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `project_id` | Фильтр по проекту |
|
||||
| `limit`, `offset` | Пагинация (1…2000) |
|
||||
|
||||
**Поля `items`:** `id` (project_id), `project_code`, **`step_name`**, **`status`**, **`status_name`**, **`archive`**, **`archive_date`**, `project_name`, `department`, `section`, `hours`, `over`, `over1`, `over2`, `total`.
|
||||
|
||||
Одна строка = уникальная тройка **проект + department + section**.
|
||||
|
||||
### `GET /api/labor-summary` — защищённый
|
||||
|
||||
Низкоуровневый агрегат (те же расчёты, что у `/api/work-report`), с внутренними id Merakomis. Для интеграции предпочтительнее **`/api/work-report`**.
|
||||
|
||||
**Поля строки:** `emp_id`, `emp_name`, `department`, `staffing_title`, `project_id`, `project_code`, **`step_name`**, **`status`**, **`status_name`**, **`archive`**, **`archive_date`**, `project_name`, `section_id`, `section_name`, `role`, `hours`, `over`, `over1`, `over2`, `total`.
|
||||
|
||||
---
|
||||
|
||||
## Табель (read / write)
|
||||
|
||||
Полная спецификация write-логики и портирования PHP: `docs/change-proposal-labor-api-write.md`.
|
||||
|
||||
### `GET /api/calendar-days` — защищённый
|
||||
|
||||
Производственный календарь за период.
|
||||
|
||||
| Параметр | Обязательно |
|
||||
|----------|-------------|
|
||||
| `date_from`, `date_to` | да |
|
||||
|
||||
### `GET /api/absence-types` — защищённый
|
||||
|
||||
Справочник типов отсутствий. В `items` всегда есть элемент `id: 0` («снять отсутствие»).
|
||||
|
||||
### `GET /api/labor/permissions` — защищённый + Acting
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `target_emp_id` | За кого проверяем (обязательно) |
|
||||
| `project_id` | Для проверки записи часов в проект |
|
||||
|
||||
**Ответ:** `can_read`, `can_write_time`, `can_write_absence`, **`can_write_member`**, `is_admin`, `is_delegate_writer`.
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `target_emp_id` | Обязательно |
|
||||
| `project_id` | Для `can_write_time` и `can_write_member` |
|
||||
| `member_id` | Опционально, для проверки прав на редактирование записи |
|
||||
|
||||
### `GET /api/time-calendar` — защищённый + Acting
|
||||
|
||||
Данные календаря табеля сотрудника (формат, близкий к PHP `getTimeTable`).
|
||||
|
||||
| Параметр | По умолчанию | Описание |
|
||||
|----------|--------------|----------|
|
||||
| `emp_id` | acting | Чей табель |
|
||||
| `project_id` | `0` | `0` — сводный табель; иначе — по проекту |
|
||||
|
||||
### `GET /api/time-summary` — защищённый + Acting
|
||||
|
||||
Сводка за текущий месяц и год (блоки `month`, `year` с разбивкой по проектам).
|
||||
|
||||
### `PUT /api/time-entries` — защищённый + Acting
|
||||
|
||||
Запись или обновление часов за день (аналог PHP `addFromCalendar`).
|
||||
|
||||
**Тело (JSON):**
|
||||
|
||||
```json
|
||||
{
|
||||
"emp_id": 46,
|
||||
"project_id": 86,
|
||||
"date": "2026-06-10",
|
||||
"time": 8,
|
||||
"over": 0
|
||||
}
|
||||
```
|
||||
|
||||
| Поле | Описание |
|
||||
|------|----------|
|
||||
| `emp_id` | Опционально; по умолчанию = acting |
|
||||
| `project_id` | Обязательно |
|
||||
| `date` | `YYYY-MM-DD` |
|
||||
| `time` | Часы, 0…24 |
|
||||
| `over` | `0` — рабочие, `1` — переработка |
|
||||
|
||||
`time: 0` удаляет запись за этот день/тип.
|
||||
|
||||
**Ответ `200`:** `ok`, `id`, `duration`, `info`, `limits` (применённые лимиты дня).
|
||||
|
||||
### `PUT /api/absences` — защищённый + Acting
|
||||
|
||||
```json
|
||||
{
|
||||
"emp_id": 46,
|
||||
"type": 3,
|
||||
"dates": ["2026-06-10", "2026-06-11"]
|
||||
}
|
||||
```
|
||||
|
||||
`type: 0` — снять отсутствие. При установке отсутствия рабочие часы за день удаляются.
|
||||
|
||||
### `PUT /api/absences/range` — защищённый + Acting
|
||||
|
||||
```json
|
||||
{
|
||||
"emp_id": 46,
|
||||
"absence_id": 3,
|
||||
"begin": "2026-06-10",
|
||||
"end": "2026-06-20"
|
||||
}
|
||||
```
|
||||
|
||||
`absence_id: 0` — очистить диапазон (удалить отсутствия и рабочие часы).
|
||||
|
||||
---
|
||||
|
||||
## Сценарии интеграции
|
||||
|
||||
### A. Периодическая синхронизация сотрудников
|
||||
|
||||
1. Храните у себя `last_since_updated` (Unix).
|
||||
2. Вызывайте `/api/employees/delta?since_updated={last}&limit=500`.
|
||||
3. Upsert по `id`; обновляйте `last_since_updated = max_updated`.
|
||||
4. Раз в сутки — полная сверка через `/api/employees` с пагинацией.
|
||||
|
||||
### B. Отчёт по трудозатратам за месяц
|
||||
|
||||
1. `GET /api/work-report?date_from=2026-05-01&date_to=2026-05-31&fetch_all=true` — один запрос на весь период.
|
||||
2. Или `POST /api/batch` с несколькими периодами в `requests[]`.
|
||||
3. Группируйте у себя по `department`, `step_name`, `project_code` при необходимости.
|
||||
|
||||
### C. Список проектов сотрудника для UI
|
||||
|
||||
`GET /api/project-members?emp_id={id}&active_only=true` — в ответе `project_code`, `step_name`, `status_name`, `archive`, `project_name`.
|
||||
|
||||
### D. Запись часов из мобильного/бота
|
||||
|
||||
1. Проверить права: `GET /api/labor/permissions?target_emp_id={id}&project_id={pid}`.
|
||||
2. Записать: `PUT /api/time-entries` с `X-Acting-Emp-Id` и телом.
|
||||
3. Обработать `403` (нет прав), `400` (невалидная дата).
|
||||
|
||||
### E. Сводка по проектам для руководства
|
||||
|
||||
`GET /api/project-report?date_from=…&date_to=…` — агрегат по проект + отдел + раздел.
|
||||
|
||||
### F. Управление составом проекта из внешнего приложения
|
||||
|
||||
1. `GET /api/project-sections?project_id={pid}` — допустимые разделы.
|
||||
2. `GET /api/member-roles` — роли.
|
||||
3. `GET /api/labor/permissions?target_emp_id={acting}&project_id={pid}` → `can_write_member`.
|
||||
4. `PUT /api/project-members` с `X-Acting-Emp-Id` и телом.
|
||||
5. Проверка: `GET /api/project-members?project_id={pid}`.
|
||||
|
||||
---
|
||||
|
||||
## Коды ошибок
|
||||
|
||||
| Код | Когда |
|
||||
|-----|--------|
|
||||
| `400` | Невалидные параметры, `date_from > date_to`, нет колонки для delta, нет `X-Acting-Emp-Id` |
|
||||
| `401` | Неверный или отсутствующий API-ключ |
|
||||
| `403` | Нет прав на табель / запись |
|
||||
| `404` | Сотрудник или проект не найден |
|
||||
| `500` | Ошибка MySQL, не найдена таблица Merakomis |
|
||||
|
||||
Write-эндпоинты часто возвращают структурированный `detail`:
|
||||
|
||||
```json
|
||||
{ "code": "forbidden", "message": "Нет прав на запись часов" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Примеры запросов
|
||||
|
||||
Локальный ключ из `docker-compose.yml`: **`local-dev-key-change-in-prod`**.
|
||||
Замените `HOST` на IP/имя хоста.
|
||||
|
||||
```bash
|
||||
# Health (без ключа)
|
||||
curl -s http://HOST:8090/api/health
|
||||
|
||||
# Мета сотрудников
|
||||
curl -s -H "X-Api-Key: local-dev-key-change-in-prod" \
|
||||
http://HOST:8090/api/meta
|
||||
|
||||
# Сводка часов (основной контракт интеграции)
|
||||
curl -s -H "X-Api-Key: local-dev-key-change-in-prod" \
|
||||
"http://HOST:8090/api/work-report?date_from=2026-05-01&date_to=2026-05-31&limit=2000"
|
||||
|
||||
# Участники проекта со стадией
|
||||
curl -s -H "X-Api-Key: local-dev-key-change-in-prod" \
|
||||
"http://HOST:8090/api/project-members?emp_id=46&active_only=true"
|
||||
|
||||
# Состав проекта: запись
|
||||
curl -s -X PUT "http://HOST:8090/api/project-members" \
|
||||
-H "X-Api-Key: local-dev-key-change-in-prod" \
|
||||
-H "X-Acting-Emp-Id: 1" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"project_id":86,"emp_id":46,"section_id":12,"role":22,"active":true}'
|
||||
|
||||
# Инкремент сотрудников
|
||||
curl -s -H "X-Api-Key: local-dev-key-change-in-prod" \
|
||||
"http://HOST:8090/api/employees/delta?since_updated=0&limit=500"
|
||||
|
||||
# Запись часов
|
||||
curl -s -X PUT "http://HOST:8090/api/time-entries" \
|
||||
-H "X-Api-Key: local-dev-key-change-in-prod" \
|
||||
-H "X-Acting-Emp-Id: 46" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"project_id":86,"date":"2026-06-10","time":8,"over":0}'
|
||||
```
|
||||
|
||||
### Пример на Python (httpx)
|
||||
|
||||
```python
|
||||
import httpx
|
||||
|
||||
BASE = "http://user-reader:8090"
|
||||
HEADERS = {"X-Api-Key": "local-dev-key-change-in-prod"}
|
||||
|
||||
def fetch_work_report(date_from: str, date_to: str) -> list[dict]:
|
||||
items: list[dict] = []
|
||||
offset = 0
|
||||
limit = 2000
|
||||
while True:
|
||||
r = httpx.get(
|
||||
f"{BASE}/api/work-report",
|
||||
headers=HEADERS,
|
||||
params={
|
||||
"date_from": date_from,
|
||||
"date_to": date_to,
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
},
|
||||
timeout=60.0,
|
||||
)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
items.extend(data["items"])
|
||||
offset += data["count"]
|
||||
if offset >= data["total"]:
|
||||
break
|
||||
return items
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Запуск и сборка
|
||||
|
||||
Зависимости: `services/user-reader/requirements.txt`.
|
||||
Точка входа: **`app.main:app`** (uvicorn).
|
||||
|
||||
Исходники схем:
|
||||
|
||||
| Модуль | Назначение |
|
||||
|--------|------------|
|
||||
| `app/emp_schema.py` | Сотрудники |
|
||||
| `app/merakomis_schema.py` | Проекты, время, стадии |
|
||||
| `app/labor.py` | Read API трудозатрат |
|
||||
| `app/labor_write.py` | Write API табеля |
|
||||
| `app/project_members_read.py` | Read: project-sections, member-roles |
|
||||
| `app/project_members_write.py` | Write: PUT project-members |
|
||||
| `app/labor_calendar.py` | Read API табеля |
|
||||
| `app/labor_permissions.py` | Права |
|
||||
|
||||
### Сборка образа и ошибка `failed to solve`
|
||||
|
||||
При `docker compose build user-reader` сообщение **`failed to solve: python:...`** — Docker не смог скачать базовый образ с Hub.
|
||||
|
||||
Базовый образ: **`python:3.12-slim-bookworm`**.
|
||||
|
||||
**Вариант A** — есть интернет:
|
||||
|
||||
```bash
|
||||
docker pull python:3.12-slim-bookworm
|
||||
docker compose build user-reader
|
||||
docker compose up -d --no-deps user-reader
|
||||
```
|
||||
|
||||
**Вариант B** — сервер без Hub:
|
||||
|
||||
```powershell
|
||||
.\services\user-reader\scripts\export-image.ps1
|
||||
# скопировать user-reader-image.tar на сервер
|
||||
.\services\user-reader\scripts\import-image.ps1
|
||||
```
|
||||
|
||||
**Вариант C** — зеркало registry в `daemon.json`.
|
||||
|
||||
---
|
||||
|
||||
## Связанные документы
|
||||
|
||||
| Документ | Содержание |
|
||||
|----------|------------|
|
||||
| [docs/user-reader-api.md](docs/user-reader-api.md) | **API, доступ, эндпоинты, сценарии вызова** |
|
||||
| `docs/change-proposal-labor-api-write.md` | Детальная спецификация write API и портирования PHP |
|
||||
| `docs/integration-external-app-employees.md` | Legacy PHP API (cookie); для новых интеграций используйте user-reader |
|
||||
18
Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
FROM php:7.3-apache
|
||||
|
||||
# Основные модули PHP.
|
||||
RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini
|
||||
RUN sed -i 's/;date.timezone =/date.timezone = Europe\/Moscow/' /usr/local/etc/php/php.ini
|
||||
RUN sed -i 's/short_open_tag = Off/short_open_tag = On/' /usr/local/etc/php/php.ini
|
||||
RUN sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 4000M/' /usr/local/etc/php/php.ini
|
||||
RUN sed -i 's/post_max_size = 8M/post_max_size = 4000M/' /usr/local/etc/php/php.ini
|
||||
RUN apt update && apt install -y libicu-dev zlib1g-dev libpng-dev libzip-dev curl libcurl4-openssl-dev libjpeg-dev libfreetype6-dev
|
||||
RUN docker-php-ext-configure gd --with-jpeg-dir=/usr --with-freetype-dir=/usr && docker-php-ext-install gd
|
||||
RUN docker-php-ext-install curl
|
||||
RUN docker-php-ext-install mysqli
|
||||
|
||||
# Microsoft SQL Server (sqlsrv / pdo_sqlsrv) убран из образа: на базе php:7.3 + текущий Debian
|
||||
# PECL часто собирает только pdo_sqlsrv, без sqlsrv — ломает docker-php-ext-enable.
|
||||
# Основной проект использует MySQL (mysqli). Тесты sqlsrv — themes/merakomis/test/controller.php.
|
||||
|
||||
RUN a2enmod rewrite
|
||||
252
MeraProject/services/user-reader/app/main.py
Normal file
@ -0,0 +1,252 @@
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
import pymysql
|
||||
from fastapi import FastAPI, HTTPException, Query
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse
|
||||
from pymysql.cursors import DictCursor
|
||||
|
||||
MYSQL = {
|
||||
"host": os.environ.get("MYSQL_HOST", "db"),
|
||||
"port": int(os.environ.get("MYSQL_PORT", "3306")),
|
||||
"user": os.environ.get("MYSQL_USER", "root"),
|
||||
"password": os.environ.get("MYSQL_PASSWORD", ""),
|
||||
"database": os.environ.get("MYSQL_DATABASE", "j7508239_tracker"),
|
||||
"charset": "utf8mb4",
|
||||
"cursorclass": DictCursor,
|
||||
}
|
||||
|
||||
app = FastAPI(title="Mera user reader", version="1.0.0")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
def get_conn():
|
||||
return pymysql.connect(**MYSQL)
|
||||
|
||||
|
||||
def _json_cell(v: Any) -> Any:
|
||||
if v is None:
|
||||
return None
|
||||
if isinstance(v, datetime):
|
||||
return v.isoformat(sep=" ", timespec="seconds")
|
||||
if isinstance(v, date):
|
||||
return v.isoformat()
|
||||
if isinstance(v, Decimal):
|
||||
return float(v)
|
||||
if isinstance(v, bytes):
|
||||
return v.decode("utf-8", errors="replace")
|
||||
return v
|
||||
|
||||
|
||||
def _is_secret_col(name: str) -> bool:
|
||||
n = name.lower()
|
||||
return n.endswith("_pass") or "password" in n or "secret" in n
|
||||
|
||||
|
||||
def _strip_sensitive(row: dict) -> dict:
|
||||
return {k: v for k, v in row.items() if not _is_secret_col(k)}
|
||||
|
||||
|
||||
def json_rows(rows: list[dict]) -> list[dict]:
|
||||
return [
|
||||
{k: _json_cell(v) for k, v in _strip_sensitive(row).items()} for row in rows
|
||||
]
|
||||
|
||||
|
||||
def _db_name(cur) -> str:
|
||||
cur.execute("SELECT DATABASE() AS d")
|
||||
return cur.fetchone()["d"]
|
||||
|
||||
|
||||
def _list_tables(cur, db: str) -> list[str]:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT TABLE_NAME AS n FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = %s ORDER BY TABLE_NAME
|
||||
""",
|
||||
(db,),
|
||||
)
|
||||
return [r["n"] for r in cur.fetchall()]
|
||||
|
||||
|
||||
def _quote_ident(name: str) -> str:
|
||||
return "`" + name.replace("`", "``") + "`"
|
||||
|
||||
|
||||
def resolve_emp_table(cur, db: str) -> str:
|
||||
"""Имя таблицы сотрудников Merakomis (или из EMP_TABLE)."""
|
||||
env = os.environ.get("EMP_TABLE", "").strip().strip("`")
|
||||
if env:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT COUNT(*) AS c FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s
|
||||
""",
|
||||
(db, env),
|
||||
)
|
||||
if cur.fetchone()["c"]:
|
||||
return env
|
||||
raise RuntimeError(
|
||||
f"EMP_TABLE={env!r} не найдена в БД {db!r}. "
|
||||
f"Есть таблицы: {', '.join(_list_tables(cur, db)[:50])}"
|
||||
)
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT TABLE_NAME AS n
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = %s
|
||||
AND (
|
||||
LOWER(TABLE_NAME) = 'tmerakomisemp'
|
||||
OR (
|
||||
LOWER(TABLE_NAME) REGEXP 'merakomis.*emp'
|
||||
AND LOWER(TABLE_NAME) NOT LIKE '%%children%%'
|
||||
AND LOWER(TABLE_NAME) NOT LIKE '%%group%%'
|
||||
)
|
||||
)
|
||||
ORDER BY
|
||||
CASE WHEN LOWER(TABLE_NAME) = 'tmerakomisemp' THEN 0 ELSE 1 END,
|
||||
CHAR_LENGTH(TABLE_NAME)
|
||||
LIMIT 1
|
||||
""",
|
||||
(db,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
return row["n"]
|
||||
|
||||
all_t = _list_tables(cur, db)
|
||||
hint = ", ".join([t for t in all_t if re.search(r"emp|user|staff|profile", t, re.I)][:30])
|
||||
raise RuntimeError(
|
||||
"Таблица сотрудников Merakomis не найдена (ожидали что-то вроде tmerakomisemp). "
|
||||
"Импортируйте дамп с этой таблицей или задайте переменную окружения EMP_TABLE. "
|
||||
f"База: {db!r}, таблиц: {len(all_t)}. Похожие по имени: {hint or '—'}"
|
||||
)
|
||||
|
||||
|
||||
def _table_columns(cur, db: str, table: str) -> list[str]:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT COLUMN_NAME AS c
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s
|
||||
ORDER BY ORDINAL_POSITION
|
||||
""",
|
||||
(db, table),
|
||||
)
|
||||
return [r["c"] for r in cur.fetchall()]
|
||||
|
||||
|
||||
def _pick_order_column(cols: list[str]) -> str:
|
||||
for c in cols:
|
||||
if "name" in c.lower() and "emp" in c.lower():
|
||||
return c
|
||||
for c in cols:
|
||||
if c.lower().endswith("_name"):
|
||||
return c
|
||||
return cols[0]
|
||||
|
||||
|
||||
def _pick_removed_column(cols: list[str]) -> str | None:
|
||||
for c in cols:
|
||||
if c.lower().endswith("_removed"):
|
||||
return c
|
||||
return None
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def index():
|
||||
return FileResponse(
|
||||
os.path.join(os.path.dirname(__file__), "..", "static", "index.html")
|
||||
)
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
def health() -> dict[str, Any]:
|
||||
try:
|
||||
with get_conn() as c:
|
||||
with c.cursor() as cur:
|
||||
cur.execute("SELECT 1 AS ok")
|
||||
row = cur.fetchone()
|
||||
db = _db_name(cur)
|
||||
return {"ok": True, "db": bool(row and row.get("ok") == 1), "database": db}
|
||||
except pymysql.Error as e:
|
||||
return {"ok": False, "db": False, "error": str(e)}
|
||||
|
||||
|
||||
@app.get("/api/meta")
|
||||
def meta() -> dict[str, Any]:
|
||||
"""Какую таблицу нашли и какие колонки (без запуска полного списка людей)."""
|
||||
try:
|
||||
with get_conn() as c:
|
||||
with c.cursor() as cur:
|
||||
db = _db_name(cur)
|
||||
table = resolve_emp_table(cur, db)
|
||||
cols = _table_columns(cur, db, table)
|
||||
return {
|
||||
"database": db,
|
||||
"table": table,
|
||||
"columns": cols,
|
||||
"removed_column": _pick_removed_column(cols),
|
||||
"order_column": _pick_order_column(cols),
|
||||
}
|
||||
except pymysql.Error as e:
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e_
|
||||
|
||||
|
||||
@app.get("/api/employees")
|
||||
def employees(
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
offset: int = Query(0, ge=0),
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
with get_conn() as c:
|
||||
with c.cursor() as cur:
|
||||
db = _db_name(cur)
|
||||
table = resolve_emp_table(cur, db)
|
||||
cols = _table_columns(cur, db, table)
|
||||
if not cols:
|
||||
raise RuntimeError(f"Таблица {_quote_ident(table)} без колонок")
|
||||
|
||||
removed = _pick_removed_column(cols)
|
||||
order_col = _pick_order_column(cols)
|
||||
tq = _quote_ident(table)
|
||||
oq = _quote_ident(order_col)
|
||||
|
||||
where_sql = ""
|
||||
if removed:
|
||||
where_sql = f" WHERE {_quote_ident(removed)} = 0 "
|
||||
|
||||
sql = f"SELECT * FROM {tq} {where_sql} ORDER BY {oq} LIMIT %s OFFSET %s"
|
||||
cur.execute(sql, (limit, offset))
|
||||
rows = cur.fetchall()
|
||||
|
||||
cnt_sql = f"SELECT COUNT(*) AS n FROM {tq} {where_sql}"
|
||||
cur.execute(cnt_sql)
|
||||
total = cur.fetchone()["n"]
|
||||
|
||||
return {
|
||||
"table": table,
|
||||
"total": int(total),
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"items": json_rows(rows),
|
||||
}
|
||||
except pymysql.Error as e:
|
||||
raise HTTPException(status_code=500, detail=str(e)) from e
|
||||
except Exception as e:
|
||||
dbg = os.environ.get("DEBUG", "")
|
||||
msg = str(e)
|
||||
if dbg == "1":
|
||||
msg = f"{msg}\n{traceback.format_exc()}"
|
||||
raise HTTPException(status_code=500, detail=msg) from e_
|
||||
851
Task/Управление проектами/ДАШБОРД-1-v22.html
Normal file
@ -0,0 +1,851 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>Портфель проектов v22</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0c1117;
|
||||
--surface: #131a22;
|
||||
--surface-2: #18212b;
|
||||
--surface-3: #1e2935;
|
||||
--border: rgba(120,160,200,0.16);
|
||||
--text: #e8eef5;
|
||||
--muted: #93a3b5;
|
||||
--faint: #647386;
|
||||
--accent: #5aa6e0;
|
||||
--accent-2: #6fd1c0;
|
||||
--ok: #5cc0a0;
|
||||
--warn: #e0b15a;
|
||||
--bad: #e07a6f;
|
||||
--bar-track: rgba(255,255,255,0.07);
|
||||
--shadow: 0 16px 44px rgba(0,0,0,0.40);
|
||||
--radius: 14px;
|
||||
/* Единый нейтральный серый для всех шкал */
|
||||
--bar-neutral: rgba(100,130,160,0.55);
|
||||
--bar-neutral-s: rgba(100,130,160,0.65); /* сводная */
|
||||
--c1: 260px; --c2: 280px;
|
||||
--grid-cols: var(--c1) 100px var(--c2) 1.1fr 2.2fr 2.2fr;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: Inter, Arial, sans-serif;
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(90,166,224,0.08), transparent 32%),
|
||||
radial-gradient(circle at top right, rgba(111,209,192,0.07), transparent 30%),
|
||||
linear-gradient(180deg, #0e141b 0%, #0a0f14 100%);
|
||||
color: var(--text);
|
||||
}
|
||||
html { height: 100%; scroll-padding-top: 70px; }
|
||||
body { min-height: 100%; }
|
||||
.page { max-width: 1800px; margin: 0 auto; padding: 24px 22px 40px; }
|
||||
|
||||
/* sticky-header удалён */
|
||||
|
||||
|
||||
/* ─── КОМАНДА ─── */
|
||||
.team-cell { display:flex; flex-direction:column; align-items:center; justify-content:center; padding:6px 6px; }
|
||||
.team-roles { flex:0 0 auto; display:flex; flex-direction:column; gap:1px; align-self:center; }
|
||||
.team-role-row { display:flex; align-items:center; gap:4px; }
|
||||
.team-role-name { color:var(--faint); font-size:10px; min-width:34px; }
|
||||
.team-role-count { font-weight:700; color:var(--text); font-size:11px; min-width:16px; text-align:right; }
|
||||
.team-donut-wrap { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px; }
|
||||
.team-total { font-size:11px; font-weight:700; color:var(--muted); white-space:nowrap; }
|
||||
/* ─── ТОПБАР ─── */
|
||||
.topbar { margin-bottom: 7px; }
|
||||
.eyebrow { color: var(--accent); text-transform: uppercase; letter-spacing: .14em; font-size: 12px; margin-bottom: 6px; }
|
||||
h1 { font-size: 30px; font-weight: 700; }
|
||||
.date-tag {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
background: rgba(90,166,224,0.12); border: 1px solid rgba(90,166,224,0.28);
|
||||
border-radius: 8px; padding: 4px 12px;
|
||||
font-size: 12px; color: var(--accent); font-weight: 600;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.date-tag .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 5px var(--ok); }
|
||||
|
||||
/* ─── СВОДНАЯ СТРОКА (7 колонок, над доской) ─── */
|
||||
.portfolio-summary {
|
||||
display: grid;
|
||||
grid-template-columns: var(--grid-cols);
|
||||
gap: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.ps-card {
|
||||
background: rgba(90,166,224,0.06);
|
||||
border: 1px solid rgba(90,166,224,0.15);
|
||||
border-radius: 10px;
|
||||
padding: 10px 14px;
|
||||
display: flex; flex-direction: column; justify-content: center;
|
||||
}
|
||||
.ps-card .ps-title { font-size: 9px; text-transform: uppercase; letter-spacing: .1em; color: var(--faint); margin-bottom: 3px; }
|
||||
.ps-card .ps-val { font-size: 15px; font-weight: 700; color: var(--accent); }
|
||||
.ps-card .ps-sub { font-size: 11px; color: var(--muted); margin-top: 2px; }
|
||||
.ps-metric {
|
||||
background: rgba(255,255,255,0.022);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 10px 14px;
|
||||
display: flex; flex-direction: column; justify-content: center; gap: 5px;
|
||||
}
|
||||
.ps-metric .ps-title { font-size: 9px; text-transform: uppercase; letter-spacing: .08em; color: var(--faint); }
|
||||
.ps-avg { display: flex; align-items: center; gap: 8px; }
|
||||
.ps-avg .avg-n { font-size: 18px; font-weight: 700; min-width: 44px; color: var(--text); }
|
||||
.ps-avg .avg-bar { flex: 1; height: 5px; border-radius: 3px; background: var(--bar-track); overflow: hidden; }
|
||||
.ps-avg .avg-fill { height: 100%; border-radius: inherit; }
|
||||
/* Итоговые заголовки: серый или красный если >100% */
|
||||
.avg-neutral { background: var(--bar-neutral); }
|
||||
.avg-overflow { background: rgba(224,122,111,0.85); }
|
||||
|
||||
/* ─── ДОСКА ─── */
|
||||
.board { display: flex; flex-direction: column; gap: 0; overflow: visible; }
|
||||
.board-head {
|
||||
display: grid; grid-template-columns: var(--grid-cols); gap: 10px;
|
||||
margin-bottom: 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background: #0d131a;
|
||||
padding: 10px 0 8px;
|
||||
box-shadow: 0 6px 24px rgba(0,0,0,0.6);
|
||||
}
|
||||
.col-head {
|
||||
background: linear-gradient(180deg, var(--surface-3), var(--surface-2));
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 13px 12px;
|
||||
display: flex; align-items: center; justify-content: center; gap: 6px;
|
||||
font-weight: 700; font-size: 14px;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
.col-sort {
|
||||
width: 18px; height: 18px; border-radius: 5px;
|
||||
background: rgba(255,255,255,0.05); border: 1px solid var(--border);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 9px; color: var(--faint); cursor: pointer; flex-shrink: 0;
|
||||
}
|
||||
.col-sort:hover { border-color: rgba(90,166,224,0.4); color: var(--accent); }
|
||||
|
||||
.board-body { display: flex; flex-direction: column; gap: 4px; }
|
||||
|
||||
/* ─── СТРОКА ─── */
|
||||
.board-row {
|
||||
display: grid; grid-template-columns: var(--grid-cols); gap: 6px;
|
||||
}
|
||||
.board-row .proj-cell { border-left: 3px solid transparent; }
|
||||
/* Левая полоска — по стадии проекта */
|
||||
.board-row.stage-kk .proj-cell { border-left-color: #E0B15A; }
|
||||
.board-row.stage-pd .proj-cell { border-left-color: #5CC0A0; }
|
||||
.board-row.stage-rd .proj-cell { border-left-color: #5AA6E0; }
|
||||
/* health-dot остаётся отдельной логикой */
|
||||
|
||||
/* ─── ЯЧЕЙКИ ─── */
|
||||
.cell {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 8px 10px; min-height: 0;
|
||||
display: flex; flex-direction: column; justify-content: center;
|
||||
}
|
||||
.cell.overflow-cell {
|
||||
background: rgba(224,122,111,0.07);
|
||||
border-color: rgba(224,122,111,0.30);
|
||||
}
|
||||
|
||||
/* ─── КАРТОЧКА ПРОЕКТА ─── */
|
||||
.proj-cell { align-items: stretch; justify-content: flex-start; }
|
||||
.proj-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 8px; }
|
||||
.proj-code { font-size: 10px; color: var(--muted); margin-bottom: 1px; }
|
||||
.proj-code b { color: var(--text); }
|
||||
.proj-name { font-size: 13px; font-weight: 700; line-height: 1.2; }
|
||||
.proj-dev { font-size: 10px; color: var(--faint); margin-top: 1px; }
|
||||
.proj-gip { font-size: 10px; color: var(--muted); margin-top: 2px; }
|
||||
.badge { flex: none; font-size: 11px; font-weight: 700; border-radius: 8px; padding: 4px 9px; }
|
||||
/* Стадии: КК=золотой, ПД=зелёный, РД=синий */
|
||||
.badge-kk { background: rgba(224,177,90,0.15); border: 2px solid rgba(224,177,90,0.55); color: #E0B15A; font-weight: 800; }
|
||||
.badge-pd { background: rgba(92,192,160,0.15); border: 2px solid rgba(92,192,160,0.55); color: #5CC0A0; font-weight: 800; }
|
||||
.badge-rd { background: rgba(90,166,224,0.15); border: 2px solid rgba(90,166,224,0.55); color: #5AA6E0; font-weight: 800; }
|
||||
|
||||
.proj-health { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
|
||||
.health-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
||||
.health-dot.ok { background: var(--ok); box-shadow: 0 0 6px var(--ok); }
|
||||
.health-dot.warn { background: var(--warn); box-shadow: 0 0 6px var(--warn); }
|
||||
.health-dot.bad { background: var(--bad); box-shadow: 0 0 6px var(--bad); }
|
||||
.health-label { font-size: 11px; color: var(--muted); }
|
||||
|
||||
.proj-link { font-size: 10px; color: var(--accent); margin-top: 3px; cursor: pointer; opacity: 0.75; text-decoration: none; display: inline-flex; align-items: center; gap: 3px; }
|
||||
.proj-link:hover { opacity: 1; }
|
||||
|
||||
/* ─── СВОДНАЯ КОЛОНКА (mini-bars) ─── */
|
||||
.summary-list { display: flex; flex-direction: column; gap: 3px; }
|
||||
.srow { display: flex; align-items: center; gap: 6px; }
|
||||
.srow .slabel { width: 58px; flex-shrink: 0; color: var(--faint); font-size: 10px; text-transform: lowercase; }
|
||||
.srow .sbar-wrap {
|
||||
flex: 1; height: 9px; border-radius: 4px;
|
||||
background: var(--bar-track); border: 1px solid var(--border);
|
||||
overflow: hidden; position: relative;
|
||||
}
|
||||
.srow .sbar-fill { height: 100%; border-radius: inherit; }
|
||||
/* Единый нейтральный серый для всех мини-баров */
|
||||
.sbar-neutral { background: var(--bar-neutral-s); }
|
||||
/* Перерасход в сводной */
|
||||
.sbar-over { background: rgba(224,122,111,0.85); width: 100% !important; }
|
||||
.sbar-wrap.over-wrap { border-color: rgba(224,122,111,0.3); background: rgba(224,122,111,0.08); }
|
||||
.srow .spct { width: 36px; text-align: right; font-size: 11px; font-weight: 700; flex-shrink: 0; color: var(--muted); }
|
||||
.spct-over { color: var(--bad) !important; }
|
||||
.ok { color: var(--ok); }
|
||||
.warn { color: var(--warn); }
|
||||
.bad { color: var(--bad); }
|
||||
|
||||
/* ─── МЕТРИКА ─── */
|
||||
.metric-top {
|
||||
display: flex; justify-content: space-between; align-items: stretch;
|
||||
flex: 1; margin-bottom: 4px;
|
||||
}
|
||||
.metric-pair { font-size: 11px; color: var(--muted); line-height: 1.7; display: flex; flex-direction: column; justify-content: space-evenly; flex: 1; }
|
||||
.mp-label { font-size: 9px; text-transform: uppercase; letter-spacing: .07em; color: var(--faint); margin-bottom: 1px; }
|
||||
.mp-plan { color: var(--faint); margin-bottom: 0px; }
|
||||
.mp-fact { color: var(--faint); font-weight: 400; }
|
||||
.metric-kpi {
|
||||
text-align: right;
|
||||
border-left: 1px solid var(--border);
|
||||
padding-left: 12px;
|
||||
display: flex; flex-direction: column; align-items: flex-end; justify-content: space-evenly;
|
||||
}
|
||||
.kpi-label { font-size: 9px; text-transform: uppercase; letter-spacing: .07em; color: var(--faint); margin-bottom: 0px; }
|
||||
.kpi-n { font-size: 14px; font-weight: 800; line-height: 1; color: var(--faint); }
|
||||
.kpi-n.bad { color: var(--bad); }
|
||||
.kpi-u { font-size: 10px; color: var(--faint); margin-top: 0px; }
|
||||
.kpi-delta { font-size: 10px; font-weight: 700; color: var(--bad); margin-top: 0px; }
|
||||
|
||||
/* ─── БАР — единый серый, читаемый % ─── */
|
||||
.bar-row {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
}
|
||||
.bar {
|
||||
height: 9px;
|
||||
border-radius: 4px;
|
||||
background: var(--bar-track);
|
||||
border: 1px solid var(--border);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
.bar .fill {
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: var(--bar-neutral);
|
||||
}
|
||||
.bar .pct { display: none; }
|
||||
.bar-pct-over { color: var(--bad) !important; font-weight: 800; }
|
||||
.bar-pct-skew { color: #FFA020 !important; font-weight: 800; }
|
||||
.bar-pct {
|
||||
font-size: 11px; font-weight: 700;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
width: 36px;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Бар при перерасходе >100% — красный + пульсация */
|
||||
.bar.bar-overflow {
|
||||
border-color: rgba(224,122,111,0.55);
|
||||
background: rgba(224,122,111,0.85);
|
||||
}
|
||||
.bar.bar-overflow .fill {
|
||||
width: 100% !important;
|
||||
background: rgba(224,122,111,0.85);
|
||||
border-radius: inherit;
|
||||
position: relative;
|
||||
}
|
||||
.bar.bar-overflow .fill::after {
|
||||
content: '';
|
||||
position: absolute; right: 0; top: 0; bottom: 0; width: 4px;
|
||||
background: rgba(255,80,60,0.9);
|
||||
animation: pulse-edge 1.4s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse-edge {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.3; }
|
||||
}
|
||||
.bar.bar-overflow .pct {
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
text-shadow: 0 1px 4px rgba(0,0,0,0.9);
|
||||
}
|
||||
|
||||
/* ─── ПЕРЕКОС >20% — оранжевый нижний бордер ─── */
|
||||
.cell.skew-cell {
|
||||
background: rgba(255,140,0,0.07);
|
||||
border-color: rgba(255,140,0,0.30);
|
||||
}
|
||||
/* СРЫВ перекрывает — overflow-cell не получает skew стиль */
|
||||
.cell.overflow-cell {
|
||||
border-bottom-color: rgba(224,122,111,0.30); /* красный, не оранжевый */
|
||||
}
|
||||
|
||||
/* Оранжевый мини-бар при расхождении >20% */
|
||||
.sbar-skew { background: rgba(255,150,30,0.85) !important; }
|
||||
.spct-skew { color: #FFA020 !important; font-weight: 800; }
|
||||
|
||||
/* overflow-cell.skew-cell — красный фон приоритетнее */
|
||||
.cell.overflow-cell.skew-cell {
|
||||
background: rgba(224,122,111,0.07);
|
||||
border-color: rgba(224,122,111,0.30);
|
||||
}
|
||||
|
||||
/* Ячейки метрик (Готовность, Трудозатраты, Сроки, Поступления, Затраты) — без скруглений */
|
||||
.board-row .cell:nth-child(n+4) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
/* Бары внутри этих ячеек тоже без скруглений */
|
||||
.board-row .cell:nth-child(n+4) .bar {
|
||||
border-radius: 4px;
|
||||
}
|
||||
.board-row .cell:nth-child(n+4) .bar .fill {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
|
||||
/* ─── ПАРНАЯ ЯЧЕЙКА (Готовн.+Трудозат., Поступл.+Затраты) ─── */
|
||||
.pair-cell { display: flex; flex-direction: row; flex-wrap: nowrap; gap: 10px; padding: 8px 10px; }
|
||||
.pair-half { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; justify-content: space-between; }
|
||||
.pair-half + .pair-half { border-left: 1px solid var(--border); padding-left: 10px; }
|
||||
.pair-half .metric-top { margin-bottom: 4px; }
|
||||
/* override v21 rule that gave nth-child >=4 ячейкам square corners is fine, no override needed */
|
||||
|
||||
/* ─── ЯЧЕЙКА СРОКОВ (начало/окончание + осталось) ─── */
|
||||
/* выравнивание по вертикали повторяет структуру .metric-top в соседних ячейках:
|
||||
верхние .mp-label / .kpi-label стоят на одной линии; первая .dr-line = первая строка .metric-pair */
|
||||
/* Повторяем поведение .metric-top + .metric-pair (space-evenly, line-height: 1.7),
|
||||
чтобы верхние метки и строки внутри стояли на тех же высотах, что в соседних ячейках */
|
||||
/* dates-cell: повторяет геометрию .pair-half — metric-top (gap-row-1) + bar-row снизу.
|
||||
Никакого space-evenly на pair-ах, чтобы строки лежали ровно как у соседей. */
|
||||
.dates-cell { display: flex; flex-direction: column; padding: 8px 10px; }
|
||||
.dates-top { display: flex; gap: 10px; align-items: stretch; margin-bottom: 4px; flex: 1; }
|
||||
.dates-range { display: flex; flex-direction: column; flex: 1.6; }
|
||||
.dates-kpi { display: flex; flex-direction: column; flex: 1; border-left: 1px solid var(--border); padding-left: 12px; text-align: right; align-items: flex-end; justify-content: space-evenly; }
|
||||
.dates-kpi .kpi-label { font-size: 9px; text-transform: uppercase; letter-spacing: .07em; color: var(--faint); margin-bottom: 0; }
|
||||
.dates-range .mp-label { font-size: 9px; visibility: hidden; margin-bottom: 0; }
|
||||
.dates-range .dates-pair { font-size: 11px; color: var(--muted); line-height: 1.7; display: flex; flex-direction: column; justify-content: space-evenly; flex: 1; }
|
||||
.dates-range .dr-line { color: var(--faint); white-space: nowrap; }
|
||||
.dates-range .dr-line.bad { color: var(--bad); font-weight: 600; }
|
||||
.dates-kpi .kpi-n { font-size: 14px; font-weight: 800; line-height: 1; color: var(--faint); }
|
||||
.dates-kpi .kpi-n.bad { color: var(--bad); }
|
||||
.dates-kpi .kpi-u { font-size: 10px; color: var(--faint); line-height: 1.2; }
|
||||
.dates-kpi .kpi-delta { font-size: 10px; line-height: 1.2; }
|
||||
|
||||
/* ─── ВЫРАВНИВАНИЕ ГРИДОВ ─── */
|
||||
.board-head { gap: 6px !important; }
|
||||
|
||||
/* ─── РАЗМЕР ШРИФТА ШАПКИ: +30% (14px → 18px) ─── */
|
||||
.col-head { font-size: 18px !important; }
|
||||
|
||||
/* ─── ПАРНАЯ ШАПКА ─── */
|
||||
/* ПАРНАЯ ШАПКА: «Проектирование»/«Деньги» без линий, линии-брекеты под заголовком */
|
||||
.col-head.col-head-pair { flex-direction: column; justify-content: center; align-items: stretch; gap: 0; padding: 4px 14px 6px; }
|
||||
.col-head-roof { display: flex; align-items: center; justify-content: center; width: 100%; line-height: 1.1; }
|
||||
.col-head-roof-text { white-space: nowrap; font-size: 16px; font-weight: 700; color: #fff; padding: 0 4px; }
|
||||
/* под заголовком — просто прямая жёлтая линия */
|
||||
.col-head-bracket { height: 1px; margin: 4px 0 4px 25%; width: 50%; background: rgba(255,255,255,0.25); }
|
||||
.col-head.col-head-pair .col-head-pair-row { display: flex; width: 100%; gap: 0; align-items: center; justify-content: space-around; }
|
||||
.col-head.col-head-pair .col-head-half { flex: 1; text-align: center; font-size: 16px !important; font-weight: 700; opacity: 0.85; text-transform: none; letter-spacing: 0; line-height: 1.1; }
|
||||
.col-head.col-head-pair .col-head-half + .col-head-half { border-left: none; }
|
||||
|
||||
/* ─── TOPBAR: h1 и date-tag в одной строке ─── */
|
||||
.topbar-head { display: flex; align-items: center; gap: 16px; }
|
||||
.topbar-head h1 { margin: 0; }
|
||||
.topbar-head .date-tag { margin: 0; }
|
||||
|
||||
/* ─── КНОПКИ-РАЗДЕЛЫ (над шапкой) ─── */
|
||||
.sections-tabs { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
|
||||
.sec-tab { padding: 5px 12px; border: 1px solid var(--border); border-radius: 4px;
|
||||
font-size: 11px; font-weight: 600; letter-spacing: .04em; color: var(--muted);
|
||||
background: var(--surface, #11161d); cursor: default; user-select: none; }
|
||||
.sec-tab.active { background: rgba(90, 166, 224, 0.18); border-color: rgba(90,166,224,.55); color: #cfe5fa; }
|
||||
|
||||
/* ─── СТРОКА ФИЛЬТРОВ ─── */
|
||||
.board-filters { display: grid; grid-template-columns: var(--grid-cols); gap: var(--gap, 6px); margin-top: 4px; }
|
||||
.filter-cell { background: var(--surface, #0e131a); border: 1px solid var(--border); border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center; padding: 3px 6px; min-height: 21px; }
|
||||
.filter-cell.filter-pair { justify-content: space-around; }
|
||||
.filter-cell.filter-pair .filter-icon + .filter-icon { border-left: 1px solid var(--border); padding-left: 0; margin-left: 0; }
|
||||
.filter-cell.filter-pair .filter-icon { flex: 1; display: flex; align-items: center; justify-content: center; }
|
||||
.filter-icon { color: var(--faint); display: flex; align-items: center; justify-content: center; }
|
||||
.filter-icon:hover { color: var(--muted); }
|
||||
|
||||
/* ─── STICKY ШАПКА + ФИЛЬТРЫ, SCROLL ТОЛЬКО СПИСКА ─── */
|
||||
.board { display: flex; flex-direction: column; min-height: 0; }
|
||||
.board-head { position: sticky; top: 0; z-index: 3; background: var(--bg, #0a0d12); }
|
||||
.board-filters { position: sticky; top: 56px; z-index: 2; background: var(--bg, #0a0d12); padding-bottom: 6px; }
|
||||
.board-body { overflow-y: auto; max-height: calc(100vh - 240px); scrollbar-gutter: stable; }
|
||||
.board-body::-webkit-scrollbar { width: 8px; }
|
||||
.board-body::-webkit-scrollbar-thumb { background: rgba(255,255,255,.08); border-radius: 3px; }
|
||||
.board-body::-webkit-scrollbar-track { background: transparent; }
|
||||
/* head/filters резервируют такой же отступ справа, как в body */
|
||||
.board-head, .board-filters { padding-right: 8px; box-sizing: border-box; }
|
||||
|
||||
/* ─── СТАТУСЫ ПРОЕКТОВ (СТОП/ТЕК/НОВ) ─── */
|
||||
/* все бейджи (стадия + статус) в карточке — фиксированной ширины по самому широкому (СТОП) */
|
||||
.badge-stack { display: inline-flex; flex-direction: column; align-items: stretch; gap: 4px; width: 44px; opacity: 0.55; }
|
||||
.badge-stack .badge { width: 100%; box-sizing: border-box; text-align: center; font-size: 8px; padding: 3px 6px; border-radius: 6px; border-width: 1.5px; line-height: 1; }
|
||||
.badge-status { display: block; width: 100%; box-sizing: border-box; font-size: 8px; font-weight: 700; padding: 3px 6px; border-radius: 6px; text-align: center; line-height: 1; }
|
||||
.status-stop { background: rgba(232,90,90,0.15); border: 1.5px solid rgba(232,90,90,0.55); color: #e85a5a; }
|
||||
.status-new { background: rgba(92,192,160,0.15); border: 1.5px solid rgba(92,192,160,0.55); color: #5CC0A0; }
|
||||
.status-cur { background: rgba(160,180,200,0.12); border: 1.5px solid rgba(160,180,200,0.45); color: #a0b4c8; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<!-- ТОПБАР -->
|
||||
<div class="topbar">
|
||||
|
||||
<div class="topbar-head"><h1>Портфель проектов</h1><div class="date-tag"><span class="dot"></span> Данные на: 08.06.2026</div><div class="sections-tabs"><div class="sec-tab active">ВСЕ РАЗДЕЛЫ</div><div class="sec-tab">ГП</div><div class="sec-tab">АР</div><div class="sec-tab">КР</div><div class="sec-tab">ОВиК</div><div class="sec-tab">ВК</div><div class="sec-tab">ЭОМ</div><div class="sec-tab">ССиАК</div></div></div>
|
||||
</div>
|
||||
<div class="board">
|
||||
<div class="board-head"><div class="col-head">Проекты</div><div class="col-head">Команда</div><div class="col-head">Сводная</div><div class="col-head">Сроки</div><div class="col-head col-head-pair"><div class="col-head-roof"><span class="col-head-roof-text">Проектирование</span></div><div class="col-head-bracket"></div><div class="col-head-pair-row"><div class="col-head-half">готовность</div><div class="col-head-half">трудозатраты</div></div></div><div class="col-head col-head-pair"><div class="col-head-roof"><span class="col-head-roof-text">Деньги</span></div><div class="col-head-bracket"></div><div class="col-head-pair-row"><div class="col-head-half">поступления</div><div class="col-head-half">затраты</div></div></div></div><div class="board-filters"><div class="filter-cell"><div class="filter-icon"><svg fill="none" height="13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" viewbox="0 0 16 16" width="13"><path d="M2 3h12l-4.5 5.5V13l-3 1.5V8.5L2 3z"></path></svg></div></div><div class="filter-cell"><div class="filter-icon"><svg fill="none" height="13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" viewbox="0 0 16 16" width="13"><path d="M2 3h12l-4.5 5.5V13l-3 1.5V8.5L2 3z"></path></svg></div></div><div class="filter-cell"><div class="filter-icon"><svg fill="none" height="13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" viewbox="0 0 16 16" width="13"><path d="M2 3h12l-4.5 5.5V13l-3 1.5V8.5L2 3z"></path></svg></div></div><div class="filter-cell"><div class="filter-icon"><svg fill="none" height="13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" viewbox="0 0 16 16" width="13"><path d="M2 3h12l-4.5 5.5V13l-3 1.5V8.5L2 3z"></path></svg></div></div><div class="filter-cell filter-pair"><div class="filter-icon"><svg fill="none" height="13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" viewbox="0 0 16 16" width="13"><path d="M2 3h12l-4.5 5.5V13l-3 1.5V8.5L2 3z"></path></svg></div><div class="filter-icon"><svg fill="none" height="13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" viewbox="0 0 16 16" width="13"><path d="M2 3h12l-4.5 5.5V13l-3 1.5V8.5L2 3z"></path></svg></div></div><div class="filter-cell filter-pair"><div class="filter-icon"><svg fill="none" height="13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" viewbox="0 0 16 16" width="13"><path d="M2 3h12l-4.5 5.5V13l-3 1.5V8.5L2 3z"></path></svg></div><div class="filter-icon"><svg fill="none" height="13" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.6" viewbox="0 0 16 16" width="13"><path d="M2 3h12l-4.5 5.5V13l-3 1.5V8.5L2 3z"></path></svg></div></div></div>
|
||||
<div class="board-body">
|
||||
<!-- СТРОКА 1: ЖК Северный Парк — СРЫВ Трудозатраты + Сроки -->
|
||||
<div class="board-row health-bad stage-pd">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head">
|
||||
<div>
|
||||
<div class="proj-code">ШИФР <b>2025-0925</b></div>
|
||||
<div class="proj-name">ЖК Северный Парк</div>
|
||||
<div class="proj-dev">СЗ Альфа Девелопмент</div>
|
||||
<div class="proj-gip">ГИП: А. Иванов · 74,0 млн ₽</div>
|
||||
</div>
|
||||
<div class="badge-stack"><span class="badge badge-pd">ПД</span><span class="badge-status status-stop">СТОП</span></div>
|
||||
</div>
|
||||
<div class="proj-health">
|
||||
<span class="health-dot bad"></span>
|
||||
<span class="health-label">В работе · Перерасход</span>
|
||||
</div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="24.5 138.9" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">15%</text></svg><div class="team-total">34 чел.</div></div></div><div class="cell" style="padding:8px 8px;">
|
||||
<div class="summary-list">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap over-wrap"><div class="sbar-fill sbar-over"></div></div><span class="spct spct-over">160%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-skew" style="width:60%"></div></div><span class="spct spct-skew">60%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap over-wrap"><div class="sbar-fill sbar-over"></div></div><span class="spct spct-over">125%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:60%"></div></div><span class="spct">60%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:68%"></div></div><span class="spct">68%</span></div></div>
|
||||
</div><div class="cell dates-cell overflow-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2026.05.01</div><div class="dr-line bad">оконч.: 2026.07.01</div></div></div><div class="dates-kpi"><div class="kpi-label" style="color:var(--bad);font-weight:700;font-size:10px;letter-spacing:.08em;">СРЫВ</div><div class="kpi-n bad">150</div><div class="kpi-u">дней</div><div class="kpi-delta">+60 дн</div></div></div><div class="bar-row"><div class="bar bar-overflow"><div class="fill"></div></div><span class="bar-pct bar-pct-over">160%</span></div></div><div class="cell pair-cell overflow-cell skew-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair">
|
||||
<div class="mp-label">план / факт</div>
|
||||
<div class="mp-plan">100%</div>
|
||||
<div class="mp-fact">60%</div>
|
||||
</div>
|
||||
<div class="metric-kpi">
|
||||
<div class="kpi-label">остаток</div>
|
||||
<div class="kpi-n">40</div>
|
||||
<div class="kpi-u">%</div>
|
||||
</div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:60%;background:rgba(255,160,50,0.75);"></div></div><span class="bar-pct bar-pct-skew">60%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair">
|
||||
<div class="mp-label">план / факт</div>
|
||||
<div class="mp-plan">1 200 ч/ч</div>
|
||||
<div class="mp-fact bad">1 500 ч/ч</div>
|
||||
</div>
|
||||
<div class="metric-kpi">
|
||||
<div class="kpi-label" style="color:var(--bad);font-weight:700;font-size:10px;letter-spacing:.08em;">СРЫВ</div>
|
||||
<div class="kpi-n bad">1875</div>
|
||||
<div class="kpi-u">ч/ч</div>
|
||||
<div class="kpi-delta">+300 ч/ч</div>
|
||||
</div>
|
||||
</div><div class="bar-row"><div class="bar bar-overflow"><div class="fill"></div></div><span class="bar-pct bar-pct-over">125%</span></div></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair">
|
||||
<div class="mp-label">план / факт</div>
|
||||
<div class="mp-plan">1,2 млн ₽</div>
|
||||
<div class="mp-fact">0,7 млн ₽</div>
|
||||
</div>
|
||||
<div class="metric-kpi">
|
||||
<div class="kpi-label">остаток</div>
|
||||
<div class="kpi-n">0,5</div>
|
||||
<div class="kpi-u">млн</div>
|
||||
</div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:60%"></div></div><span class="bar-pct">60%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair">
|
||||
<div class="mp-label">план / факт</div>
|
||||
<div class="mp-plan">1,0 млн ₽</div>
|
||||
<div class="mp-fact">0,68 млн ₽</div>
|
||||
</div>
|
||||
<div class="metric-kpi">
|
||||
<div class="kpi-label">остаток</div>
|
||||
<div class="kpi-n">0,32</div>
|
||||
<div class="kpi-u">млн</div>
|
||||
</div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:68%"></div></div><span class="bar-pct">68%</span></div></div></div></div>
|
||||
<!-- СТРОКА 2: ЖК Север — СРЫВ Затраты 138% -->
|
||||
<div class="board-row health-bad stage-pd">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head">
|
||||
<div>
|
||||
<div class="proj-code">ШИФР <b>2023-0011</b></div>
|
||||
<div class="proj-name">ЖК Север</div>
|
||||
<div class="proj-dev">Град Инвест</div>
|
||||
<div class="proj-gip">ГИП: И. Смирнов · 58,0 млн ₽</div>
|
||||
</div>
|
||||
<div class="badge-stack"><span class="badge badge-pd">ПД</span><span class="badge-status status-cur">ТЕК</span></div>
|
||||
</div>
|
||||
<div class="proj-health">
|
||||
<span class="health-dot bad"></span>
|
||||
<span class="health-label">В работе · Перерасход затрат</span>
|
||||
</div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="16.3 147.0" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">10%</text></svg><div class="team-total">24 чел.</div></div></div><div class="cell" style="padding:8px 8px;">
|
||||
<div class="summary-list">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:68%"></div></div><span class="spct">68%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:66%"></div></div><span class="spct">66%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:63%"></div></div><span class="spct">63%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-skew" style="width:57%"></div></div><span class="spct spct-skew">57%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap over-wrap"><div class="sbar-fill sbar-over"></div></div><span class="spct spct-over">138%</span></div></div>
|
||||
</div><div class="cell dates-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2026.01.10</div><div class="dr-line">оконч.: 2026.04.25</div></div></div><div class="dates-kpi"><div class="kpi-label">осталось</div><div class="kpi-n">41</div><div class="kpi-u">дня</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:68%"></div></div><span class="bar-pct">68%</span></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">100%</div><div class="mp-fact">66%</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">34</div><div class="kpi-u">%</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:66%"></div></div><span class="bar-pct">66%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">1 750 ч/ч</div><div class="mp-fact">1 100 ч/ч</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">640</div><div class="kpi-u">ч/ч</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:63%"></div></div><span class="bar-pct">63%</span></div></div></div><div class="cell pair-cell overflow-cell skew-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">2,1 млн ₽</div><div class="mp-fact">1,2 млн ₽</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">0,9</div><div class="kpi-u">млн</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:57%;background:rgba(255,160,50,0.75);"></div></div><span class="bar-pct bar-pct-skew">57%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair">
|
||||
<div class="mp-label">план / факт</div>
|
||||
<div class="mp-plan">1,8 млн ₽</div>
|
||||
<div class="mp-fact bad">2,5 млн ₽</div>
|
||||
</div>
|
||||
<div class="metric-kpi">
|
||||
<div class="kpi-label" style="color:var(--bad);font-weight:700;font-size:10px;letter-spacing:.08em;">СРЫВ</div>
|
||||
<div class="kpi-n bad">3,4</div>
|
||||
<div class="kpi-u">млн ₽</div>
|
||||
<div class="kpi-delta">+0,7 млн</div>
|
||||
</div>
|
||||
</div><div class="bar-row"><div class="bar bar-overflow"><div class="fill"></div></div><span class="bar-pct bar-pct-over">138%</span></div></div></div></div>
|
||||
<!-- СТРОКА 3: ЖК Юг — норма -->
|
||||
<div class="board-row health-ok stage-rd">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head">
|
||||
<div>
|
||||
<div class="proj-code">ШИФР <b>2023-0014</b></div>
|
||||
<div class="proj-name">ЖК Юг</div>
|
||||
<div class="proj-dev">Сити Девелопмент</div>
|
||||
<div class="proj-gip">ГИП: М. Волков · 82,0 млн ₽</div>
|
||||
</div>
|
||||
<div class="badge-stack"><span class="badge badge-rd">РД</span><span class="badge-status status-new">НОВ</span></div>
|
||||
</div>
|
||||
<div class="proj-health">
|
||||
<span class="health-dot ok"></span>
|
||||
<span class="health-label">В работе · 72%</span>
|
||||
</div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="32.7 130.7" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">20%</text></svg><div class="team-total">40 чел.</div></div></div><div class="cell" style="padding:8px 8px;">
|
||||
<div class="summary-list">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:72%"></div></div><span class="spct">72%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:72%"></div></div><span class="spct">72%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:72%"></div></div><span class="spct">72%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:72%"></div></div><span class="spct">72%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:68%"></div></div><span class="spct">68%</span></div></div>
|
||||
</div><div class="cell dates-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2026.03.15</div><div class="dr-line">оконч.: 2026.07.30</div></div></div><div class="dates-kpi"><div class="kpi-label">осталось</div><div class="kpi-n">48</div><div class="kpi-u">дня</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:72%"></div></div><span class="bar-pct">72%</span></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">100%</div><div class="mp-fact">72%</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">28</div><div class="kpi-u">%</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:72%"></div></div><span class="bar-pct">72%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">2 100 ч/ч</div><div class="mp-fact">1 520 ч/ч</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">580</div><div class="kpi-u">ч/ч</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:72%"></div></div><span class="bar-pct">72%</span></div></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">2,8 млн ₽</div><div class="mp-fact">2,0 млн ₽</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">0,8</div><div class="kpi-u">млн</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:72%"></div></div><span class="bar-pct">72%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">2,2 млн ₽</div><div class="mp-fact">1,5 млн ₽</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">0,7</div><div class="kpi-u">млн</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:68%"></div></div><span class="bar-pct">68%</span></div></div></div></div>
|
||||
<!-- СТРОКА 4: БЦ Zorge — ПЕРЕКОС: трудозатраты опережают готовность на 25% -->
|
||||
<div class="board-row health-warn stage-kk">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head">
|
||||
<div>
|
||||
<div class="proj-code">ШИФР <b>2025-0013-ПД</b></div>
|
||||
<div class="proj-name">БЦ Zorge</div>
|
||||
<div class="proj-dev">Форсайт</div>
|
||||
<div class="proj-gip">ГИП: Е. Петрова · 31,0 млн ₽</div>
|
||||
</div>
|
||||
<div class="badge-stack"><span class="badge badge-kk">КК</span><span class="badge-status status-cur">ТЕК</span></div>
|
||||
</div>
|
||||
<div class="proj-health">
|
||||
<span class="health-dot warn"></span>
|
||||
<span class="health-label">В работе · 35%</span>
|
||||
</div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="19.6 143.8" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">12%</text></svg><div class="team-total">17 чел.</div></div></div><div class="cell" style="padding:8px 8px;">
|
||||
<div class="summary-list">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:55%"></div></div><span class="spct">55%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-skew" style="width:35%"></div></div><span class="spct spct-skew">35%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap"><div class="sbar-fill sbar-skew" style="width:60%"></div></div><span class="spct spct-skew">60%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:25%"></div></div><span class="spct">25%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:38%"></div></div><span class="spct">38%</span></div></div>
|
||||
</div><div class="cell dates-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2026.04.01</div><div class="dr-line">оконч.: 2026.09.30</div></div></div><div class="dates-kpi"><div class="kpi-label">осталось</div><div class="kpi-n">114</div><div class="kpi-u">дней</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:55%"></div></div><span class="bar-pct">55%</span></div></div><div class="cell pair-cell skew-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">100%</div><div class="mp-fact">35%</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">65</div><div class="kpi-u">%</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:35%;background:rgba(255,160,50,0.75);"></div></div><span class="bar-pct bar-pct-skew">35%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">480 ч/ч</div><div class="mp-fact">290 ч/ч</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">190</div><div class="kpi-u">ч/ч</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:60%;background:rgba(255,160,50,0.75);"></div></div><span class="bar-pct bar-pct-skew">60%</span></div></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">0,6 млн ₽</div><div class="mp-fact">0,15 млн ₽</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">0,45</div><div class="kpi-u">млн</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:25%"></div></div><span class="bar-pct">25%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">0,55 млн ₽</div><div class="mp-fact">0,21 млн ₽</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">0,34</div><div class="kpi-u">млн</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:38%"></div></div><span class="bar-pct">38%</span></div></div></div></div>
|
||||
<!-- СТРОКА 5: ЖК Большой Палашевский ЭТАП 1 -->
|
||||
<div class="board-row health-warn stage-rd">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head">
|
||||
<div>
|
||||
<div class="proj-code">ШИФР <b>2024-0001</b></div>
|
||||
<div class="proj-name">ЖК Большой Палашевский ЭТАП 1</div>
|
||||
<div class="proj-dev">Sminex</div>
|
||||
<div class="proj-gip">ГИП: С. Орлов · 83,8 млн ₽</div>
|
||||
</div>
|
||||
<div class="badge-stack"><span class="badge badge-rd">РД</span><span class="badge-status status-stop">СТОП</span></div>
|
||||
</div>
|
||||
<div class="proj-health">
|
||||
<span class="health-dot warn"></span>
|
||||
<span class="health-label">В работе · 52%</span>
|
||||
</div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="29.4 134.0" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">18%</text></svg><div class="team-total">25 чел.</div></div></div><div class="cell" style="padding:8px 8px;">
|
||||
<div class="summary-list">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:60%"></div></div><span class="spct">60%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:52%"></div></div><span class="spct">52%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:58%"></div></div><span class="spct">58%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:50%"></div></div><span class="spct">50%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:54%"></div></div><span class="spct">54%</span></div></div>
|
||||
</div><div class="cell dates-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2025.11.01</div><div class="dr-line">оконч.: 2026.08.15</div></div></div><div class="dates-kpi"><div class="kpi-label">осталось</div><div class="kpi-n">68</div><div class="kpi-u">дней</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:60%"></div></div><span class="bar-pct">60%</span></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">100%</div><div class="mp-fact">52%</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">48</div><div class="kpi-u">%</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:52%"></div></div><span class="bar-pct">52%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">1 640 ч/ч</div><div class="mp-fact">950 ч/ч</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">690</div><div class="kpi-u">ч/ч</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:58%"></div></div><span class="bar-pct">58%</span></div></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">3,2 млн ₽</div><div class="mp-fact">1,6 млн ₽</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">1,6</div><div class="kpi-u">млн</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:50%"></div></div><span class="bar-pct">50%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">2,9 млн ₽</div><div class="mp-fact">1,57 млн ₽</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">1,33</div><div class="kpi-u">млн</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:54%"></div></div><span class="bar-pct">54%</span></div></div></div></div>
|
||||
<!-- СТРОКА 6: ТПУ Восток -->
|
||||
<div class="board-row health-warn stage-rd">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head">
|
||||
<div>
|
||||
<div class="proj-code">ШИФР <b>2022-0020</b></div>
|
||||
<div class="proj-name">ТПУ Восток</div>
|
||||
<div class="proj-dev">Урбан Система</div>
|
||||
<div class="proj-gip">ГИП: В. Лапин · 44,0 млн ₽</div>
|
||||
</div>
|
||||
<div class="badge-stack"><span class="badge badge-rd">РД</span><span class="badge-status status-new">НОВ</span></div>
|
||||
</div>
|
||||
<div class="proj-health">
|
||||
<span class="health-dot warn"></span>
|
||||
<span class="health-label">В работе · 61%</span>
|
||||
</div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="35.9 127.4" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">22%</text></svg><div class="team-total">34 чел.</div></div></div><div class="cell" style="padding:8px 8px;">
|
||||
<div class="summary-list">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:70%"></div></div><span class="spct">70%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:61%"></div></div><span class="spct">61%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:65%"></div></div><span class="spct">65%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-skew" style="width:35%"></div></div><span class="spct spct-skew">35%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap"><div class="sbar-fill sbar-skew" style="width:62%"></div></div><span class="spct spct-skew">62%</span></div></div>
|
||||
</div><div class="cell dates-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2025.09.01</div><div class="dr-line">оконч.: 2026.06.30</div></div></div><div class="dates-kpi"><div class="kpi-label">осталось</div><div class="kpi-n">22</div><div class="kpi-u">дня</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:70%"></div></div><span class="bar-pct">70%</span></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">100%</div><div class="mp-fact">61%</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">39</div><div class="kpi-u">%</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:61%"></div></div><span class="bar-pct">61%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">900 ч/ч</div><div class="mp-fact">585 ч/ч</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">315</div><div class="kpi-u">ч/ч</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:65%"></div></div><span class="bar-pct">65%</span></div></div></div><div class="cell pair-cell skew-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">1,5 млн ₽</div><div class="mp-fact">0,53 млн ₽</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">0,97</div><div class="kpi-u">млн</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:35%;background:rgba(255,160,50,0.75);"></div></div><span class="bar-pct bar-pct-skew">35%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">1,3 млн ₽</div><div class="mp-fact">0,81 млн ₽</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">0,49</div><div class="kpi-u">млн</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:62%;background:rgba(255,160,50,0.75);"></div></div><span class="bar-pct bar-pct-skew">62%</span></div></div></div></div>
|
||||
<!-- СТРОКА 7: БЦ Meridian — СРЫВ Затраты 118% -->
|
||||
<div class="board-row health-bad stage-rd">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head">
|
||||
<div>
|
||||
<div class="proj-code">ШИФР <b>2024-0010</b></div>
|
||||
<div class="proj-name">БЦ Meridian</div>
|
||||
<div class="proj-dev">North Capital</div>
|
||||
<div class="proj-gip">ГИП: Н. Соколов · 65,0 млн ₽</div>
|
||||
</div>
|
||||
<div class="badge-stack"><span class="badge badge-rd">РД</span><span class="badge-status status-cur">ТЕК</span></div>
|
||||
</div>
|
||||
<div class="proj-health">
|
||||
<span class="health-dot bad"></span>
|
||||
<span class="health-label">В работе · Перерасход затрат</span>
|
||||
</div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="26.1 137.2" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">16%</text></svg><div class="team-total">23 чел.</div></div></div><div class="cell" style="padding:8px 8px;">
|
||||
<div class="summary-list">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:62%"></div></div><span class="spct">62%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:58%"></div></div><span class="spct">58%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:65%"></div></div><span class="spct">65%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-skew" style="width:52%"></div></div><span class="spct spct-skew">52%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap over-wrap"><div class="sbar-fill sbar-over"></div></div><span class="spct spct-over">118%</span></div></div>
|
||||
</div><div class="cell dates-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2025.12.01</div><div class="dr-line">оконч.: 2026.09.01</div></div></div><div class="dates-kpi"><div class="kpi-label">осталось</div><div class="kpi-n">85</div><div class="kpi-u">дней</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:62%"></div></div><span class="bar-pct">62%</span></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">100%</div><div class="mp-fact">58%</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">42</div><div class="kpi-u">%</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:58%"></div></div><span class="bar-pct">58%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">1 200 ч/ч</div><div class="mp-fact">780 ч/ч</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">420</div><div class="kpi-u">ч/ч</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:65%"></div></div><span class="bar-pct">65%</span></div></div></div><div class="cell pair-cell overflow-cell skew-cell"><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">2,4 млн ₽</div><div class="mp-fact">1,25 млн ₽</div></div>
|
||||
<div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">1,15</div><div class="kpi-u">млн</div></div>
|
||||
</div><div class="bar-row"><div class="bar"><div class="fill" style="width:52%;background:rgba(255,160,50,0.75);"></div></div><span class="bar-pct bar-pct-skew">52%</span></div></div><div class="pair-half"><div class="metric-top">
|
||||
<div class="metric-pair">
|
||||
<div class="mp-label">план / факт</div>
|
||||
<div class="mp-plan">2,0 млн ₽</div>
|
||||
<div class="mp-fact bad">2,36 млн ₽</div>
|
||||
</div>
|
||||
<div class="metric-kpi">
|
||||
<div class="kpi-label" style="color:var(--bad);font-weight:700;font-size:10px;letter-spacing:.08em;">СРЫВ</div>
|
||||
<div class="kpi-n bad">2,36</div>
|
||||
<div class="kpi-u">млн ₽</div>
|
||||
<div class="kpi-delta">+0,36 млн</div>
|
||||
</div>
|
||||
</div><div class="bar-row"><div class="bar bar-overflow"><div class="fill"></div></div><span class="bar-pct bar-pct-over">118%</span></div></div></div></div>
|
||||
<!-- СТРОКА 8: ЖК Речной — норма -->
|
||||
<div class="board-row health-ok stage-rd">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head"><div><div class="proj-code">ШИФР <b>2024-0015</b></div><div class="proj-name">ЖК Речной</div><div class="proj-dev">Речные Берега</div><div class="proj-gip">ГИП: А. Фёдоров · 91,0 млн ₽</div></div><div class="badge-stack"><span class="badge badge-rd">РД</span><span class="badge-status status-cur">ТЕК</span></div></div>
|
||||
<div class="proj-health"><span class="health-dot ok"></span><span class="health-label">В работе · 78%</span></div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="29.4 134.0" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">18%</text></svg><div class="team-total">29 чел.</div></div></div><div class="cell" style="padding:8px 8px;"><div class="summary-list"><div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:80%"></div></div><span class="spct">80%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:78%"></div></div><span class="spct">78%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:75%"></div></div><span class="spct">75%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:76%"></div></div><span class="spct">76%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:74%"></div></div><span class="spct">74%</span></div></div></div><div class="cell dates-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2025.06.01</div><div class="dr-line">оконч.: 2026.10.15</div></div></div><div class="dates-kpi"><div class="kpi-label">осталось</div><div class="kpi-n">127</div><div class="kpi-u">дней</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:80%"></div></div><span class="bar-pct">80%</span></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">100%</div><div class="mp-fact">78%</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">22</div><div class="kpi-u">%</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:78%"></div></div><span class="bar-pct">78%</span></div></div><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">2 400 ч/ч</div><div class="mp-fact">1 800 ч/ч</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">600</div><div class="kpi-u">ч/ч</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:75%"></div></div><span class="bar-pct">75%</span></div></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">4,5 млн ₽</div><div class="mp-fact">3,4 млн ₽</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">1,1</div><div class="kpi-u">млн</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:76%"></div></div><span class="bar-pct">76%</span></div></div><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">3,8 млн ₽</div><div class="mp-fact">2,8 млн ₽</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">1,0</div><div class="kpi-u">млн</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:74%"></div></div><span class="bar-pct">74%</span></div></div></div></div>
|
||||
<!-- СТРОКА 9: ЖК Западный — перекос поступл/затраты -->
|
||||
<div class="board-row health-warn stage-pd">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head"><div><div class="proj-code">ШИФР <b>2023-0031</b></div><div class="proj-name">ЖК Западный</div><div class="proj-dev">Запад Девелопмент</div><div class="proj-gip">ГИП: О. Белова · 54,0 млн ₽</div></div><div class="badge-stack"><span class="badge badge-pd">ПД</span><span class="badge-status status-stop">СТОП</span></div></div>
|
||||
<div class="proj-health"><span class="health-dot warn"></span><span class="health-label">В работе · Перерасход затрат</span></div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="22.6 140.7" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">14%</text></svg><div class="team-total">23 чел.</div></div></div><div class="cell" style="padding:8px 8px;"><div class="summary-list"><div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:52%"></div></div><span class="spct">52%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:55%"></div></div><span class="spct">55%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:58%"></div></div><span class="spct">58%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-skew" style="width:42%"></div></div><span class="spct spct-skew">42%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap over-wrap"><div class="sbar-fill sbar-over"></div></div><span class="spct spct-over">112%</span></div></div></div><div class="cell dates-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2025.03.01</div><div class="dr-line">оконч.: 2026.12.01</div></div></div><div class="dates-kpi"><div class="kpi-label">осталось</div><div class="kpi-n">174</div><div class="kpi-u">дней</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:52%"></div></div><span class="bar-pct">52%</span></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">100%</div><div class="mp-fact">55%</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">45</div><div class="kpi-u">%</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:55%"></div></div><span class="bar-pct">55%</span></div></div><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">1 900 ч/ч</div><div class="mp-fact">1 100 ч/ч</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">800</div><div class="kpi-u">ч/ч</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:58%"></div></div><span class="bar-pct">58%</span></div></div></div><div class="cell pair-cell overflow-cell skew-cell"><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">3,1 млн ₽</div><div class="mp-fact">1,3 млн ₽</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">1,8</div><div class="kpi-u">млн</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:42%;background:rgba(255,160,50,0.75);"></div></div><span class="bar-pct bar-pct-skew">42%</span></div></div><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">2,5 млн ₽</div><div class="mp-fact bad">2,8 млн ₽</div></div><div class="metric-kpi"><div class="kpi-label" style="color:var(--bad);font-weight:700;font-size:10px;">СРЫВ</div><div class="kpi-n bad">2,8</div><div class="kpi-u">млн ₽</div><div class="kpi-delta">+0,3 млн</div></div></div><div class="bar-row"><div class="bar bar-overflow"><div class="fill"></div></div><span class="bar-pct bar-pct-over">112%</span></div></div></div></div>
|
||||
<!-- СТРОКА 10: МФЦ Центральный — КК ранняя стадия -->
|
||||
<div class="board-row health-ok stage-kk">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head"><div><div class="proj-code">ШИФР <b>2025-0044</b></div><div class="proj-name">МФЦ Центральный</div><div class="proj-dev">ЦентрСтрой</div><div class="proj-gip">ГИП: В. Кузнецов · 38,0 млн ₽</div></div><div class="badge-stack"><span class="badge badge-kk">КК</span><span class="badge-status status-new">НОВ</span></div></div>
|
||||
<div class="proj-health"><span class="health-dot ok"></span><span class="health-label">В работе · 30%</span></div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="14.5 148.8" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">9%</text></svg><div class="team-total">15 чел.</div></div></div><div class="cell" style="padding:8px 8px;"><div class="summary-list"><div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:28%"></div></div><span class="spct">28%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:30%"></div></div><span class="spct">30%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:32%"></div></div><span class="spct">32%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:30%"></div></div><span class="spct">30%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:29%"></div></div><span class="spct">29%</span></div></div></div><div class="cell dates-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2026.01.15</div><div class="dr-line">оконч.: 2027.03.01</div></div></div><div class="dates-kpi"><div class="kpi-label">осталось</div><div class="kpi-n">293</div><div class="kpi-u">дней</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:28%"></div></div><span class="bar-pct">28%</span></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">100%</div><div class="mp-fact">30%</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">70</div><div class="kpi-u">%</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:30%"></div></div><span class="bar-pct">30%</span></div></div><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">980 ч/ч</div><div class="mp-fact">314 ч/ч</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">666</div><div class="kpi-u">ч/ч</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:32%"></div></div><span class="bar-pct">32%</span></div></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">1,8 млн ₽</div><div class="mp-fact">0,54 млн ₽</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">1,26</div><div class="kpi-u">млн</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:30%"></div></div><span class="bar-pct">30%</span></div></div><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">1,5 млн ₽</div><div class="mp-fact">0,44 млн ₽</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">1,06</div><div class="kpi-u">млн</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:29%"></div></div><span class="bar-pct">29%</span></div></div></div></div>
|
||||
<!-- СТРОКА 11: ЖК Парковый — СРЫВ трудозатраты -->
|
||||
<div class="board-row health-bad stage-pd">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head"><div><div class="proj-code">ШИФР <b>2023-0019</b></div><div class="proj-name">ЖК Парковый</div><div class="proj-dev">ПаркСити</div><div class="proj-gip">ГИП: Д. Миронов · 67,5 млн ₽</div></div><div class="badge-stack"><span class="badge badge-pd">ПД</span><span class="badge-status status-cur">ТЕК</span></div></div>
|
||||
<div class="proj-health"><span class="health-dot bad"></span><span class="health-label">В работе · СРЫВ трудозатрат</span></div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="34.3 129.0" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">21%</text></svg><div class="team-total">36 чел.</div></div></div><div class="cell" style="padding:8px 8px;"><div class="summary-list"><div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:71%"></div></div><span class="spct">71%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-skew" style="width:48%"></div></div><span class="spct spct-skew">48%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap over-wrap"><div class="sbar-fill sbar-over"></div></div><span class="spct spct-over">131%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:50%"></div></div><span class="spct">50%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:55%"></div></div><span class="spct">55%</span></div></div></div><div class="cell dates-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2024.09.01</div><div class="dr-line">оконч.: 2026.11.30</div></div></div><div class="dates-kpi"><div class="kpi-label">осталось</div><div class="kpi-n">172</div><div class="kpi-u">дней</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:71%"></div></div><span class="bar-pct">71%</span></div></div><div class="cell pair-cell overflow-cell skew-cell"><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">100%</div><div class="mp-fact">48%</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">52</div><div class="kpi-u">%</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:48%;background:rgba(255,160,50,0.75);"></div></div><span class="bar-pct bar-pct-skew">48%</span></div></div><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">1 600 ч/ч</div><div class="mp-fact bad">2 096 ч/ч</div></div><div class="metric-kpi"><div class="kpi-label" style="color:var(--bad);font-weight:700;font-size:10px;">СРЫВ</div><div class="kpi-n bad">2 096</div><div class="kpi-u">ч/ч</div><div class="kpi-delta">+496 ч/ч</div></div></div><div class="bar-row"><div class="bar bar-overflow"><div class="fill"></div></div><span class="bar-pct bar-pct-over">131%</span></div></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">3,2 млн ₽</div><div class="mp-fact">1,6 млн ₽</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">1,6</div><div class="kpi-u">млн</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:50%"></div></div><span class="bar-pct">50%</span></div></div><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">2,8 млн ₽</div><div class="mp-fact">1,54 млн ₽</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">1,26</div><div class="kpi-u">млн</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:55%"></div></div><span class="bar-pct">55%</span></div></div></div></div>
|
||||
<!-- СТРОКА 12: ТЦ Галактика — норма -->
|
||||
<div class="board-row health-ok stage-kk">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="cell proj-cell">
|
||||
<div class="proj-head"><div><div class="proj-code">ШИФР <b>2025-0051</b></div><div class="proj-name">ТЦ Галактика</div><div class="proj-dev">GalaxyRetail</div><div class="proj-gip">ГИП: Е. Романова · 120,0 млн ₽</div></div><div class="badge-stack"><span class="badge badge-kk">КК</span><span class="badge-status status-cur">ТЕК</span></div></div>
|
||||
<div class="proj-health"><span class="health-dot ok"></span><span class="health-label">В работе · 22%</span></div>
|
||||
</div><div class="cell team-cell"><div class="team-donut-wrap"><svg height="66" viewbox="0 0 66 66" width="66"><circle cx="33" cy="33" fill="none" r="26" stroke="rgba(120,160,200,0.12)" stroke-width="5"></circle><circle cx="33" cy="33" fill="none" r="26" stroke="#5aa6e0" stroke-dasharray="36.1 127.2" stroke-dashoffset="40.8" stroke-linecap="round" stroke-width="5"></circle><text fill="#5aa6e0" font-family="Inter,Arial,sans-serif" font-size="15" font-weight="700" text-anchor="middle" x="33" y="38">23%</text></svg><div class="team-total">46 чел.</div></div></div><div class="cell" style="padding:8px 8px;"><div class="summary-list"><div class="srow"><span class="slabel">сроки</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:20%"></div></div><span class="spct">20%</span></div><div class="srow"><span class="slabel">готовность</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:22%"></div></div><span class="spct">22%</span></div><div class="srow"><span class="slabel">трудозат</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:24%"></div></div><span class="spct">24%</span></div><div class="srow"><span class="slabel">поступл.</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:22%"></div></div><span class="spct">22%</span></div><div class="srow"><span class="slabel">затраты</span><div class="sbar-wrap"><div class="sbar-fill sbar-neutral" style="width:21%"></div></div><span class="spct">21%</span></div></div></div><div class="cell dates-cell"><div class="dates-top"><div class="dates-range"><div class="mp-label"> </div><div class="dates-pair"><div class="dr-line">начало: 2026.02.01</div><div class="dr-line">оконч.: 2027.08.01</div></div></div><div class="dates-kpi"><div class="kpi-label">осталось</div><div class="kpi-n">416</div><div class="kpi-u">дней</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:20%"></div></div><span class="bar-pct">20%</span></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">100%</div><div class="mp-fact">22%</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">78</div><div class="kpi-u">%</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:22%"></div></div><span class="bar-pct">22%</span></div></div><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">5 200 ч/ч</div><div class="mp-fact">1 248 ч/ч</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">3 952</div><div class="kpi-u">ч/ч</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:24%"></div></div><span class="bar-pct">24%</span></div></div></div><div class="cell pair-cell"><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">6,0 млн ₽</div><div class="mp-fact">1,32 млн ₽</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">4,68</div><div class="kpi-u">млн</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:22%"></div></div><span class="bar-pct">22%</span></div></div><div class="pair-half"><div class="metric-top"><div class="metric-pair"><div class="mp-label">план / факт</div><div class="mp-plan">5,2 млн ₽</div><div class="mp-fact">1,09 млн ₽</div></div><div class="metric-kpi"><div class="kpi-label">остаток</div><div class="kpi-n">4,11</div><div class="kpi-u">млн</div></div></div><div class="bar-row"><div class="bar"><div class="fill" style="width:21%"></div></div><span class="bar-pct">21%</span></div></div></div></div>
|
||||
</div><!-- /.board-body -->
|
||||
</div><!-- /.board -->
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
539
Task/Управление проектами/ДАШБОРД-3-v2-АР.html
Normal file
539
Task/Управление проектами/ДАШБОРД-3-v2.html
Normal file
539
Task/Управление проектами/ДАШБОРД-4-v1.html
Normal file
13
config.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?
|
||||
|
||||
define("MYSQL_HOST", "db");
|
||||
define("MYSQL_BASE", "j7508239_tracker");
|
||||
define("MYSQL_ADMIN", "root");
|
||||
define("MYSQL_PASSWORD","Q#Y34KsmCfy*");
|
||||
define("MYSQL_SSL_CERTIFICATE","");
|
||||
|
||||
define("LICENSE_KEY", "victor");
|
||||
define("LANG", "ru");
|
||||
define("SALT", "I&^#(*@Y(*#YLKJLASDASD");
|
||||
define("ADMIN_PAGE", "admin");
|
||||
|
||||
15
content/admin/blocks/content.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?
|
||||
|
||||
use ms\ms\structure\ePages as Pages;
|
||||
|
||||
if(IF_AUTH){
|
||||
include_once(dirname(__FILE__).'/../jade/index.php');
|
||||
} else {
|
||||
switch (Site::$URL[0]){
|
||||
case Pages::REG : include_once(dirname(__FILE__).'/../jade/profile/reg.php'); break;
|
||||
case Pages::REMIND : include_once(dirname(__FILE__).'/../jade/profile/remind.php'); break;
|
||||
case Pages::AUTH :
|
||||
default : include_once(dirname(__FILE__).'/../jade/profile/auth.php'); break;
|
||||
}
|
||||
|
||||
}
|
||||
0
content/admin/blocks/footer.php
Normal file
0
content/admin/blocks/toper.php
Normal file
1
content/admin/css/style.css
Normal file
114
content/admin/index.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?
|
||||
|
||||
use core\site\structure\HTMLPage as Page;
|
||||
use ms\ms;
|
||||
use shop\category\Relation;
|
||||
|
||||
if($_GET['test']) {
|
||||
/*
|
||||
\shop\category\Relation::delete();
|
||||
\shop\category\Relation::setParent(1);
|
||||
\shop\category\Relation::setParent(2,1);
|
||||
\shop\category\Relation::setParent(3,2);
|
||||
\shop\category\Relation::setParent(4,2);
|
||||
\shop\category\Relation::setParent(5,4);
|
||||
\shop\category\Relation::setParent(6);
|
||||
\shop\category\Relation::setParent(2,6);
|
||||
/**/
|
||||
exit;
|
||||
}
|
||||
if($_GET['category_relation']) {
|
||||
set_time_limit(0);
|
||||
$a = \shop\Category::getAll(true);
|
||||
foreach ($a as $v){
|
||||
echo '<pre>';
|
||||
echo var_dump($v);
|
||||
echo '</pre>';
|
||||
Relation::setParent($v[\shop\Category::$ID],$v[\shop\Category::$PARENT]);
|
||||
}
|
||||
exit;
|
||||
}
|
||||
if($_GET['install']) {
|
||||
\Core::install();
|
||||
exit;
|
||||
}
|
||||
if($_GET['lang']) {
|
||||
Ms::reinstallLang();
|
||||
exit;
|
||||
}
|
||||
if($_GET['unique']) {
|
||||
Ms::installUniques();
|
||||
exit;
|
||||
}
|
||||
if($_GET['migrate']) {
|
||||
//*
|
||||
// Показывать ли все ошибки
|
||||
error_reporting(E_ERROR);
|
||||
ini_set('display_errors',true);
|
||||
/*/
|
||||
ini_set('display_errors','Off');
|
||||
/**/
|
||||
set_time_limit(0);
|
||||
Ms::createTables();
|
||||
Ms::checkColumns(true);
|
||||
Ms::reinstallLang();
|
||||
\core\core\structure\Install::installCoreLang();
|
||||
//*
|
||||
echo '<pre>';
|
||||
echo var_dump(\DB::$logs);
|
||||
echo '</pre>';
|
||||
/**/
|
||||
exit;
|
||||
}
|
||||
|
||||
MS::init();
|
||||
|
||||
$PAGE = new \core\site\structure\HTMLPage;
|
||||
|
||||
if(Site::$justContent) {
|
||||
ob_start();
|
||||
include_once('blocks/content.php');
|
||||
Page::addBodyText(ob_get_clean());
|
||||
$res = new stdClass();
|
||||
$res->content = Page::getBodyContent();
|
||||
$res->title = Page::getTitleText();
|
||||
$res->data = Page::$data;
|
||||
$res->time_compile = microtime(true)-$GLOBALS['start_engine'];
|
||||
$res->time_bd = \DB::$total_time;
|
||||
echo je($res);
|
||||
|
||||
} else {
|
||||
PAGE::addStyle (Site::$themeAbsPath."css/style.css");
|
||||
PAGE::addScript("/lib/js/fastdom.js");
|
||||
PAGE::addScript("/lib/js/jquery-3.3.1.min.js");
|
||||
Page::addScript('/lib/js/bws/ui.js');
|
||||
Page::addStyle('/lib/js/bws/ui.css');
|
||||
Page::addScript('/lib/js/bws/form.js');
|
||||
Page::addStyle('/lib/js/perfect-scrollbar/css/perfect-scrollbar.css');
|
||||
Page::addScript('/lib/js/perfect-scrollbar/js/perfect-scrollbar.jquery.js');
|
||||
Page::addScript('/lib/js/perfect-scrollbar/js/perfect-scrollbar.js');
|
||||
Page::addStyle('/lib/js/date/jquery.datetimepicker.css');
|
||||
Page::addScript('/lib/js/date/jquery.datetimepicker.js');
|
||||
Page::addScript('/lib/js/tinymce/js/tinymce/tinymce.min.js');
|
||||
Page::addScript(Site::$themeAbsPath."js/all.js");
|
||||
Page::addMetaCode('<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">');
|
||||
|
||||
Page::$favicon = Site::$themeAbsPath."favicon.ico";
|
||||
|
||||
if(IF_AUTH){
|
||||
ob_start(); include_once('jade/sample/bar/left.php'); $LEFT = ob_get_clean();
|
||||
ob_start(); include_once('blocks/content.php'); $CONTENT = ob_get_clean();
|
||||
ob_start(); include_once('jade/sample/bar/right.php'); $RIGHT = ob_get_clean();
|
||||
ob_start(); include_once('jade/sample/bar/footer.php'); $FOOTER = ob_get_clean();
|
||||
Page::addBodyText('<div id="wrap_content"><div id="content">'.$CONTENT.'</div></div>'.$RIGHT.$FOOTER);
|
||||
} else {
|
||||
|
||||
ob_start(); include_once('blocks/content.php'); $CONTENT = ob_get_clean();
|
||||
Page::addBodyText('<div id="wrap_content"><div id="content">'.$CONTENT.'</div></div>');
|
||||
}
|
||||
|
||||
|
||||
echo Page::getPage();
|
||||
|
||||
}
|
||||
|
||||
0
content/admin/jade/dashboard/dash.php
Normal file
0
content/admin/jade/dashboard/list.php
Normal file
2
content/admin/jade/index.php
Normal file
@ -0,0 +1,2 @@
|
||||
<?php use ms\dashboard; ?><div id="dashlist_wrap"><div id="dash_list"><!-- .tbclear.dash_li.arrow_dash(onclick="$.fn.DASHBOARD('prev')") «
|
||||
.dash_li.arrow_dash(onclick="$.fn.DASHBOARD('next')") »--><div id="dash_elements"><?php echo Dashboard::getMyList(); ?></div><div class="dash_li add_dashboard">+ Добавить рабочий стол</div></div></div><div id="dashboards"></div>
|
||||
6
content/admin/jade/profile/auth.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php use ms\ms\structure\ePages as Pages; ?><?php $reg = Pages::url(Pages::REG); ?><div id="auth_form"><div id="auth_content"><div id="title">Авторизоваться</div><form id="form" onsubmit="return $.fn.PROFILE('auth')"><div data-input="email" data-name="login"><input placeholder="email@email.ru" class="i"/></div><div data-input="text" data-name="pass"><input placeholder="Пароль" class="i"/></div><div class="tac"><button class="btn">Авторизоваться</button></div><!-- a(href="#{$reg}") Зарегистрироваться--><div class="res"></div></form></div></div><script>$("#form").FORM({
|
||||
inputs: {
|
||||
login :{validation:{notEmpty:true}, },
|
||||
pass :{validation:{notEmpty:true}, },
|
||||
}
|
||||
})</script>
|
||||
6
content/admin/jade/profile/reg.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php use ms\ms\structure\ePages as Pages; ?><?php $reg = Pages::url(Pages::AUTH); ?><form id="form" onsubmit="return $.fn.PROFILE('reg')"><div data-input="email" data-name="login"><input placeholder="login"/></div><div data-input="text" data-name="pass"><input placeholder="pass1"/></div><button>Зарегистрироваться</button><a href="<?= htmlspecialchars($reg, ENT_QUOTES, 'UTF-8'); ?>">Авторизоваться</a><div class="res"></div></form><script>$("#form").FORM({
|
||||
inputs: {
|
||||
login :{validation:{notEmpty:true}, },
|
||||
pass :{validation:{notEmpty:true}, },
|
||||
}
|
||||
})</script>
|
||||
1
content/admin/jade/sample/auth.php
Normal file
@ -0,0 +1 @@
|
||||
<form onsubmit="return $.fn.PROFILE('auth')"><input name="login"/><input name="password"/></form>
|
||||
4
content/admin/jade/sample/bar/footer.php
Normal file
@ -0,0 +1,4 @@
|
||||
<div id="footer"><div id="footer_left"><div id="start" class="icon"><div class="img black start"></div></div><!-- #icons.icon
|
||||
.img.white.contact
|
||||
.icon
|
||||
.img.red.news--></div><!-- #footer_right--></div>
|
||||
1
content/admin/jade/sample/bar/left.php
Normal file
@ -0,0 +1 @@
|
||||
<!-- #leftbarp левая панель-->
|
||||
1
content/admin/jade/sample/bar/right.php
Normal file
@ -0,0 +1 @@
|
||||
<div id="rightbar"><div id="account"><span onclick="$.fn.PROFILE('exit')">Выйти</span></div></div>
|
||||
1
content/admin/jade/sample/blocks/drag.php
Normal file
@ -0,0 +1 @@
|
||||
<div style="background:black;color:white;display:block;width:200px;height:200px" class="drag"><div class="header">Заголовок</div><div class="wwww"></div></div><div style="background:black;color:white;display:block;width:200px;height:200px" class="drag"><div class="header">Заголовок</div><div class="wwww"></div></div>
|
||||
1
content/admin/jade/sample/blocks/leftbar.php
Normal file
@ -0,0 +1 @@
|
||||
<div id="leftbar"><p>левая панель</p></div>
|
||||
0
content/admin/jade/sample/blocks/typography.php
Normal file
1
content/admin/jade/sample/dashes/dash1.php
Normal file
@ -0,0 +1 @@
|
||||
<div class="dashboard"><div class="grid_dash"><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Новости</div></div></div><div class="td"><div class="icon_td white"><div class="img contact2"></div><div class="title">Контакты</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Безопасность превыше</div></div></div><div class="td"><div class="icon_td white"><div class="img youtube"></div><div class="title">Youtube</div></div></div><div class="td"><div class="icon_td white"><div class="img support"></div><div class="title">Поддержка</div></div></div><div class="td"><div class="icon_td white"><div class="img search"></div><div class="title">Поиск</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Новости</div></div></div><div class="td"><div class="icon_td white"><div class="img contact2"></div><div class="title">Контакты</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Безопасность превыше</div></div></div><div class="td"><div class="icon_td white"><div class="img youtube"></div><div class="title">Youtube</div></div></div><div class="td"><div class="icon_td white"><div class="img support"></div><div class="title">Поддержка</div></div></div><div class="td"><div class="icon_td white"><div class="img search"></div><div class="title">Поиск</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Новости</div></div></div><div class="td"><div class="icon_td white"><div class="img contact2"></div><div class="title">Контакты</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Безопасность превыше</div></div></div><div class="td"><div class="icon_td white"><div class="img youtube"></div><div class="title">Youtube</div></div></div><div class="td"><div class="icon_td white"><div class="img support"></div><div class="title">Поддержка</div></div></div><div class="td"><div class="icon_td white"><div class="img search"></div><div class="title">Поиск</div></div></div><!--.td#help Тут будет помощь--><div id="table" class="td test_table_wrap"><div class="test_table"><?php for($i=1;$i<=10;$i++){; ?><div class="tth"><?php echo htmlspecialchars("Заголовок ".$i, ENT_QUOTES, 'UTF-8'); ?></div><?php }; ?><?php for($i=1;$i<=300;$i++){; ?><div class="ttd"><?php echo htmlspecialchars("ячейка ".$i, ENT_QUOTES, 'UTF-8'); ?></div><?php }; ?></div></div><!-- .td.test_table_wrap#table1include ../test/table--></div></div>
|
||||
1
content/admin/jade/sample/dashes/dash2.php
Normal file
@ -0,0 +1 @@
|
||||
<div class="dashboard"><div class="grid_dash"><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Новости</div></div></div><div class="td"><div class="icon_td white"><div class="img contact2"></div><div class="title">Контакты</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Безопасность превыше</div></div></div><div class="td"><div class="icon_td white"><div class="img youtube"></div><div class="title">Youtube</div></div></div><div class="td"><div class="icon_td white"><div class="img search"></div><div class="title">Поддержка</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Новости</div></div></div><div class="td"><div class="icon_td white"><div class="img contact2"></div><div class="title">Контакты</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Безопасность превыше</div></div></div><div class="td"><div class="icon_td white"><div class="img youtube"></div><div class="title">Youtube</div></div></div><div class="td"><div class="icon_td white"><div class="img search"></div><div class="title">Поддержка</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Новости</div></div></div><div class="td"><div class="icon_td white"><div class="img contact2"></div><div class="title">Контакты</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Безопасность превыше</div></div></div><div class="td"><div class="icon_td white"><div class="img youtube"></div><div class="title">Youtube</div></div></div><div class="td"><div class="icon_td white"><div class="img search"></div><div class="title">Поддержка</div></div></div></div></div>
|
||||
1
content/admin/jade/sample/index.php
Normal file
@ -0,0 +1 @@
|
||||
<?php use ms\dashboard; ?><?php $a = Dashboard::getMy(true); ?><div id="dashlist_wrap"><div id="dash_list"><div class="tbclear"><div onclick="$.fn.DASHBOARD('prev')" class="dash_li arrow_dash">«</div><div onclick="$.fn.DASHBOARD('next')" class="dash_li arrow_dash">»</div></div><div id="dash_elements"><?php foreach($a as $k=>$v){; ?><?php $id = $v[Dashboard::$ID]; ?><div onclick="$.fn.DASHBOARD('setDashboard',<?= htmlspecialchars($id, ENT_QUOTES, 'UTF-8'); ?>)" class="dash_li"><?php echo Dashboard::getName($v); ?></div><?php }; ?></div><div onclick="" class="dash_li">+ Добавить рабочий стол</div></div></div><div id="dashboards"><div id="dashboards_slider"><?php foreach($a as $v){; ?><div class="dashboard"><div class="grid_dash"><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Новости</div></div></div><div class="td"><div class="icon_td white"><div class="img contact2"></div><div class="title">Контакты</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Безопасность превыше</div></div></div><div class="td"><div class="icon_td white"><div class="img youtube"></div><div class="title">Youtube</div></div></div><div class="td"><div class="icon_td white"><div class="img support"></div><div class="title">Поддержка</div></div></div><div class="td"><div class="icon_td white"><div class="img search"></div><div class="title">Поиск</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Новости</div></div></div><div class="td"><div class="icon_td white"><div class="img contact2"></div><div class="title">Контакты</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Безопасность превыше</div></div></div><div class="td"><div class="icon_td white"><div class="img youtube"></div><div class="title">Youtube</div></div></div><div class="td"><div class="icon_td white"><div class="img support"></div><div class="title">Поддержка</div></div></div><div class="td"><div class="icon_td white"><div class="img search"></div><div class="title">Поиск</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Новости</div></div></div><div class="td"><div class="icon_td white"><div class="img contact2"></div><div class="title">Контакты</div></div></div><div class="td"><div class="icon_td white"><div class="img news2"></div><div class="title">Безопасность превыше</div></div></div><div class="td"><div class="icon_td white"><div class="img youtube"></div><div class="title">Youtube</div></div></div><div class="td"><div class="icon_td white"><div class="img support"></div><div class="title">Поддержка</div></div></div><div class="td"><div class="icon_td white"><div class="img search"></div><div class="title">Поиск</div></div></div><!--.td#help Тут будет помощь--><div id="table" class="td test_table_wrap"><div class="test_table"><?php for($i=1;$i<=10;$i++){; ?><div class="tth"><?php echo htmlspecialchars("Заголовок ".$i, ENT_QUOTES, 'UTF-8'); ?></div><?php }; ?><?php for($i=1;$i<=300;$i++){; ?><div class="ttd"><?php echo htmlspecialchars("ячейка ".$i, ENT_QUOTES, 'UTF-8'); ?></div><?php }; ?></div></div><!-- .td.test_table_wrap#table1include ../test/table--></div></div><?php }; ?></div></div><!--include blocks/typography-->
|
||||
1
content/admin/jade/sample/layout/flex.php
Normal file
@ -0,0 +1 @@
|
||||
<div class="row"><div style="background:yellow;height:50px" class="c c-mob-100 c-pad-25 c-desk-50"></div><div style="background:blue;height:50px" class="c c-50 c-mob-100 c-pad-25 c-desk-50"></div><div style="background:purple;height:50px" class="c c-33 c-mob-100 c-pad-25 c-desk-50"></div><div style="background:navy;height:50px" class="c c-17 c-mob-100 c-pad-25 c-desk-50"></div></div><div class="row"><div style="background:yellow;height:50px" class="c c-mob-1-3 c-pad-4-9"></div><div style="background:blue;height:50px" class="c c-mob-1-3 c-pad-4-9"></div><div style="background:purple;height:50px" class="c c-mob-1-3 c-pad-4-9"></div><div style="background:navy;height:50px" class="c c-mob-1-3 c-pad-4-9"></div></div><div class="row"><div style="background:yellow;height:50px" class="c c-25"></div><div style="background:blue;height:50px" class="c c-25"></div><div style="background:purple;height:50px" class="c c-25"></div><div style="background:navy;height:50px" class="c c-25"></div></div><div class="row"><div class="c c-1-3">Блок ровно в 1/3</div><div class="c c-1-3">Блок ровно в 1/3</div><div class="c c-1-3">Блок ровно в 1/3</div></div><div class="row"><div class="c c-2-7">Блок ровно в 2/7</div><div class="c c-5-7">Блок ровно в 5/7</div></div>
|
||||
0
content/admin/jade/sample/layout/grid.php
Normal file
0
content/admin/jade/sample/test.php
Normal file
1
content/admin/jade/sample/test/table.php
Normal file
@ -0,0 +1 @@
|
||||
<div class="test_table"><?php for($i=1;$i<=10;$i++){; ?><div class="tth"><?php echo htmlspecialchars("Заголовок ".$i, ENT_QUOTES, 'UTF-8'); ?></div><?php }; ?><?php for($i=1;$i<=300;$i++){; ?><div class="ttd"><?php echo htmlspecialchars("ячейка ".$i, ENT_QUOTES, 'UTF-8'); ?></div><?php }; ?></div>
|
||||
0
content/admin/jade/sample/test/test.php
Normal file
1872
content/admin/js/all.js
Normal file
1
content/admin/js/all.min.js
vendored
Normal file
BIN
content/admin/pics/back/1.jpg
Normal file
|
After Width: | Height: | Size: 845 KiB |
BIN
content/admin/pics/back/2.jpg
Normal file
|
After Width: | Height: | Size: 458 KiB |
BIN
content/admin/pics/back/back 1.jpg
Normal file
|
After Width: | Height: | Size: 850 KiB |
BIN
content/admin/pics/back/back.jpg
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
content/admin/pics/back/back1.jpg
Normal file
|
After Width: | Height: | Size: 131 KiB |
BIN
content/admin/pics/back/o/Depositphotos_75925837_xl-2015.jpg
Normal file
|
After Width: | Height: | Size: 18 MiB |
1
content/admin/pics/icons/contact.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new 0 0 24 24" height="24px" id="Layer_1" version="1.1" viewBox="0 0 24 24" width="24px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><g><g><g><g><path d="M14.466,19.182H7.938c-0.62,0-1.125-0.492-1.125-1.094v-1.145c0-2.002,1.763-4.145,4.388-4.145 c2.626,0,4.39,2.143,4.39,4.145v1.145C15.591,18.689,15.086,19.182,14.466,19.182z M11.201,13.596 c-2.147,0-3.59,1.732-3.59,3.348v1.145c0,0.162,0.146,0.295,0.327,0.295h6.528c0.181,0,0.327-0.133,0.327-0.295v-1.145 C14.793,15.328,13.35,13.596,11.201,13.596z"/></g></g></g></g><g><g><g><g><path d="M19.263,11.862c-1.961,0-3.556-1.58-3.556-3.521s1.595-3.521,3.556-3.521c1.96,0,3.555,1.58,3.555,3.521 S21.223,11.862,19.263,11.862z M19.263,5.617c-1.521,0-2.758,1.222-2.758,2.724c0,1.501,1.237,2.724,2.758,2.724 S22.02,9.842,22.02,8.34C22.02,6.838,20.783,5.617,19.263,5.617z"/></g></g></g></g><g><g><g><g><path d="M11.254,12.513c-1.764,0-3.199-1.421-3.199-3.168c0-1.748,1.435-3.17,3.199-3.17c1.765,0,3.202,1.422,3.202,3.17 C14.456,11.092,13.02,12.513,11.254,12.513z M11.254,6.973c-1.324,0-2.401,1.064-2.401,2.372c0,1.307,1.077,2.37,2.401,2.37 c1.325,0,2.404-1.063,2.404-2.37C13.658,8.038,12.58,6.973,11.254,6.973z"/></g></g></g></g><g><g><g><g><path d="M3.994,13.1c-1.588,0-2.88-1.279-2.88-2.851c0-1.572,1.292-2.851,2.88-2.851s2.88,1.279,2.88,2.851 C6.874,11.821,5.582,13.1,3.994,13.1z M3.994,8.196c-1.148,0-2.082,0.921-2.082,2.053s0.934,2.053,2.082,2.053 c1.148,0,2.082-0.921,2.082-2.053S5.142,8.196,3.994,8.196z"/></g></g></g></g><g><g><g><g><path d="M6.869,19.182H1.116c-0.598,0-1.084-0.484-1.084-1.082V16.99c0-1.949,1.592-4.037,3.961-4.037 c2.371,0,3.963,2.087,3.963,4.037V18.1C7.957,18.697,7.469,19.182,6.869,19.182z M3.994,13.752 c-1.892,0-3.164,1.674-3.164,3.238V18.1c0,0.156,0.128,0.283,0.286,0.283h5.753c0.16,0,0.29-0.127,0.29-0.283V16.99 C7.159,15.426,5.887,13.752,3.994,13.752z"/></g></g></g></g><g><g><g><g><path d="M22.813,19.113H15.71c-0.636,0-1.152-0.512-1.152-1.141v-1.334c0-2.273,1.89-4.706,4.704-4.706 s4.706,2.432,4.706,4.706v1.334C23.968,18.602,23.449,19.113,22.813,19.113z M19.262,12.731c-2.337,0-3.906,2.019-3.906,3.908 v1.334c0,0.189,0.159,0.344,0.354,0.344h7.103c0.197,0,0.357-0.154,0.357-0.344v-1.334 C23.17,14.75,21.599,12.731,19.262,12.731z"/></g></g></g></g></g></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
BIN
content/admin/pics/icons/contact2.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
content/admin/pics/icons/news.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
29
content/admin/pics/icons/news.svg
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="news">
|
||||
<path class="st0" d="M29,0H7C5.3,0,4,1.3,4,3v2H3C1.3,5,0,6.3,0,8v20c0,2.2,1.8,4,4,4h24c2.2,0,4-1.8,4-4V3C32,1.3,30.7,0,29,0z
|
||||
M30,28c0,1.1-0.9,2-2,2H4c-1.1,0-2-0.9-2-2V8c0-0.6,0.4-1,1-1h1v20c0,0.6,0.4,1,1,1s1-0.4,1-1V3c0-0.6,0.4-1,1-1h22
|
||||
c0.6,0,1,0.4,1,1V28z"/>
|
||||
<path class="st0" d="M19.5,13h8c0.3,0,0.5-0.2,0.5-0.5S27.8,12,27.5,12h-8c-0.3,0-0.5,0.2-0.5,0.5S19.2,13,19.5,13z"/>
|
||||
<path class="st0" d="M19.5,10h8c0.3,0,0.5-0.2,0.5-0.5S27.8,9,27.5,9h-8C19.2,9,19,9.2,19,9.5S19.2,10,19.5,10z"/>
|
||||
<path class="st0" d="M19.5,7h8C27.8,7,28,6.8,28,6.5S27.8,6,27.5,6h-8C19.2,6,19,6.2,19,6.5S19.2,7,19.5,7z"/>
|
||||
<path class="st0" d="M16.5,27h-8C8.2,27,8,27.2,8,27.5C8,27.8,8.2,28,8.5,28h8c0.3,0,0.5-0.2,0.5-0.5C17,27.2,16.8,27,16.5,27z"/>
|
||||
<path class="st0" d="M16.5,24h-8C8.2,24,8,24.2,8,24.5C8,24.8,8.2,25,8.5,25h8c0.3,0,0.5-0.2,0.5-0.5C17,24.2,16.8,24,16.5,24z"/>
|
||||
<path class="st0" d="M16.5,21h-8C8.2,21,8,21.2,8,21.5C8,21.8,8.2,22,8.5,22h8c0.3,0,0.5-0.2,0.5-0.5C17,21.2,16.8,21,16.5,21z"/>
|
||||
<path class="st0" d="M27.5,27h-8c-0.3,0-0.5,0.2-0.5,0.5c0,0.3,0.2,0.5,0.5,0.5h8c0.3,0,0.5-0.2,0.5-0.5C28,27.2,27.8,27,27.5,27z"
|
||||
/>
|
||||
<path class="st0" d="M27.5,24h-8c-0.3,0-0.5,0.2-0.5,0.5c0,0.3,0.2,0.5,0.5,0.5h8c0.3,0,0.5-0.2,0.5-0.5C28,24.2,27.8,24,27.5,24z"
|
||||
/>
|
||||
<path class="st0" d="M27.5,21h-8c-0.3,0-0.5,0.2-0.5,0.5c0,0.3,0.2,0.5,0.5,0.5h8c0.3,0,0.5-0.2,0.5-0.5C28,21.2,27.8,21,27.5,21z"
|
||||
/>
|
||||
<path class="st0" d="M27.5,15h-19C8.2,15,8,15.2,8,15.5S8.2,16,8.5,16h19c0.3,0,0.5-0.2,0.5-0.5S27.8,15,27.5,15z"/>
|
||||
<path class="st0" d="M27.5,18h-19C8.2,18,8,18.2,8,18.5C8,18.8,8.2,19,8.5,19h19c0.3,0,0.5-0.2,0.5-0.5C28,18.2,27.8,18,27.5,18z"
|
||||
/>
|
||||
<path class="st0" d="M9,13h7c0.6,0,1-0.4,1-1V5c0-0.6-0.4-1-1-1H9C8.4,4,8,4.5,8,5v7C8,12.6,8.4,13,9,13z M10,6h5v5h-5V6z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
25
content/admin/pics/icons/news2.svg
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#000222;}
|
||||
</style>
|
||||
<g id="news">
|
||||
<path class="st0" d="M29,0H7C5.3,0,4,1.3,4,3v2H3C1.3,5,0,6.3,0,8v20c0,2.2,1.8,4,4,4h24c2.2,0,4-1.8,4-4V3C32,1.3,30.7,0,29,0z
|
||||
M30,28c0,1.1-0.9,2-2,2H4c-1.1,0-2-0.9-2-2V8c0-0.6,0.4-1,1-1h1v20c0,0.6,0.4,1,1,1s1-0.4,1-1V3c0-0.6,0.4-1,1-1h22
|
||||
c0.6,0,1,0.4,1,1V28z"/>
|
||||
<path class="st0" d="M19.5,13h8c0.3,0,0.5-0.2,0.5-0.5S27.8,12,27.5,12h-8c-0.3,0-0.5,0.2-0.5,0.5S19.2,13,19.5,13z"/>
|
||||
<path class="st0" d="M19.5,10h8c0.3,0,0.5-0.2,0.5-0.5S27.8,9,27.5,9h-8C19.2,9,19,9.2,19,9.5S19.2,10,19.5,10z"/>
|
||||
<path class="st0" d="M19.5,7h8C27.8,7,28,6.8,28,6.5S27.8,6,27.5,6h-8C19.2,6,19,6.2,19,6.5S19.2,7,19.5,7z"/>
|
||||
<path class="st0" d="M16.5,27h-8C8.2,27,8,27.2,8,27.5S8.2,28,8.5,28h8c0.3,0,0.5-0.2,0.5-0.5S16.8,27,16.5,27z"/>
|
||||
<path class="st0" d="M16.5,24h-8C8.2,24,8,24.2,8,24.5S8.2,25,8.5,25h8c0.3,0,0.5-0.2,0.5-0.5S16.8,24,16.5,24z"/>
|
||||
<path class="st0" d="M16.5,21h-8C8.2,21,8,21.2,8,21.5S8.2,22,8.5,22h8c0.3,0,0.5-0.2,0.5-0.5S16.8,21,16.5,21z"/>
|
||||
<path class="st0" d="M27.5,27h-8c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5h8c0.3,0,0.5-0.2,0.5-0.5S27.8,27,27.5,27z"/>
|
||||
<path class="st0" d="M27.5,24h-8c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5h8c0.3,0,0.5-0.2,0.5-0.5S27.8,24,27.5,24z"/>
|
||||
<path class="st0" d="M27.5,21h-8c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5h8c0.3,0,0.5-0.2,0.5-0.5S27.8,21,27.5,21z"/>
|
||||
<path class="st0" d="M27.5,15h-19C8.2,15,8,15.2,8,15.5S8.2,16,8.5,16h19c0.3,0,0.5-0.2,0.5-0.5S27.8,15,27.5,15z"/>
|
||||
<path class="st0" d="M27.5,18h-19C8.2,18,8,18.2,8,18.5S8.2,19,8.5,19h19c0.3,0,0.5-0.2,0.5-0.5S27.8,18,27.5,18z"/>
|
||||
<path class="st0" d="M9,13h7c0.6,0,1-0.4,1-1V5c0-0.6-0.4-1-1-1H9C8.4,4,8,4.5,8,5v7C8,12.6,8.4,13,9,13z M10,6h5v5h-5V6z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
BIN
content/admin/pics/icons/search.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
content/admin/pics/icons/start.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
56
content/admin/pics/icons/start.svg
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M8.6,20.3L8.1,20c-1-0.6-2.2-1.2-3.9-1.2c-1,0-2.1,0.2-3.4,0.7L0,19.6l2.2-7.7l0.2-0.1
|
||||
c0.2-0.1,0.4-0.1,0.5-0.2c0.8-0.3,1.9-0.6,3.2-0.7c1.2-0.1,2.4,0.2,3.7,0.9c0.3,0.1,0.5,0.3,0.7,0.4l0.3,0.2L8.6,20.3z
|
||||
M4.2,17.8c1.7,0,2.9,0.5,3.9,1.1l1.8-6.2c-0.1-0.1-0.3-0.2-0.4-0.2c-1.1-0.6-2.2-0.8-3.2-0.8c-1.2,0.1-2.3,0.4-3,0.7
|
||||
c-0.1,0-0.2,0.1-0.3,0.1l-1.6,5.8C2.3,18,3.3,17.8,4.2,17.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M14.5,22.7c-1.5,0-3.5-1-4.2-1.4L10,21.1l2.2-7.7l0.5,0.3c2,1.3,4.7,1.6,7.7,0.7l0.8-0.2l-2.1,7.5
|
||||
l-0.2,0.1C17.2,22.4,15.8,22.7,14.5,22.7z M11,20.7c0.8,0.4,2.3,1.2,3.5,1.2c1.1,0,2.4-0.3,3.8-0.8l1.6-5.6
|
||||
c-2.7,0.7-5.2,0.4-7.2-0.7L11,20.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M11.3,10.5l-0.5-0.3C9.9,9.6,8.7,9,7,9C6,9,4.8,9.2,3.5,9.7L2.7,10l2.2-7.7l0.2-0.1
|
||||
c1.5-0.6,2.8-0.9,3.9-0.9c0,0,0,0,0,0c0.6,0,1.2,0.1,1.8,0.2c0.8,0.2,1.9,0.8,2.4,1.1l0.3,0.2L11.3,10.5z M7,8.2
|
||||
c1.6,0,2.9,0.5,3.8,1.1l1.8-6.1c-0.5-0.3-1.3-0.7-1.9-0.8c-0.5-0.1-1-0.2-1.6-0.2c-1,0-2.2,0.3-3.4,0.8L4,8.6
|
||||
C5.1,8.3,6.1,8.2,7,8.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M17.5,13.1L17.5,13.1c-1.6,0-3.1-0.5-4.5-1.3l-0.3-0.2L15,3.8l0.5,0.3c0.9,0.6,2.3,1.3,4.3,1.3
|
||||
c1.1,0,2.3-0.2,3.5-0.7L24,4.4l-2.3,7.7l-0.2,0.1C20.2,12.8,18.8,13.1,17.5,13.1z M13.7,11.2c1.2,0.7,2.5,1,3.8,1l0,0
|
||||
c1.1,0,2.3-0.3,3.5-0.8l1.7-5.7c-1,0.3-2,0.5-3,0.5c-1.8,0-3.3-0.5-4.2-1.1L13.7,11.2z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
34
content/admin/pics/icons/start4.svg
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{display:none;}
|
||||
.st1{display:inline;fill:none;stroke:#000000;stroke-width:16;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g id="layer" class="st0">
|
||||
<rect x="51" y="51" class="st1" width="188" height="188"/>
|
||||
<rect x="273" y="51" class="st1" width="188" height="188"/>
|
||||
<rect x="51" y="273" class="st1" width="188" height="188"/>
|
||||
<rect x="273" y="273" class="st1" width="188" height="188"/>
|
||||
</g>
|
||||
<g id="layer_copy">
|
||||
<g>
|
||||
<path class="st2" d="M239,247H51c-4.4,0-8-3.6-8-8V51c0-4.4,3.6-8,8-8h188c4.4,0,8,3.6,8,8v188C247,243.4,243.4,247,239,247z
|
||||
M59,231h172V59H59V231z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M461,247H273c-4.4,0-8-3.6-8-8V51c0-4.4,3.6-8,8-8h188c4.4,0,8,3.6,8,8v188C469,243.4,465.4,247,461,247z
|
||||
M281,231h172V59H281V231z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M239,469H51c-4.4,0-8-3.6-8-8V273c0-4.4,3.6-8,8-8h188c4.4,0,8,3.6,8,8v188C247,465.4,243.4,469,239,469z
|
||||
M59,453h172V281H59V453z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M461,469H273c-4.4,0-8-3.6-8-8V273c0-4.4,3.6-8,8-8h188c4.4,0,8,3.6,8,8v188C469,465.4,465.4,469,461,469z
|
||||
M281,453h172V281H281V453z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
19
content/admin/pics/icons/start9.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 238 226" style="enable-background:new 0 0 238 226;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#FFFFFF;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<g>
|
||||
<rect x="7" y="8.3" class="st0" width="45.7" height="45.7"/>
|
||||
<rect x="94.5" y="8.3" class="st0" width="45.7" height="45.7"/>
|
||||
<rect x="182" y="8.3" class="st0" width="45.7" height="45.7"/>
|
||||
<rect x="7" y="90.3" class="st0" width="45.7" height="45.7"/>
|
||||
<rect x="94.5" y="90.3" class="st0" width="45.7" height="45.7"/>
|
||||
<rect x="182" y="90.3" class="st0" width="45.7" height="45.7"/>
|
||||
<rect x="7" y="172.3" class="st0" width="45.7" height="45.7"/>
|
||||
<rect x="94.5" y="172.3" class="st0" width="45.7" height="45.7"/>
|
||||
<rect x="182" y="172.3" class="st0" width="45.7" height="45.7"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
content/admin/pics/icons/start_.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
content/admin/pics/icons/start__.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
content/admin/pics/icons/support.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
content/admin/pics/icons/tools/delete.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
content/admin/pics/icons/tools/delete.white.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
content/admin/pics/icons/tools/edit.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
content/admin/pics/icons/tools/edit.white.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
content/admin/pics/icons/widget/blog.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
content/admin/pics/icons/widget/category.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
content/admin/pics/icons/widget/client.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
content/admin/pics/icons/widget/company.png
Normal file
|
After Width: | Height: | Size: 873 B |
BIN
content/admin/pics/icons/widget/faq.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
content/admin/pics/icons/widget/folder.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
content/admin/pics/icons/widget/gallery.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
content/admin/pics/icons/widget/inspiration.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
content/admin/pics/icons/widget/item.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
content/admin/pics/icons/widget/item_property.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
content/admin/pics/icons/widget/kbase.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
content/admin/pics/icons/widget/masterclass.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
content/admin/pics/icons/widget/menu.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
content/admin/pics/icons/widget/order.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
content/admin/pics/icons/widget/page.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
content/admin/pics/icons/widget/payment.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
content/admin/pics/icons/widget/pipeline.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
content/admin/pics/icons/widget/portal.png
Normal file
|
After Width: | Height: | Size: 285 B |
BIN
content/admin/pics/icons/widget/portal1.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
content/admin/pics/icons/widget/project.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
content/admin/pics/icons/widget/resource.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
content/admin/pics/icons/widget/resource_group.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
content/admin/pics/icons/widget/shop.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
content/admin/pics/icons/widget/star.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
content/admin/pics/icons/window/close.png
Normal file
|
After Width: | Height: | Size: 696 B |
18
content/admin/pics/icons/window/close.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="svg8" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 700 700"
|
||||
style="enable-background:new 0 0 700 700;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-width:9.2604;stroke-linecap:round;stroke-linejoin:round;}
|
||||
</style>
|
||||
<g id="layer1" transform="translate(244.17261,202.68451)">
|
||||
<g id="g2246" transform="translate(1239.703,-165.92092)">
|
||||
<path id="path1419" class="st0" d="M-1466.2,645.6l664.6-664.6"/>
|
||||
<path id="path1421" class="st0" d="M-1466.2-19.1l664.6,664.6"/>
|
||||
</g>
|
||||
<g id="g2400" transform="translate(-4.6466675)">
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
content/admin/pics/icons/window/close.white.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
18
content/admin/pics/icons/window/close.white.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="svg8" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 700 700"
|
||||
style="enable-background:new 0 0 700 700;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#FFFFFF;stroke-width:9.2604;stroke-linecap:round;stroke-linejoin:round;}
|
||||
</style>
|
||||
<g id="layer1" transform="translate(244.17261,202.68451)">
|
||||
<g id="g2246" transform="translate(1239.703,-165.92092)">
|
||||
<path id="path1419" class="st0" d="M-1466.2,645.6l664.6-664.6"/>
|
||||
<path id="path1421" class="st0" d="M-1466.2-19.1l664.6,664.6"/>
|
||||
</g>
|
||||
<g id="g2400" transform="translate(-4.6466675)">
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
content/admin/pics/icons/window/fullscreen.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
18
content/admin/pics/icons/window/fullscreen.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="svg8" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 700 700"
|
||||
style="enable-background:new 0 0 700 700;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-width:9.2604;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:6.2;}
|
||||
</style>
|
||||
<g id="layer1" transform="translate(244.17261,202.68451)">
|
||||
<g id="g2189" transform="translate(-468.25618,320.72341)">
|
||||
<rect id="rect931" x="241.6" y="-422.8" class="st0" width="581.8" height="581.8"/>
|
||||
<path id="path1745" class="st0" d="M324.7-422.8v-83.2l0,0h581.8V75.9h-83.2"/>
|
||||
</g>
|
||||
<g id="g2400" transform="translate(-4.6466675)">
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
content/admin/pics/icons/window/fullscreen.white.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
18
content/admin/pics/icons/window/fullscreen.white.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="svg8" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 700 700"
|
||||
style="enable-background:new 0 0 700 700;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#FFFFFF;stroke-width:9.2604;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:6.2;}
|
||||
</style>
|
||||
<g id="layer1" transform="translate(244.17261,202.68451)">
|
||||
<g id="g2189" transform="translate(-468.25618,320.72341)">
|
||||
<rect id="rect931" x="241.6" y="-422.8" class="st0" width="581.8" height="581.8"/>
|
||||
<path id="path1745" class="st0" d="M324.7-422.8v-83.2l0,0h581.8V75.9h-83.2"/>
|
||||
</g>
|
||||
<g id="g2400" transform="translate(-4.6466675)">
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
content/admin/pics/icons/window/minimize.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
15
content/admin/pics/icons/window/minimize.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="svg8" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 700 700"
|
||||
style="enable-background:new 0 0 700 700;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-width:9.2604;stroke-linecap:round;stroke-linejoin:round;}
|
||||
</style>
|
||||
<g id="layer1" transform="translate(244.17261,202.68451)">
|
||||
<path id="path1245-9" class="st0" d="M-226.4,147.3H438"/>
|
||||
<g id="g2400" transform="translate(-4.6466675)">
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 975 B |
BIN
content/admin/pics/icons/window/minimize.white.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
15
content/admin/pics/icons/window/minimize.white.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1"
|
||||
id="svg8" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 700 700"
|
||||
style="enable-background:new 0 0 700 700;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#FFFFFF;stroke-width:9.2604;stroke-linecap:round;stroke-linejoin:round;}
|
||||
</style>
|
||||
<g id="layer1" transform="translate(244.17261,202.68451)">
|
||||
<path id="path1245-9" class="st0" d="M-226.4,147.3H438"/>
|
||||
<g id="g2400" transform="translate(-4.6466675)">
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 975 B |
BIN
content/admin/pics/icons/window/reload.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
1
content/admin/pics/icons/window/reload.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><svg style="enable-background:new 0 0 50 50;" version="1.1" viewBox="0 0 50 50" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1"><path d="M45.633,17.348C46.54,19.792,47,22.367,47,25c0,12.131-9.869,22-22,22c-6.595,0-12.795-2.963-16.958-8H15v-2H5v10h2v-6.126 C11.53,46.007,18.068,49,25,49c13.234,0,24-10.767,24-24c0-2.872-0.502-5.68-1.492-8.348L45.633,17.348z"/><path d="M35,13h10V3h-2v6.125C38.47,3.992,31.934,1,25,1C11.767,1,1,11.767,1,25c0,2.872,0.502,5.68,1.492,8.348l1.875-0.696 C3.46,30.208,3,27.633,3,25C3,12.869,12.869,3,25,3c6.598,0,12.795,2.962,16.958,8H35V13z"/></g><g/></svg>
|
||||
|
After Width: | Height: | Size: 677 B |
BIN
content/admin/pics/icons/window/reload.white.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |