Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e60ffba00 | ||
|
|
312cbcf7d9 |
173
AGENTS.md
Normal file
173
AGENTS.md
Normal file
@ -0,0 +1,173 @@
|
||||
<!-- vibe42-agents-version: v3-guided-2026-06-01 -->
|
||||
# Vibe42 — учебная песочница для лендингов
|
||||
|
||||
Workspace юзера `Dinara`. Это **учебная среда**, где обычные люди (не разработчики) пробуют сделать свой первый сайт.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 ТВОЯ РОЛЬ
|
||||
|
||||
Ты — **гид и помощник**, а не слепой исполнитель. Цель сессии — чтобы юзер вышел с:
|
||||
1. **рабочим лендингом**, опубликованным по адресу `https://pages.git.vibe42.kz/Dinara/<repo>/`,
|
||||
2. ощущением «это было легко» — без серверов, БД, токенов, конфигов.
|
||||
|
||||
Юзер не разработчик. Ему важен **результат, который видно в браузере**, а не код.
|
||||
|
||||
---
|
||||
|
||||
## 🗺 СЦЕНАРИЙ ПЕРВОГО ЗАХОДА (юзер только зашёл, ещё ничего нет)
|
||||
|
||||
1. Поздоровайся коротко: «Привет! Тут за 10 минут собираем лендинг и публикуем его в интернете. О чём хочешь сделать?»
|
||||
2. Если он не знает — предложи **4 конкретных идеи** (выбирай близкие к нему, не абстрактные):
|
||||
- Промо хобби (фотография / музыка / спорт)
|
||||
- Резюме / personal page с контактами
|
||||
- Афиша мероприятия (концерт, день рождения, мастер-класс)
|
||||
- Меню заведения / прайс услуг
|
||||
- Лендинг продукта или будущего проекта (waitlist)
|
||||
3. Уточни **2 короткие детали**: стиль (тёмный/светлый/яркий) и главную цель (рассказать / собрать заявку / показать работы).
|
||||
4. Сразу делай `./new-project <name>` и собирай страницу. Не спрашивай разрешения на каждый шаг.
|
||||
|
||||
---
|
||||
|
||||
## 💬 ЕСЛИ ЮЗЕР ОТВЕЧАЕТ РАСПЛЫВЧАТО
|
||||
|
||||
Юзер говорит «сделай что-нибудь» / «ну хз» / «сюрприз» → **не делай ничего абстрактного**.
|
||||
|
||||
Скажи: «Давай определимся, я задам 3 коротких вопроса:
|
||||
1. Это для тебя лично, для проекта/бизнеса, или для события?
|
||||
2. Главная цель — рассказать о чём-то / собрать заявку / показать портфолио?
|
||||
3. Любимое настроение — строгое тёмное, лёгкое светлое, яркое цветное?»
|
||||
|
||||
После ответов **сразу** предложи 2 конкретных варианта названия+структуры. Дай выбрать и иди делать.
|
||||
|
||||
---
|
||||
|
||||
## 🚫 ЕСЛИ ЮЗЕР ХОЧЕТ СЛОЖНОЕ — ПЕРЕФОРМУЛИРУЙ В ЛЕНДИНГ
|
||||
|
||||
| Запрос | Что делаем вместо |
|
||||
|--------|-------------------|
|
||||
| «магазин с корзиной» | лендинг с товарами + кнопка «купить» = ссылка на WhatsApp / Telegram |
|
||||
| «соцсеть» | лендинг будущего проекта + waitlist-форма (Formspree / Getform) |
|
||||
| «блог с админкой» | personal-page + ссылки на статьи в Telegram/Medium |
|
||||
| «приложение для записи» | лендинг услуги + ссылка на Calendly / WhatsApp |
|
||||
| «сайт с входом юзеров» | публичный лендинг без логина (нам логин не нужен) |
|
||||
| «бот в Telegram» | лендинг с описанием бота + кнопка `t.me/...` |
|
||||
|
||||
**Не говори «это невозможно».** Скажи: «У нас песочница только для статических сайтов. Давай сделаем лендинг, который покажет твою идею — а кнопки/формы свяжем с готовыми сервисами (WhatsApp, Telegram, Formspree)». Юзер счастлив, результат за 15 минут.
|
||||
|
||||
---
|
||||
|
||||
## 📐 ШАБЛОНЫ СТРАНИЦ (выбирай под идею юзера)
|
||||
|
||||
### A — Промо продукта/услуги
|
||||
**Секции:** Hero (заголовок + подзаголовок + CTA-кнопка) → 3-4 преимущества (иконка emoji + текст) → социальное доказательство (отзыв или цифра) → CTA (кнопка/телефон/мессенджер).
|
||||
|
||||
### B — Personal / резюме
|
||||
**Секции:** Hero (фото-аватарка + имя + одна фраза «кто я») → О себе (1-2 абзаца) → 3-5 карточек проектов/опыта → Контакты (email, telegram, github как ссылки-кнопки).
|
||||
|
||||
### C — Афиша мероприятия
|
||||
**Секции:** Hero (название + дата + место крупно) → Программа (список с временем) → Локация (картинка-placeholder + адрес) → Регистрация (форма Formspree или контакт).
|
||||
|
||||
### D — Меню / прайс
|
||||
**Секции:** Hero (название + слоган) → Меню/прайс (категории с ценами) → Контакты (телефон, адрес, часы работы, карта-картинка).
|
||||
|
||||
### E — Waitlist для будущего проекта
|
||||
**Секции:** Hero (название проекта + одна фраза + email-форма) → 3 фичи «что будет» → FAQ (3 пункта) → CTA (та же email-форма).
|
||||
|
||||
Все шаблоны — **одна страница, прокрутка вниз**. Никаких роутов, ничего динамического.
|
||||
|
||||
---
|
||||
|
||||
## ⚡ РИТУАЛ ПОСЛЕ ПЕРВОГО ЗАПУСКА
|
||||
|
||||
Как только готов первый рабочий вариант (даже грубый):
|
||||
|
||||
1. **Сразу запушь:**
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "v1"
|
||||
git push origin HEAD:pages
|
||||
```
|
||||
2. **ОБЯЗАТЕЛЬНО** дай юзеру ссылку **жирно**:
|
||||
> 🎉 Готово! Твой лендинг здесь: **https://pages.git.vibe42.kz/Dinara/<repo>/**
|
||||
3. Скажи: «Открой в новой вкладке, посмотри. Что хочешь поменять?»
|
||||
4. Дальше короткие итерации: правка → push → новый URL-показ. Каждые 2-3 правки — push.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ ЖЕЛЕЗНЫЕ ПРАВИЛА (НЕ нарушать никогда)
|
||||
|
||||
1. **Только статика — HTML + CSS + JS в браузере.**
|
||||
2. **Никакого бэкенда.** Никаких Node/Express/FastAPI/Django/PHP/Go-серверов. Никаких БД. Никакого Redis.
|
||||
3. **Никакой аутентификации / OAuth / JWT.**
|
||||
4. **Никакого Docker, nginx, sudo, системных настроек.**
|
||||
5. **Никаких тяжёлых сборщиков** (`npm install` дерево на 500МБ). Tailwind — только через CDN.
|
||||
6. **НИКОГДА `git init` в workspace root (`/workspaces/Dinara`)** — это папка-контейнер юзера, не репозиторий.
|
||||
|
||||
---
|
||||
|
||||
## ✅ ВСЕГДА работай через `./new-project`
|
||||
|
||||
Если юзер сказал «сделай сайт NAME» / «создай проект NAME»:
|
||||
|
||||
```bash
|
||||
cd /workspaces/Dinara
|
||||
./new-project NAME # создаёт repo в Gitea + клонит локально в ./NAME/
|
||||
cd NAME
|
||||
# теперь создавай index.html / style.css / script.js внутри ./NAME
|
||||
```
|
||||
|
||||
`./new-project` сам создаёт repo, клонит, и копирует туда `AGENTS.md` + `design.md`.
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Git и публикация
|
||||
|
||||
**НЕТ GitHub.** Self-hosted git: **https://git.vibe42.kz**
|
||||
|
||||
- Профиль юзера: https://git.vibe42.kz/Dinara
|
||||
- Pages (живые лендинги): https://pages.git.vibe42.kz/Dinara/<repo>/
|
||||
- Креды уже в `/workspaces/Dinara/.git-credentials` — git push/clone работают без пароля
|
||||
- **НЕ спрашивай юзера про GitHub URL / токен** — их не нужно
|
||||
|
||||
### Опубликовать лендинг
|
||||
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "site"
|
||||
git push origin HEAD:pages
|
||||
```
|
||||
|
||||
Ветка **`pages`** (Caddy её обслуживает; `gh-pages` тоже работает как fallback). Push → лендинг доступен мгновенно.
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Когда что-то идёт не так
|
||||
|
||||
- **Pages 404** → запушь ветку `pages` снова: `git push origin HEAD:pages -f`
|
||||
- **Не дёргай Gitea API типа `/repos/.../pages`, `/settings/pages`, `/deploy_keys`** — их нет
|
||||
- **Не пытайся «настроить Pages через UI Gitea»** — Pages у нас работают только через push в ветку `pages`
|
||||
- Запуталось — сделай новый чистый проект через `./new-project NAME-v2`, перенеси туда работающий index.html
|
||||
|
||||
---
|
||||
|
||||
## ❌ Чего НЕ делать НИКОГДА
|
||||
|
||||
- ❌ `git init` в workspace root
|
||||
- ❌ `npm install` с прод-зависимостями (express/mongoose/pg/prisma/next/nuxt)
|
||||
- ❌ Создавать `server.js` / `app.py` / `main.go` как backend
|
||||
- ❌ Использовать `gh` CLI или GitHub API
|
||||
- ❌ Вызывать Gitea Pages-API (его нет)
|
||||
- ❌ Долгое отлаживание Pages — почти всегда решение «push HEAD:pages»
|
||||
- ❌ Просить юзера ввести токен/URL/пароль — всё уже настроено
|
||||
- ❌ Задавать юзеру 10 вопросов подряд (максимум 2-3 за раз)
|
||||
- ❌ Показывать юзеру голый код больше 1 раза — ему важен результат, а не как написано
|
||||
- ❌ Предлагать «давай сначала дизайн в Figma» — мы делаем сразу в HTML
|
||||
- ❌ Говорить «это сложно» — переформулируй в простое
|
||||
- ❌ Зависать в обсуждениях — сделай первый вариант грубо, потом итерируй
|
||||
|
||||
---
|
||||
|
||||
## 🎨 design.md
|
||||
|
||||
Рядом лежит `design.md` с готовой палитрой, типографикой и стартер-шаблоном `index.html`. **Начинай с него.** Не выдумывай новые цвета — модифицируй существующие.
|
||||
110
design.md
Normal file
110
design.md
Normal file
@ -0,0 +1,110 @@
|
||||
<!-- vibe42-design-version: v1-2026-06-01 -->
|
||||
# Design system — Vibe42 песочница
|
||||
|
||||
Базовые цвета и типографика для лендингов. Можно отклоняться, но начинай с этого.
|
||||
|
||||
## Палитра
|
||||
|
||||
| Token | Hex | Использование |
|
||||
|-------|-----|---------------|
|
||||
| `--ink` | `#0F1218` | Тёмный фон / основной текст |
|
||||
| `--cyan` | `#00E5FF` | Основной акцент (кнопки, лого) |
|
||||
| `--cyan-50` | `#E8FCFF` | Светлая подложка для акцентов |
|
||||
| `--white` | `#FFFFFF` | Основной фон |
|
||||
| `--gray-500` | `#5B6573` | Вторичный текст |
|
||||
| `--gray-100` | `#F2F4F7` | Сепараторы / тонкие фоны |
|
||||
|
||||
## Типографика
|
||||
|
||||
```css
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
|
||||
```
|
||||
|
||||
| Уровень | Размер | Вес | line-height |
|
||||
|---------|--------|-----|-------------|
|
||||
| h1 (hero) | 56px | 800 | 1.05 |
|
||||
| h2 (section) | 36px | 700 | 1.15 |
|
||||
| h3 | 22px | 700 | 1.3 |
|
||||
| body | 17px | 400 | 1.6 |
|
||||
| small | 14px | 400 | 1.5 |
|
||||
|
||||
На мобиле — h1 уменьши до 36px, h2 до 28px.
|
||||
|
||||
## Лейаут
|
||||
|
||||
- max-width контента: **1140px** (контейнер с padding по бокам)
|
||||
- секция: `padding: 80px 24px` (мобила: `48px 20px`)
|
||||
- gap между блоками внутри секции: `24-32px`
|
||||
- border-radius: `8px` (кнопки, карточки), `16px` (большие карточки)
|
||||
|
||||
## Кнопки
|
||||
|
||||
```css
|
||||
.btn-primary {
|
||||
background: var(--cyan); color: var(--ink);
|
||||
padding: 14px 28px; border-radius: 8px;
|
||||
font-weight: 700; text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
.btn-secondary {
|
||||
background: transparent; color: var(--ink);
|
||||
border: 2px solid var(--ink);
|
||||
padding: 12px 26px; border-radius: 8px;
|
||||
}
|
||||
```
|
||||
|
||||
## Стартер `index.html`
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Мой проект</title>
|
||||
<style>
|
||||
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7}
|
||||
*{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font:17px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;color:var(--ink);background:var(--white)}
|
||||
.container{max-width:1140px;margin:0 auto;padding:80px 24px}
|
||||
.hero{background:var(--ink);color:var(--white)}
|
||||
.hero h1{font-size:56px;font-weight:800;line-height:1.05;margin-bottom:24px}
|
||||
.hero p{font-size:20px;color:#9aa3b2;max-width:600px;margin-bottom:32px}
|
||||
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:14px 28px;border-radius:8px;font-weight:700;text-decoration:none}
|
||||
.btn:hover{background:#1be5ff}
|
||||
.section h2{font-size:36px;font-weight:700;margin-bottom:24px}
|
||||
.card{background:var(--gray-100);border-radius:16px;padding:32px;margin-bottom:16px}
|
||||
@media (max-width:640px){.hero h1{font-size:36px}.section h2{font-size:28px}.container{padding:48px 20px}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<h1>Заголовок проекта</h1>
|
||||
<p>Подзаголовок — пара предложений о чём это.</p>
|
||||
<a class="btn" href="#section">Начать</a>
|
||||
</div>
|
||||
</section>
|
||||
<section id="section" class="section">
|
||||
<div class="container">
|
||||
<h2>Секция</h2>
|
||||
<div class="card">Контент карточки.</div>
|
||||
<div class="card">Контент карточки.</div>
|
||||
</div>
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Чем НЕ пользоваться
|
||||
|
||||
- Bootstrap, Material UI, Chakra, Ant Design — слишком тяжело и не нужно для лендинга
|
||||
- Font Awesome — используй emoji (🚀 ⚡ ✨) или inline SVG
|
||||
- jQuery — vanilla JS более чем достаточно
|
||||
|
||||
## Чем МОЖНО (если очень надо)
|
||||
|
||||
- **Tailwind через CDN**: `<script src="https://cdn.tailwindcss.com"></script>` — для прототипа OK
|
||||
- **Lottie animations через CDN**
|
||||
- **Placeholder картинки**: `https://picsum.photos/800/600`, `https://placehold.co/600x400`
|
||||
- **Шрифты Google Fonts через `<link>`** в head
|
||||
722
index.html
Normal file
722
index.html
Normal file
@ -0,0 +1,722 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>HR Ассистент — контроль поручений</title>
|
||||
<style>
|
||||
:root {
|
||||
--ink: #0F1218;
|
||||
--cyan: #00E5FF;
|
||||
--cyan-50: #E8FCFF;
|
||||
--white: #fff;
|
||||
--gray-500: #5B6573;
|
||||
--gray-100: #F2F4F7;
|
||||
--gray-200: #E4E7EC;
|
||||
--green: #10B981;
|
||||
--green-bg: #ECFDF5;
|
||||
--amber: #F59E0B;
|
||||
--amber-bg: #FFFBEB;
|
||||
--red: #EF4444;
|
||||
--red-bg: #FEF2F2;
|
||||
--indigo: #4F46E5;
|
||||
--indigo-bg: #EEF2FF;
|
||||
--radius: 12px;
|
||||
--shadow: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
|
||||
--shadow-lg: 0 4px 24px rgba(0,0,0,.08);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0 }
|
||||
|
||||
body {
|
||||
font: 16px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
|
||||
color: var(--ink);
|
||||
background: var(--gray-100);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
/* NAV */
|
||||
.nav {
|
||||
position: sticky; top: 0; z-index: 100;
|
||||
background: var(--white);
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
.nav .container {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
height: 60px;
|
||||
}
|
||||
.nav-logo {
|
||||
font-size: 18px; font-weight: 700;
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
text-decoration: none; color: var(--ink);
|
||||
}
|
||||
.nav-logo .dot {
|
||||
width: 32px; height: 32px;
|
||||
background: var(--cyan);
|
||||
border-radius: 8px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 16px;
|
||||
}
|
||||
.nav-links { display: flex; gap: 8px; flex-wrap: wrap }
|
||||
.nav-links a {
|
||||
text-decoration: none; color: var(--gray-500);
|
||||
font-size: 14px; font-weight: 500;
|
||||
padding: 6px 14px; border-radius: 8px;
|
||||
transition: all .15s;
|
||||
}
|
||||
.nav-links a:hover, .nav-links a.active { background: var(--gray-100); color: var(--ink) }
|
||||
|
||||
/* HERO / STATS */
|
||||
.hero { padding: 48px 0 32px }
|
||||
.hero h1 { font-size: 36px; font-weight: 800; line-height: 1.2; margin-bottom: 8px }
|
||||
.hero .sub { color: var(--gray-500); font-size: 17px; margin-bottom: 36px }
|
||||
|
||||
.stats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px }
|
||||
.stat-card {
|
||||
background: var(--white); border-radius: var(--radius);
|
||||
padding: 24px; box-shadow: var(--shadow);
|
||||
display: flex; flex-direction: column; gap: 8px;
|
||||
}
|
||||
.stat-card .label { font-size: 13px; font-weight: 600; color: var(--gray-500); text-transform: uppercase; letter-spacing: .4px }
|
||||
.stat-card .value { font-size: 36px; font-weight: 800 }
|
||||
.stat-card .dot-indicator {
|
||||
display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px;
|
||||
}
|
||||
|
||||
/* SECTION HEADER */
|
||||
.section-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
margin: 56px 0 24px; flex-wrap: wrap; gap: 12px;
|
||||
}
|
||||
.section-header h2 { font-size: 24px; font-weight: 700 }
|
||||
|
||||
.search-bar {
|
||||
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
|
||||
}
|
||||
.search-bar input {
|
||||
padding: 10px 16px; border: 1px solid var(--gray-200);
|
||||
border-radius: 8px; font-size: 14px; outline: none;
|
||||
min-width: 220px; background: var(--white);
|
||||
}
|
||||
.search-bar input:focus { border-color: var(--cyan) }
|
||||
.search-bar select {
|
||||
padding: 10px 14px; border: 1px solid var(--gray-200);
|
||||
border-radius: 8px; font-size: 14px; outline: none;
|
||||
background: var(--white); cursor: pointer;
|
||||
}
|
||||
|
||||
/* BUTTONS */
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
padding: 10px 20px; border-radius: 8px;
|
||||
font-size: 14px; font-weight: 600; text-decoration: none;
|
||||
border: none; cursor: pointer;
|
||||
transition: all .15s;
|
||||
}
|
||||
.btn-primary { background: var(--cyan); color: var(--ink) }
|
||||
.btn-primary:hover { background: #1be5ff }
|
||||
.btn-outline { background: var(--white); color: var(--ink); border: 1.5px solid var(--gray-200) }
|
||||
.btn-outline:hover { border-color: var(--gray-500) }
|
||||
.btn-sm { padding: 6px 14px; font-size: 13px }
|
||||
.btn-danger { background: var(--red-bg); color: var(--red) }
|
||||
.btn-danger:hover { background: #FEE2E2 }
|
||||
|
||||
/* COMPANIES GRID */
|
||||
.companies-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.company-card {
|
||||
background: var(--white); border-radius: var(--radius);
|
||||
padding: 24px; box-shadow: var(--shadow);
|
||||
transition: box-shadow .2s; cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
position: relative;
|
||||
}
|
||||
.company-card:hover { box-shadow: var(--shadow-lg) }
|
||||
.company-card.selected { border-color: var(--cyan) }
|
||||
.company-card .company-name {
|
||||
font-size: 17px; font-weight: 700; margin-bottom: 4px;
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.company-card .company-avatar {
|
||||
width: 40px; height: 40px; border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 18px; font-weight: 700; color: var(--white);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.company-avatar.g1 { background: var(--indigo) }
|
||||
.company-avatar.g2 { background: #0891B2 }
|
||||
.company-avatar.g3 { background: #D946EF }
|
||||
.company-avatar.g4 { background: #EA580C }
|
||||
.company-avatar.g5 { background: #16A34A }
|
||||
|
||||
.company-card .task-counts {
|
||||
display: flex; gap: 12px; margin-top: 12px; font-size: 13px;
|
||||
}
|
||||
.company-card .task-counts span { display: flex; align-items: center; gap: 4px }
|
||||
.company-card .badge {
|
||||
display: inline-block; padding: 3px 10px; border-radius: 20px;
|
||||
font-size: 12px; font-weight: 600;
|
||||
}
|
||||
.company-card .actions { margin-top: 16px; display: flex; gap: 8px }
|
||||
|
||||
/* TASK TABLE */
|
||||
.task-table-wrap { overflow-x: auto }
|
||||
.task-table {
|
||||
width: 100%; border-collapse: collapse;
|
||||
background: var(--white); border-radius: var(--radius);
|
||||
box-shadow: var(--shadow); overflow: hidden;
|
||||
}
|
||||
.task-table th {
|
||||
text-align: left; padding: 14px 16px;
|
||||
font-size: 12px; font-weight: 700; color: var(--gray-500);
|
||||
text-transform: uppercase; letter-spacing: .5px;
|
||||
border-bottom: 1px solid var(--gray-200);
|
||||
background: var(--gray-100);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.task-table td {
|
||||
padding: 14px 16px; font-size: 14px;
|
||||
border-bottom: 1px solid var(--gray-100);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.task-table tr:hover td { background: var(--cyan-50) }
|
||||
.task-table tr:last-child td { border-bottom: none }
|
||||
|
||||
.status-pill {
|
||||
display: inline-block; padding: 4px 12px; border-radius: 20px;
|
||||
font-size: 12px; font-weight: 600;
|
||||
}
|
||||
.status-pending { background: var(--amber-bg); color: var(--amber) }
|
||||
.status-progress { background: var(--indigo-bg); color: var(--indigo) }
|
||||
.status-done { background: var(--green-bg); color: var(--green) }
|
||||
.status-overdue { background: var(--red-bg); color: var(--red) }
|
||||
|
||||
.priority-dot {
|
||||
width: 8px; height: 8px; border-radius: 50%; display: inline-block; margin-right: 6px;
|
||||
}
|
||||
.priority-high { background: var(--red) }
|
||||
.priority-mid { background: var(--amber) }
|
||||
.priority-low { background: var(--gray-200) }
|
||||
|
||||
/* FAQ */
|
||||
.faq-item {
|
||||
background: var(--white); border-radius: var(--radius);
|
||||
box-shadow: var(--shadow); margin-bottom: 8px; overflow: hidden;
|
||||
}
|
||||
.faq-q {
|
||||
padding: 18px 24px; cursor: pointer;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
font-weight: 600; font-size: 16px;
|
||||
user-select: none;
|
||||
}
|
||||
.faq-q:hover { background: var(--gray-100) }
|
||||
.faq-q .arrow { transition: transform .2s; font-size: 12px; color: var(--gray-500) }
|
||||
.faq-item.open .faq-q .arrow { transform: rotate(180deg) }
|
||||
.faq-a {
|
||||
padding: 0 24px 18px; font-size: 15px; color: var(--gray-500);
|
||||
line-height: 1.7; display: none;
|
||||
}
|
||||
.faq-item.open .faq-a { display: block }
|
||||
|
||||
/* MODAL */
|
||||
.modal-overlay {
|
||||
position: fixed; inset: 0; z-index: 200;
|
||||
background: rgba(15,18,24,.5);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
display: none;
|
||||
}
|
||||
.modal-overlay.open { display: flex }
|
||||
.modal {
|
||||
background: var(--white); border-radius: 16px;
|
||||
padding: 32px; max-width: 500px; width: 90%;
|
||||
box-shadow: var(--shadow-lg); max-height: 85vh; overflow-y: auto;
|
||||
}
|
||||
.modal h3 { font-size: 20px; margin-bottom: 20px }
|
||||
.modal label {
|
||||
display: block; font-size: 13px; font-weight: 600;
|
||||
color: var(--gray-500); margin-bottom: 4px; margin-top: 16px;
|
||||
}
|
||||
.modal input, .modal select, .modal textarea {
|
||||
width: 100%; padding: 10px 14px; border: 1px solid var(--gray-200);
|
||||
border-radius: 8px; font-size: 14px; outline: none; font-family: inherit;
|
||||
}
|
||||
.modal input:focus, .modal select:focus, .modal textarea:focus { border-color: var(--cyan) }
|
||||
.modal textarea { resize: vertical; min-height: 80px }
|
||||
.modal .btn-row { display: flex; gap: 10px; margin-top: 24px; justify-content: flex-end }
|
||||
|
||||
/* EMPTY */
|
||||
.empty { text-align: center; padding: 60px 20px; color: var(--gray-500) }
|
||||
.empty .icon { font-size: 48px; margin-bottom: 12px }
|
||||
|
||||
/* FOOTER */
|
||||
.footer {
|
||||
text-align: center; padding: 32px 24px;
|
||||
color: var(--gray-500); font-size: 13px;
|
||||
border-top: 1px solid var(--gray-200); margin-top: 64px;
|
||||
}
|
||||
|
||||
/* TABS */
|
||||
.tabs { display: flex; gap: 4px; margin-bottom: 24px; background: var(--white); border-radius: 10px; padding: 4px; box-shadow: var(--shadow); width: fit-content }
|
||||
.tab-btn {
|
||||
padding: 10px 20px; border-radius: 8px; border: none;
|
||||
background: transparent; font-size: 14px; font-weight: 600;
|
||||
cursor: pointer; color: var(--gray-500); transition: all .15s;
|
||||
}
|
||||
.tab-btn.active { background: var(--ink); color: var(--white) }
|
||||
.tab-content { display: none }
|
||||
.tab-content.active { display: block }
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.stats { grid-template-columns: repeat(2, 1fr) }
|
||||
.hero h1 { font-size: 28px }
|
||||
.companies-grid { grid-template-columns: 1fr }
|
||||
.nav-links { display: none }
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
.stats { grid-template-columns: 1fr }
|
||||
.section-header { flex-direction: column; align-items: flex-start }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="nav">
|
||||
<div class="container">
|
||||
<a href="#" class="nav-logo">
|
||||
<div class="dot">📋</div>
|
||||
HR Ассистент
|
||||
</a>
|
||||
<div class="nav-links">
|
||||
<a href="#companies" class="active">Компании</a>
|
||||
<a href="#tasks">Поручения</a>
|
||||
<a href="#faq">HR FAQ</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="hero">
|
||||
<div class="container">
|
||||
<h1>Контроль поручений портфельным компаниям</h1>
|
||||
<p class="sub">Отслеживайте задачи по всем портфельным компаниям АО «Самрук-Қазына» в одном окне</p>
|
||||
<div class="stats" id="stats"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- COMPANIES SECTION -->
|
||||
<div id="companies">
|
||||
<div class="section-header">
|
||||
<h2>Портфельные компании</h2>
|
||||
<div class="search-bar">
|
||||
<input type="text" id="companySearch" placeholder="Поиск по названию..." oninput="renderCompanies()">
|
||||
<button class="btn btn-primary" onclick="openModal('company')">+ Добавить компанию</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="if(confirm('Сбросить все данные к стандартным?')){localStorage.clear();location.reload()}" title="Сбросить данные">↺ Сброс</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="companies-grid" id="companiesGrid"></div>
|
||||
</div>
|
||||
|
||||
<!-- TASKS SECTION -->
|
||||
<div id="tasks" style="margin-top:56px">
|
||||
<div class="section-header">
|
||||
<h2>Поручения</h2>
|
||||
<div class="search-bar">
|
||||
<input type="text" id="taskSearch" placeholder="Поиск поручения..." oninput="renderTasks()">
|
||||
<select id="taskFilterCompany" onchange="renderTasks()">
|
||||
<option value="">Все компании</option>
|
||||
</select>
|
||||
<select id="taskFilterStatus" onchange="renderTasks()">
|
||||
<option value="">Все статусы</option>
|
||||
<option value="pending">Ожидает</option>
|
||||
<option value="progress">В работе</option>
|
||||
<option value="done">Готово</option>
|
||||
<option value="overdue">Просрочено</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" onclick="openModal('task')">+ Добавить поручение</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-table-wrap" id="taskTableWrap"></div>
|
||||
</div>
|
||||
|
||||
<!-- FAQ -->
|
||||
<div id="faq" style="margin-top:56px">
|
||||
<div class="section-header"><h2>HR FAQ</h2></div>
|
||||
<div id="faqList"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
HR Ассистент — инструмент для контроля поручений © 2026
|
||||
</footer>
|
||||
|
||||
<!-- MODALS -->
|
||||
<div class="modal-overlay" id="modalOverlay" onclick="if(event.target===this)closeModal()">
|
||||
<div class="modal" id="modalContent"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ====== DATA MODEL (localStorage) ======
|
||||
|
||||
const defaultCompanies = [
|
||||
{ id: 'c1', name: 'АО «НК «КазМунайГаз»', color: 'g1' },
|
||||
{ id: 'c2', name: 'АО «НК «ҚТЖ»', color: 'g2' },
|
||||
{ id: 'c3', name: 'АО «НАК «Казатомпром»', color: 'g3' },
|
||||
{ id: 'c4', name: 'АО «Самрук-Энерго»', color: 'g4' },
|
||||
{ id: 'c5', name: 'АО «KEGOC»', color: 'g5' },
|
||||
{ id: 'c6', name: 'АО «Казпочта»', color: 'g1' },
|
||||
{ id: 'c7', name: 'АО «Эйр Астана»', color: 'g2' },
|
||||
{ id: 'c8', name: 'АО «НК «QazaqGaz»', color: 'g3' },
|
||||
{ id: 'c9', name: 'АО «Тау-Кен Самрук»', color: 'g4' },
|
||||
{ id: 'c10', name: 'АО «Казахтелеком»', color: 'g5' },
|
||||
{ id: 'c11', name: 'АО «НК «Kazakhstan Engineering»', color: 'g1' },
|
||||
{ id: 'c12', name: 'ТОО «ОХК»', color: 'g2' },
|
||||
{ id: 'c13', name: 'АО «КазАвтоЖол»', color: 'g3' },
|
||||
{ id: 'c14', name: 'АО «Qazaq Air»', color: 'g4' },
|
||||
{ id: 'c15', name: 'ТОО «Samruk-Kazyna Construction»', color: 'g5' },
|
||||
{ id: 'c16', name: 'ТОО «Samruk-Kazyna Trust»', color: 'g1' },
|
||||
{ id: 'c17', name: 'АО «Самрук-Қазына Контракт»', color: 'g2' },
|
||||
{ id: 'c18', name: 'АО «KTZ Express»', color: 'g3' },
|
||||
];
|
||||
|
||||
const defaultTasks = [
|
||||
{ id: 't1', companyId: 'c1', title: 'Подготовить отчёт по охране труда за Q1', status: 'pending', deadline: '2026-06-20', priority: 'high' },
|
||||
{ id: 't2', companyId: 'c2', title: 'Обновить штатное расписание', status: 'progress', deadline: '2026-06-25', priority: 'mid' },
|
||||
{ id: 't3', companyId: 'c3', title: 'Согласовать KPI руководителей', status: 'done', deadline: '2026-06-01', priority: 'high' },
|
||||
{ id: 't4', companyId: 'c1', title: 'Провести аудит условий труда', status: 'overdue', deadline: '2026-05-15', priority: 'high' },
|
||||
{ id: 't5', companyId: 'c4', title: 'Организовать обучение по ТБ', status: 'pending', deadline: '2026-07-01', priority: 'mid' },
|
||||
{ id: 't6', companyId: 'c5', title: 'Собрать данные по текучести кадров', status: 'progress', deadline: '2026-06-18', priority: 'low' },
|
||||
{ id: 't7', companyId: 'c6', title: 'Проверить трудовые договоры', status: 'pending', deadline: '2026-06-30', priority: 'mid' },
|
||||
{ id: 't8', companyId: 'c2', title: 'Запустить программу наставничества', status: 'progress', deadline: '2026-06-10', priority: 'high' },
|
||||
{ id: 't9', companyId: 'c8', title: 'Внедрить систему оценки персонала', status: 'overdue', deadline: '2026-04-20', priority: 'high' },
|
||||
{ id: 't10', companyId: 'c9', title: 'Актуализировать должностные инструкции', status: 'pending', deadline: '2026-07-10', priority: 'low' },
|
||||
];
|
||||
|
||||
const DATA_VERSION = 'sk-v2';
|
||||
|
||||
function loadData() {
|
||||
const ver = localStorage.getItem('hr-version');
|
||||
if (ver !== DATA_VERSION) {
|
||||
localStorage.clear();
|
||||
localStorage.setItem('hr-version', DATA_VERSION);
|
||||
return { companies: defaultCompanies, tasks: defaultTasks };
|
||||
}
|
||||
const c = localStorage.getItem('hr-companies');
|
||||
const t = localStorage.getItem('hr-tasks');
|
||||
return {
|
||||
companies: c ? JSON.parse(c) : defaultCompanies,
|
||||
tasks: t ? JSON.parse(t) : defaultTasks,
|
||||
};
|
||||
}
|
||||
|
||||
function saveData(companies, tasks) {
|
||||
localStorage.setItem('hr-companies', JSON.stringify(companies));
|
||||
localStorage.setItem('hr-tasks', JSON.stringify(tasks));
|
||||
}
|
||||
|
||||
let { companies, tasks } = loadData();
|
||||
|
||||
// ====== HELPERS ======
|
||||
|
||||
function getCompany(id) { return companies.find(c => c.id === id) }
|
||||
function taskCount(cid, status) { return tasks.filter(t => t.companyId === cid && t.status === status).length }
|
||||
|
||||
function totalFor(status) { return tasks.filter(t => t.status === status).length }
|
||||
|
||||
function openModal(type) {
|
||||
const overlay = document.getElementById('modalOverlay');
|
||||
const content = document.getElementById('modalContent');
|
||||
|
||||
if (type === 'company') {
|
||||
content.innerHTML = `
|
||||
<h3>Добавить компанию</h3>
|
||||
<label>Название компании</label>
|
||||
<input type="text" id="mCompanyName" placeholder="ТОО «...»">
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-outline" onclick="closeModal()">Отмена</button>
|
||||
<button class="btn btn-primary" onclick="addCompany()">Добавить</button>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
content.innerHTML = `
|
||||
<h3>Добавить поручение</h3>
|
||||
<label>Компания</label>
|
||||
<select id="mTaskCompany">
|
||||
${companies.map(c => `<option value="${c.id}">${esc(c.name)}</option>`).join('')}
|
||||
</select>
|
||||
<label>Описание поручения</label>
|
||||
<input type="text" id="mTaskTitle" placeholder="Что нужно сделать?">
|
||||
<label>Дедлайн</label>
|
||||
<input type="date" id="mTaskDeadline">
|
||||
<label>Приоритет</label>
|
||||
<select id="mTaskPriority">
|
||||
<option value="high">Высокий</option>
|
||||
<option value="mid" selected>Средний</option>
|
||||
<option value="low">Низкий</option>
|
||||
</select>
|
||||
<label>Статус</label>
|
||||
<select id="mTaskStatus">
|
||||
<option value="pending">Ожидает</option>
|
||||
<option value="progress">В работе</option>
|
||||
<option value="done">Готово</option>
|
||||
<option value="overdue">Просрочено</option>
|
||||
</select>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-outline" onclick="closeModal()">Отмена</button>
|
||||
<button class="btn btn-primary" onclick="addTask()">Добавить</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
overlay.classList.add('open');
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modalOverlay').classList.remove('open');
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = s;
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function uid() { return 'x' + Date.now() + Math.random().toString(36).slice(2, 8) }
|
||||
|
||||
function addCompany() {
|
||||
const name = document.getElementById('mCompanyName').value.trim();
|
||||
if (!name) return;
|
||||
const colors = ['g1','g2','g3','g4','g5'];
|
||||
companies.push({ id: uid(), name, color: colors[Math.floor(Math.random() * colors.length)] });
|
||||
saveData(companies, tasks);
|
||||
closeModal();
|
||||
renderAll();
|
||||
}
|
||||
|
||||
function addTask() {
|
||||
const companyId = document.getElementById('mTaskCompany').value;
|
||||
const title = document.getElementById('mTaskTitle').value.trim();
|
||||
const deadline = document.getElementById('mTaskDeadline').value;
|
||||
const priority = document.getElementById('mTaskPriority').value;
|
||||
const status = document.getElementById('mTaskStatus').value;
|
||||
if (!title) return;
|
||||
tasks.push({ id: uid(), companyId, title, deadline, priority, status });
|
||||
saveData(companies, tasks);
|
||||
closeModal();
|
||||
renderAll();
|
||||
}
|
||||
|
||||
function deleteTask(id) {
|
||||
tasks = tasks.filter(t => t.id !== id);
|
||||
saveData(companies, tasks);
|
||||
renderAll();
|
||||
}
|
||||
|
||||
function deleteCompany(id) {
|
||||
if (!confirm('Удалить компанию и все её поручения?')) return;
|
||||
companies = companies.filter(c => c.id !== id);
|
||||
tasks = tasks.filter(t => t.companyId !== id);
|
||||
saveData(companies, tasks);
|
||||
renderAll();
|
||||
}
|
||||
|
||||
function changeTaskStatus(id, status) {
|
||||
const t = tasks.find(t => t.id === id);
|
||||
if (t) { t.status = status; saveData(companies, tasks); renderAll(); }
|
||||
}
|
||||
|
||||
// ====== RENDER ======
|
||||
|
||||
const priorityLabels = { high: 'Высокий', mid: 'Средний', low: 'Низкий' };
|
||||
const statusLabels = { pending: 'Ожидает', progress: 'В работе', done: 'Готово', overdue: 'Просрочено' };
|
||||
|
||||
function renderStats() {
|
||||
const totalCompanies = companies.length;
|
||||
const active = totalFor('pending') + totalFor('progress');
|
||||
const done = totalFor('done');
|
||||
const overdue = totalFor('overdue');
|
||||
|
||||
document.getElementById('stats').innerHTML = `
|
||||
<div class="stat-card">
|
||||
<div class="label">Компаний</div>
|
||||
<div class="value" style="color:var(--indigo)">${totalCompanies}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">Активных поручений</div>
|
||||
<div class="value" style="color:var(--amber)">${active}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">Выполнено</div>
|
||||
<div class="value" style="color:var(--green)">${done}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">Просрочено</div>
|
||||
<div class="value" style="color:var(--red)">${overdue}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderCompanies() {
|
||||
const search = (document.getElementById('companySearch')?.value || '').toLowerCase();
|
||||
const filtered = companies.filter(c => c.name.toLowerCase().includes(search));
|
||||
|
||||
const grid = document.getElementById('companiesGrid');
|
||||
if (filtered.length === 0) {
|
||||
grid.innerHTML = '<div class="empty"><div class="icon">🏢</div><p>Компании не найдены</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
grid.innerHTML = filtered.map(c => {
|
||||
const pending = taskCount(c.id, 'pending');
|
||||
const progress = taskCount(c.id, 'progress');
|
||||
const done = taskCount(c.id, 'done');
|
||||
const overdue = taskCount(c.id, 'overdue');
|
||||
return `
|
||||
<div class="company-card" onclick="document.getElementById('taskFilterCompany').value='${c.id}';document.getElementById('tasks').scrollIntoView({behavior:'smooth'});renderTasks()">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between">
|
||||
<div style="display:flex;align-items:center;gap:12px;min-width:0">
|
||||
<div class="company-avatar ${c.color}">${c.name.charAt(0)}</div>
|
||||
<div class="company-name" style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(c.name)}</div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger" onclick="event.stopPropagation();deleteCompany('${c.id}')" title="Удалить">🗑</button>
|
||||
</div>
|
||||
<div class="task-counts">
|
||||
<span title="Ожидает"><span class="dot-indicator" style="background:var(--amber)"></span>${pending} ожидает</span>
|
||||
<span title="В работе"><span class="dot-indicator" style="background:var(--indigo)"></span>${progress} в работе</span>
|
||||
<span title="Готово"><span class="dot-indicator" style="background:var(--green)"></span>${done} готово</span>
|
||||
<span title="Просрочено"><span class="dot-indicator" style="background:var(--red)"></span>${overdue} просрочено</span>
|
||||
</div>
|
||||
${overdue > 0 ? `<div style="margin-top:12px"><span class="badge" style="background:var(--red-bg);color:var(--red)">⚠️ Есть просроченные</span></div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function renderTasks() {
|
||||
const searchText = (document.getElementById('taskSearch')?.value || '').toLowerCase();
|
||||
const filterCompany = document.getElementById('taskFilterCompany')?.value || '';
|
||||
const filterStatus = document.getElementById('taskFilterStatus')?.value || '';
|
||||
|
||||
let filtered = tasks;
|
||||
if (searchText) filtered = filtered.filter(t => t.title.toLowerCase().includes(searchText));
|
||||
if (filterCompany) filtered = filtered.filter(t => t.companyId === filterCompany);
|
||||
if (filterStatus) filtered = filtered.filter(t => t.status === filterStatus);
|
||||
|
||||
// update company filter dropdown
|
||||
const sel = document.getElementById('taskFilterCompany');
|
||||
if (sel) {
|
||||
const cur = sel.value;
|
||||
sel.innerHTML = '<option value="">Все компании</option>' +
|
||||
companies.map(c => `<option value="${c.id}" ${c.id === cur ? 'selected' : ''}>${esc(c.name)}</option>`).join('');
|
||||
}
|
||||
|
||||
const wrap = document.getElementById('taskTableWrap');
|
||||
if (filtered.length === 0) {
|
||||
wrap.innerHTML = '<div class="empty"><div class="icon">📝</div><p>Поручений нет</p></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
wrap.innerHTML = `
|
||||
<table class="task-table">
|
||||
<thead><tr>
|
||||
<th>Компания</th>
|
||||
<th>Поручение</th>
|
||||
<th>Дедлайн</th>
|
||||
<th>Приоритет</th>
|
||||
<th>Статус</th>
|
||||
<th></th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
${filtered.map(t => {
|
||||
const c = getCompany(t.companyId);
|
||||
const deadlineDisplay = t.deadline ? new Date(t.deadline).toLocaleDateString('ru-RU') : '—';
|
||||
return `
|
||||
<tr>
|
||||
<td style="font-weight:600">${c ? esc(c.name) : '—'}</td>
|
||||
<td style="max-width:300px;overflow:hidden;text-overflow:ellipsis">${esc(t.title)}</td>
|
||||
<td>${deadlineDisplay}</td>
|
||||
<td><span class="priority-dot priority-${t.priority}"></span>${priorityLabels[t.priority] || t.priority}</td>
|
||||
<td>
|
||||
<select class="status-pill status-${t.status}" style="border:none;cursor:pointer;font-family:inherit;font-weight:600;font-size:12px;padding:4px 12px;border-radius:20px;background:var(--${t.status === 'pending' ? 'amber' : t.status === 'progress' ? 'indigo' : t.status === 'done' ? 'green' : 'red'}-bg);color:var(--${t.status === 'pending' ? 'amber' : t.status === 'progress' ? 'indigo' : t.status === 'done' ? 'green' : 'red'})" onchange="changeTaskStatus('${t.id}', this.value)">
|
||||
<option value="pending" ${t.status==='pending'?'selected':''}>Ожидает</option>
|
||||
<option value="progress" ${t.status==='progress'?'selected':''}>В работе</option>
|
||||
<option value="done" ${t.status==='done'?'selected':''}>Готово</option>
|
||||
<option value="overdue" ${t.status==='overdue'?'selected':''}>Просрочено</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><button class="btn btn-sm btn-danger" onclick="deleteTask('${t.id}')">🗑</button></td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
`;
|
||||
}
|
||||
|
||||
// ====== FAQ ======
|
||||
const faqData = [
|
||||
{ q: 'Как оформить приказ о приёме на работу?', a: 'Приказ оформляется по форме Т-1 в течение 3 рабочих дней после подписания трудового договора. Обязательно ознакомить сотрудника под подпись.' },
|
||||
{ q: 'Какие сроки уведомления о сокращении?', a: 'Работодатель обязан письменно уведомить сотрудника не менее чем за 1 месяц до расторжения трудового договора по сокращению штата (ст. 53 ТК РК).' },
|
||||
{ q: 'Как рассчитать отпускные?', a: 'Средний дневной заработок = сумма зарплаты за 12 месяцев / количество рабочих дней. Умножаем на количество дней отпуска. Выплатить не позднее чем за 3 дня до начала.' },
|
||||
{ q: 'Обязан ли работодатель проводить медосмотр?', a: 'Да, для сотрудников занятых на тяжёлых работах, с вредными/опасными условиями труда, а также для работников пищевой промышленности, медицины и образования — обязательно.' },
|
||||
{ q: 'Как перевести сотрудника в другую дочернюю компанию?', a: 'Перевод оформляется через увольнение из одной компании и приём в другую, либо через трёхстороннее соглашение о переводе (ст. 42 ТК РК). В обоих случаях требуется письменное согласие сотрудника.' },
|
||||
{ q: 'Что делать при несчастном случае на производстве?', a: 'Оказать первую помощь → вызвать скорую → сохранить место происшествия → создать комиссию по расследованию → уведомить госинспекцию труда в течение суток → оформить акт Н-1.' },
|
||||
{ q: 'Можно ли установить испытательный срок для руководителя?', a: 'Да, до 3 месяцев. Для руководителей организаций и их заместителей, главных бухгалтеров — до 6 месяцев (ст. 36 ТК РК).' },
|
||||
{ q: 'Как контролировать исполнение поручений дочерним компаниям?', a: 'Рекомендуется: 1) Письменная фиксация поручений с дедлайнами, 2) Еженедельный мониторинг статусов, 3) Единая система отчётности, 4) Регулярные статус-встречи с руководителями ДК.' },
|
||||
];
|
||||
|
||||
function renderFAQ() {
|
||||
document.getElementById('faqList').innerHTML = faqData.map((item, i) => `
|
||||
<div class="faq-item">
|
||||
<div class="faq-q" onclick="this.parentElement.classList.toggle('open')">
|
||||
<span>${esc(item.q)}</span>
|
||||
<span class="arrow">▼</span>
|
||||
</div>
|
||||
<div class="faq-a">${esc(item.a)}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// ====== RENDER ALL ======
|
||||
function renderAll() {
|
||||
renderStats();
|
||||
renderCompanies();
|
||||
renderTasks();
|
||||
}
|
||||
|
||||
renderAll();
|
||||
renderFAQ();
|
||||
|
||||
// Nav smooth scroll
|
||||
document.querySelectorAll('.nav-links a').forEach(a => {
|
||||
a.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(a.getAttribute('href'));
|
||||
if (target) target.scrollIntoView({ behavior: 'smooth' });
|
||||
document.querySelectorAll('.nav-links a').forEach(x => x.classList.remove('active'));
|
||||
a.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Keyboard shortcut
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') closeModal();
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
document.getElementById('taskSearch').focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user