From bcc0954b2adfbafa105312495da6399b7416e961 Mon Sep 17 00:00:00 2001 From: Dauren777 Date: Tue, 11 Aug 2026 09:27:27 +0000 Subject: [PATCH] =?UTF-8?q?v4:=20=D0=B0=D0=BA=D0=BA=D0=B0=D1=83=D0=BD?= =?UTF-8?q?=D1=82=D1=82=D0=B0=D1=80=D0=B4=D1=8B=20=D0=B1=D0=B0=D1=81=D2=9B?= =?UTF-8?q?=D0=B0=D1=80=D1=83,=20=D2=9B=D2=B1=D0=BF=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=D3=A9=D0=B7=D0=B4=D1=96=20=D2=9B=D0=B0=D0=BB=D0=BF=D1=8B=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BA=D0=B5=D0=BB=D1=82=D1=96=D1=80=D1=83,=20=D0=B6?= =?UTF-8?q?=D0=B5=D0=BA=D0=B5=20=D1=85=D0=B0=D1=82=D1=82=D0=B0=D1=80=D2=93?= =?UTF-8?q?=D0=B0=20=D0=B6=D0=B0=D1=83=D0=B0=D0=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data.json | 427 +++++++++++++++++++++++++++++++++++++++++++++-------- index.html | 7 + script.js | 263 +++++++++++++++++++++++++++++++-- server.js | 102 ++++++++++++- style.css | 31 ++++ users.json | 6 + 6 files changed, 759 insertions(+), 77 deletions(-) create mode 100644 users.json diff --git a/data.json b/data.json index 58289ee..82aa1a4 100644 --- a/data.json +++ b/data.json @@ -1,54 +1,224 @@ { - "users": [ - { "login": "1001", "password": "Admin123", "name": "Асет (Комплаенс офицер)", "role": "admin" }, - { "login": "2001", "password": "Worker123", "name": "Айгүл Серікқызы", "role": "user" }, - { "login": "2002", "password": "Worker123", "name": "Бауыржан Нұрланұлы", "role": "user" }, - { "login": "2003", "password": "Worker123", "name": "Дана Қайратқызы", "role": "user" } - ], "announcements": [ - { "id": 1, "title": "Жылдық комплаенс оқуы басталды", "text": "Барлық қызметкерлер 1 қарашаға дейін «Сыбайлас жемқорлыққа қарсы» тестін тапсыруы қажет. Видео сабақтар мен заңдар бөлімін қарап шығыңыз.", "date": "2026-08-11" }, - { "id": 2, "title": "Мүдделер қақтығысын декларациялау", "text": "Қызметкерлер жыл сайын мүдделер қақтығысы туралы декларация толтырады. Нысан шағым жәшігі бөлімінен алуға болады.", "date": "2026-08-05" } + { + "id": 1, + "title": "Жылдық комплаенс оқуы басталды", + "text": "Барлық қызметкерлер 1 қарашаға дейін «Сыбайлас жемқорлыққа қарсы» тестін тапсыруы қажет. Видео сабақтар мен заңдар бөлімін қарап шығыңыз.", + "date": "2026-08-11" + }, + { + "id": 2, + "title": "Мүдделер қақтығысын декларациялау", + "text": "Қызметкерлер жыл сайын мүдделер қақтығысы туралы декларация толтырады. Нысан шағым жәшігі бөлімінен алуға болады.", + "date": "2026-08-05" + } ], "org": [ - { "level": 1, "name": "Бас директор", "person": "Ерлан Қасымов", "email": "e.kasymov@qonaev.kz", "phone": "+7 (727) 300-10-01" }, - { "level": 2, "name": "Басқарма төрағасы", "person": "Гүлнара Әбенова", "email": "g.abenova@qonaev.kz", "phone": "+7 (727) 300-10-02" }, - { "level": 3, "name": "Қаржы дирекциясы", "person": "Серік Жұмабаев", "email": "s.zhumabaev@qonaev.kz", "phone": "+7 (727) 300-10-03" }, - { "level": 3, "name": "Эфир дирекциясы", "person": "Айжан Төлегенова", "email": "a.tolegenova@qonaev.kz", "phone": "+7 (727) 300-10-04" }, - { "level": 3, "name": "Жаңалықтар дирекциясы", "person": "Марат Бекенов", "email": "m.bekenov@qonaev.kz", "phone": "+7 (727) 300-10-05" }, - { "level": 3, "name": "Техникалық дирекция", "person": "Нұрлан Сыдықов", "email": "n.sydykov@qonaev.kz", "phone": "+7 (727) 300-10-06" }, - { "level": 3, "name": "HR және әкімшілік", "person": "Ләззат Қалиева", "email": "l.kalieva@qonaev.kz", "phone": "+7 (727) 300-10-07" }, - { "level": 4, "name": "Комплаенс қызметі", "person": "Асет (сіз)", "email": "compliance@qonaev.kz", "phone": "+7 (727) 300-10-08" } + { + "level": 1, + "name": "Бас директор", + "person": "Ерлан Қасымов", + "email": "e.kasymov@qonaev.kz", + "phone": "+7 (727) 300-10-01" + }, + { + "level": 2, + "name": "Басқарма төрағасы", + "person": "Гүлнара Әбенова", + "email": "g.abenova@qonaev.kz", + "phone": "+7 (727) 300-10-02" + }, + { + "level": 3, + "name": "Қаржы дирекциясы", + "person": "Серік Жұмабаев", + "email": "s.zhumabaev@qonaev.kz", + "phone": "+7 (727) 300-10-03" + }, + { + "level": 3, + "name": "Эфир дирекциясы", + "person": "Айжан Төлегенова", + "email": "a.tolegenova@qonaev.kz", + "phone": "+7 (727) 300-10-04" + }, + { + "level": 3, + "name": "Жаңалықтар дирекциясы", + "person": "Марат Бекенов", + "email": "m.bekenov@qonaev.kz", + "phone": "+7 (727) 300-10-05" + }, + { + "level": 3, + "name": "Техникалық дирекция", + "person": "Нұрлан Сыдықов", + "email": "n.sydykov@qonaev.kz", + "phone": "+7 (727) 300-10-06" + }, + { + "level": 3, + "name": "HR және әкімшілік", + "person": "Ләззат Қалиева", + "email": "l.kalieva@qonaev.kz", + "phone": "+7 (727) 300-10-07" + }, + { + "level": 4, + "name": "Комплаенс қызметі", + "person": "Асет (сіз)", + "email": "compliance@qonaev.kz", + "phone": "+7 (727) 300-10-08" + } ], "contacts": [ - { "org": "Антикор (ҚР Антикоррупциялық қызметі) Алматы қаласы бойынша департаменті", "type": "Ресми телефон", "phone": "8 (727) 278-73-24", "email": "almaty@nab.gov.kz", "note": "Мекенжай: Жібек Жолы даңғылы, 15" }, - { "org": "Антикор бірыңғай Call-орталығы", "type": "Сенім телефоны", "phone": "1424", "email": "", "note": "Қазақстан бойынша қоңырау тегін" }, - { "org": "Антикор WhatsApp-чат-бот", "type": "WhatsApp", "phone": "8 777 000 1424", "email": "", "note": "Хабарлама жіберуге болады" }, - { "org": "Алматы қаласы прокуратурасы", "type": "Кеңсе", "phone": "8 (727) 225-40-86", "email": "", "note": "Мекенжай: Желтоқсан көшесі, 189" }, - { "org": "Алматы қаласы прокуратурасы", "type": "Кезекші прокурор", "phone": "8 (727) 330-80-51", "email": "", "note": "Тәулік бойы" }, - { "org": "ҚР Бас прокуратурасы Call-орталығы", "type": "Сенім телефоны", "phone": "115", "email": "", "note": "Дс-жм 09:00–18:30, қоңырау тегін" }, - { "org": "Алматы қаласы Еңбек инспекциясы басқармасы", "type": "Ресми телефон", "phone": "+7 (727) 338-22-42", "email": "upr_gitm@mail.ru", "note": "Мекенжай: Достық даңғылы, 85" }, - { "org": "Алматы қаласы Еңбек инспекциясы (канцелярия)", "type": "Телефон", "phone": "8 (727) 374-94-47", "email": "kgu2016@mail.ru", "note": "" }, - { "org": "Алматы қаласы бойынша Мемлекеттік кірістер департаменті", "type": "Ресми телефон", "phone": "8 (7272) 267-15-54", "email": "", "note": "Мекенжай: Бөгенбай батыр көшесі, 132" } + { + "org": "Антикор (ҚР Антикоррупциялық қызметі) Алматы қаласы бойынша департаменті", + "type": "Ресми телефон", + "phone": "8 (727) 278-73-24", + "email": "almaty@nab.gov.kz", + "note": "Мекенжай: Жібек Жолы даңғылы, 15" + }, + { + "org": "Антикор бірыңғай Call-орталығы", + "type": "Сенім телефоны", + "phone": "1424", + "email": "", + "note": "Қазақстан бойынша қоңырау тегін" + }, + { + "org": "Антикор WhatsApp-чат-бот", + "type": "WhatsApp", + "phone": "8 777 000 1424", + "email": "", + "note": "Хабарлама жіберуге болады" + }, + { + "org": "Алматы қаласы прокуратурасы", + "type": "Кеңсе", + "phone": "8 (727) 225-40-86", + "email": "", + "note": "Мекенжай: Желтоқсан көшесі, 189" + }, + { + "org": "Алматы қаласы прокуратурасы", + "type": "Кезекші прокурор", + "phone": "8 (727) 330-80-51", + "email": "", + "note": "Тәулік бойы" + }, + { + "org": "ҚР Бас прокуратурасы Call-орталығы", + "type": "Сенім телефоны", + "phone": "115", + "email": "", + "note": "Дс-жм 09:00–18:30, қоңырау тегін" + }, + { + "org": "Алматы қаласы Еңбек инспекциясы басқармасы", + "type": "Ресми телефон", + "phone": "+7 (727) 338-22-42", + "email": "upr_gitm@mail.ru", + "note": "Мекенжай: Достық даңғылы, 85" + }, + { + "org": "Алматы қаласы Еңбек инспекциясы (канцелярия)", + "type": "Телефон", + "phone": "8 (727) 374-94-47", + "email": "kgu2016@mail.ru", + "note": "" + }, + { + "org": "Алматы қаласы бойынша Мемлекеттік кірістер департаменті", + "type": "Ресми телефон", + "phone": "8 (7272) 267-15-54", + "email": "", + "note": "Мекенжай: Бөгенбай батыр көшесі, 132" + } ], "videos": [ - { "id": 1, "title": "Комплаенс деген не?", "desc": "Кіріспе: комплаенс жүйесінің негіздері, мақсаттары және рөлі.", "dur": "12 мин", "thumb": "https://picsum.photos/seed/compl1/480/270" }, - { "id": 2, "title": "Сыбайлас жемқорлыққа қарсы заңнама", "desc": "ҚР заңдары, тыйымдар мен жауапкершілік туралы негізгі ережелер.", "dur": "18 мин", "thumb": "https://picsum.photos/seed/compl2/480/270" }, - { "id": 3, "title": "Мүдделер қақтығысы", "desc": "Мүдделер қақтығысын қалай тану және декларациялау керек.", "dur": "15 мин", "thumb": "https://picsum.photos/seed/compl3/480/270" }, - { "id": 4, "title": "Сыйлықтар мен белгілер ережелері", "desc": "Қызметкерлерге қандай сыйлықтар қабылдауға рұқсат етіледі.", "dur": "10 мин", "thumb": "https://picsum.photos/seed/compl4/480/270" } + { + "id": 1, + "title": "Комплаенс деген не?", + "desc": "Кіріспе: комплаенс жүйесінің негіздері, мақсаттары және рөлі.", + "dur": "12 мин", + "thumb": "https://picsum.photos/seed/compl1/480/270" + }, + { + "id": 2, + "title": "Сыбайлас жемқорлыққа қарсы заңнама", + "desc": "ҚР заңдары, тыйымдар мен жауапкершілік туралы негізгі ережелер.", + "dur": "18 мин", + "thumb": "https://picsum.photos/seed/compl2/480/270" + }, + { + "id": 3, + "title": "Мүдделер қақтығысы", + "desc": "Мүдделер қақтығысын қалай тану және декларациялау керек.", + "dur": "15 мин", + "thumb": "https://picsum.photos/seed/compl3/480/270" + }, + { + "id": 4, + "title": "Сыйлықтар мен белгілер ережелері", + "desc": "Қызметкерлерге қандай сыйлықтар қабылдауға рұқсат етіледі.", + "dur": "10 мин", + "thumb": "https://picsum.photos/seed/compl4/480/270" + } ], "laws": [ - { "id": 1, "cat": "Антикор", "title": "«Сыбайлас жемқорлыққа қарсы іс-қимыл туралы» ҚР Заңы", "text": "Қызметкерлердің жемқорлық құқық бұзушылықтар жасауына тыйым салады, кірістер декларациясын міндеттейді." }, - { "id": 2, "cat": "Еңбек", "title": "ҚР Еңбек кодексі", "text": "Қызметкерлердің құқықтары мен міндеттері, еңбек тәртібі, жауапкершілік нормалары." }, - { "id": 3, "cat": "Антикор", "title": "Мүдделер қақтығысы туралы ереже", "text": "Компания ішіндегі мүдделер қақтығысын анықтау, жариялау және шешу тәртібі." }, - { "id": 4, "cat": "Ақпарат", "title": "«Ақпаратқа қол жеткізу туралы» ҚР Заңы", "text": "Қоғамдық ақпаратқа қол жеткізу, құпия ақпаратты қорғау талаптары." }, - { "id": 5, "cat": "Этика", "title": "Компанияның Іскерлік этика кодексі", "text": "Қызметкерлердің мінез-құлық стандарттары, қарым-қатынас ережелері." } + { + "id": 1, + "cat": "Антикор", + "title": "«Сыбайлас жемқорлыққа қарсы іс-қимыл туралы» ҚР Заңы", + "text": "Қызметкерлердің жемқорлық құқық бұзушылықтар жасауына тыйым салады, кірістер декларациясын міндеттейді." + }, + { + "id": 2, + "cat": "Еңбек", + "title": "ҚР Еңбек кодексі", + "text": "Қызметкерлердің құқықтары мен міндеттері, еңбек тәртібі, жауапкершілік нормалары." + }, + { + "id": 3, + "cat": "Антикор", + "title": "Мүдделер қақтығысы туралы ереже", + "text": "Компания ішіндегі мүдделер қақтығысын анықтау, жариялау және шешу тәртібі." + }, + { + "id": 4, + "cat": "Ақпарат", + "title": "«Ақпаратқа қол жеткізу туралы» ҚР Заңы", + "text": "Қоғамдық ақпаратқа қол жеткізу, құпия ақпаратты қорғау талаптары." + }, + { + "id": 5, + "cat": "Этика", + "title": "Компанияның Іскерлік этика кодексі", + "text": "Қызметкерлердің мінез-құлық стандарттары, қарым-қатынас ережелері." + } ], "insider": [ - { "title": "Инсайдерлік ақпарат дегеніміз не?", "text": "Бұл компания туралы әлі жарияланбаған, қызметкерге өз жұмысы барысында белгілі болған деректер: қаржылық көрсеткіштер, келісімшарттар, жаңалық сюжеттері, кадрлық шешімдер." }, - { "title": "Таратуға тыйым салынады", "text": "Инсайдерлік ақпаратты эфирге шыққанға дейін немесе ресми жарияланғанға дейін үшінші тұлғаларға, әлеуметтік желілерде немесе таныстарға таратуға тыйым салынады." }, - { "title": "Дереккөздерді құпия ұстау", "text": "Журналистер өз дереккөздерін құпия ұстауға міндетті. Дереккөздің атын ашу — еңбек тәртібін өрескел бұзу." }, - { "title": "Не істеу керек?", "text": "Инсайдерлік ақпаратқа қол жеткізгенде және оны пайдалану қажет болғанда — комплаенс офицермен кеңесіңіз. Ақпарат тек қызметтік қажеттілік үшін пайдаланылады." }, - { "title": "Жауапкершілік", "text": "Инсайдерлік ақпаратты тарату — тәртіптік жауапкершілікке әкеледі, ал ауыр жағдайларда ҚР заңнамасы бойынша қылмыстық жауапкершілікке де тартылуы мүмкін." } + { + "title": "Инсайдерлік ақпарат дегеніміз не?", + "text": "Бұл компания туралы әлі жарияланбаған, қызметкерге өз жұмысы барысында белгілі болған деректер: қаржылық көрсеткіштер, келісімшарттар, жаңалық сюжеттері, кадрлық шешімдер." + }, + { + "title": "Таратуға тыйым салынады", + "text": "Инсайдерлік ақпаратты эфирге шыққанға дейін немесе ресми жарияланғанға дейін үшінші тұлғаларға, әлеуметтік желілерде немесе таныстарға таратуға тыйым салынады." + }, + { + "title": "Дереккөздерді құпия ұстау", + "text": "Журналистер өз дереккөздерін құпия ұстауға міндетті. Дереккөздің атын ашу — еңбек тәртібін өрескел бұзу." + }, + { + "title": "Не істеу керек?", + "text": "Инсайдерлік ақпаратқа қол жеткізгенде және оны пайдалану қажет болғанда — комплаенс офицермен кеңесіңіз. Ақпарат тек қызметтік қажеттілік үшін пайдаланылады." + }, + { + "title": "Жауапкершілік", + "text": "Инсайдерлік ақпаратты тарату — тәртіптік жауапкершілікке әкеледі, ал ауыр жағдайларда ҚР заңнамасы бойынша қылмыстық жауапкершілікке де тартылуы мүмкін." + } ], "giftRules": { "allowed": [ @@ -66,37 +236,178 @@ }, "tests": [ { - "id": 1, "title": "Сыбайлас жемқорлыққа қарсы негіздер", "pass": 70, + "id": 1, + "title": "Сыбайлас жемқорлыққа қарсы негіздер", + "pass": 70, "questions": [ - { "q": "Сыбайлас жемқорлық құқық бұзушылық деген не?", "options": ["Қызметкердің өз өкілеттігін жеке байыту үшін пайдалануы", "Жұмысқа уақытында келу", "Каникулға шығу", "Басшымен сөйлесу"], "correct": 0 }, - { "q": "Қызметкерге қандай сыйлық қабылдауға рұқсат?", "options": ["Кез келген уақытта кез келген сыйлық", "Құны 3 АЕК-тен аспайтын және есепке алынған сыйлық", "Тек ақшалай", "Барлық сыйлыққа тыйым салынған"], "correct": 1 }, - { "q": "Мүдделер қақтығысы дегеніміз?", "options": ["Жұмыстағы дау", "Жеке мүдде мен қызметтік міндеттердің қайшылығы", "Екі қызметкердің таласы", "Жұмыс орнын ауыстыру"], "correct": 1 }, - { "q": "Жемқорлық фактісін қайда хабарлау керек?", "options": ["Барлығына айту", "Комплаенс қызметіне немесе шағым жәшігіне", "Әлеуметтік желіде жазу", "Үндемеу"], "correct": 1 }, - { "q": "Кірістер мен мүлік туралы декларация кімге міндетті?", "options": ["Тек басшыларға", "Заңда көрсетілген қызметкерлерге", "Ешкімге", "Тек шетелге шығатындарға"], "correct": 1 } + { + "q": "Сыбайлас жемқорлық құқық бұзушылық деген не?", + "options": [ + "Қызметкердің өз өкілеттігін жеке байыту үшін пайдалануы", + "Жұмысқа уақытында келу", + "Каникулға шығу", + "Басшымен сөйлесу" + ], + "correct": 0 + }, + { + "q": "Қызметкерге қандай сыйлық қабылдауға рұқсат?", + "options": [ + "Кез келген уақытта кез келген сыйлық", + "Құны 3 АЕК-тен аспайтын және есепке алынған сыйлық", + "Тек ақшалай", + "Барлық сыйлыққа тыйым салынған" + ], + "correct": 1 + }, + { + "q": "Мүдделер қақтығысы дегеніміз?", + "options": [ + "Жұмыстағы дау", + "Жеке мүдде мен қызметтік міндеттердің қайшылығы", + "Екі қызметкердің таласы", + "Жұмыс орнын ауыстыру" + ], + "correct": 1 + }, + { + "q": "Жемқорлық фактісін қайда хабарлау керек?", + "options": [ + "Барлығына айту", + "Комплаенс қызметіне немесе шағым жәшігіне", + "Әлеуметтік желіде жазу", + "Үндемеу" + ], + "correct": 1 + }, + { + "q": "Кірістер мен мүлік туралы декларация кімге міндетті?", + "options": [ + "Тек басшыларға", + "Заңда көрсетілген қызметкерлерге", + "Ешкімге", + "Тек шетелге шығатындарға" + ], + "correct": 1 + } ] }, { - "id": 2, "title": "Іскерлік этика кодексі", "pass": 70, + "id": 2, + "title": "Іскерлік этика кодексі", + "pass": 70, "questions": [ - { "q": "Іскерлік этика кодексінің мақсаты?", "options": ["Қызметкерлерді жазалау", "Мінез-құлық стандарттарын белгілеу", "Жалақыны азайту", "Каникулды қысқарту"], "correct": 1 }, - { "q": "Алқаптас әріптеске қалай қарау керек?", "options": ["Құрметпен және теңдікте", "Менсінбеу", "Елемеу", "Тек басшыларға ғана құрмет"], "correct": 0 }, - { "q": "Конфиденциалды ақпаратты не істеуге болмайды?", "options": ["Қауіпсіз жерде сақтау", "Үшінші тұлғаларға тарату", "Пароль қою", "Құлыптап қою"], "correct": 1 }, - { "q": "Жемқорлық әрекетке қатысуды сұраса, не істеу керек?", "options": ["Келісу", "Бас тартып, комплаенс қызметіне хабарлау", "Ойланып жауап беру", "Ешкімге айтпау"], "correct": 1 }, - { "q": "Тест нәтижелері мен оқу материалдары кімге қажет?", "options": ["Тек HR-ға", "Барлық қызметкерлерге", "Тек админге", "Тек басшыларға"], "correct": 1 } + { + "q": "Іскерлік этика кодексінің мақсаты?", + "options": [ + "Қызметкерлерді жазалау", + "Мінез-құлық стандарттарын белгілеу", + "Жалақыны азайту", + "Каникулды қысқарту" + ], + "correct": 1 + }, + { + "q": "Алқаптас әріптеске қалай қарау керек?", + "options": [ + "Құрметпен және теңдікте", + "Менсінбеу", + "Елемеу", + "Тек басшыларға ғана құрмет" + ], + "correct": 0 + }, + { + "q": "Конфиденциалды ақпаратты не істеуге болмайды?", + "options": [ + "Қауіпсіз жерде сақтау", + "Үшінші тұлғаларға тарату", + "Пароль қою", + "Құлыптап қою" + ], + "correct": 1 + }, + { + "q": "Жемқорлық әрекетке қатысуды сұраса, не істеу керек?", + "options": [ + "Келісу", + "Бас тартып, комплаенс қызметіне хабарлау", + "Ойланып жауап беру", + "Ешкімге айтпау" + ], + "correct": 1 + }, + { + "q": "Тест нәтижелері мен оқу материалдары кімге қажет?", + "options": [ + "Тек HR-ға", + "Барлық қызметкерлерге", + "Тек админге", + "Тек басшыларға" + ], + "correct": 1 + } ] }, { - "id": 3, "title": "Инсайдерлік ақпарат және құпиялылық", "pass": 70, + "id": 3, + "title": "Инсайдерлік ақпарат және құпиялылық", + "pass": 70, "questions": [ - { "q": "Инсайдерлік ақпарат дегеніміз не?", "options": ["Әріптестер туралы қауесет", "Компания туралы әлі жарияланбаған қызметтік деректер", "Әлеуметтік желідегі ақпарат", "Жалпыға белгілі жаңалықтар"], "correct": 1 }, - { "q": "Жаңа сюжет эфирге шыққанға дейін оны танысқа айтуға бола ма?", "options": ["Иә, егер ол жақын таныс болса", "Жоқ, инсайдерлік ақпаратты таратуға тыйым салынады", "Иә, бірақ тек SMS арқылы", "Болады, себебі бұл жай ғана жаңалық"], "correct": 1 }, - { "q": "Дереккөздің атын басшылыққа ашуға бола ма?", "options": ["Иә, міндетті түрде", "Жоқ, журналист дереккөзді құпия ұстайды", "Тек түскі үзілісте айтуға болады", "Тек жазбаша түрде"], "correct": 1 }, - { "q": "Инсайдерлік ақпаратты таратқаны үшін не болады?", "options": ["Ештеңе болмайды", "Тәртіптік жауапкершілік, ауыр жағдайда заң бойынша жауапкершілік", "Тек сөгіс беріледі", "Бонус өседі"], "correct": 1 }, - { "q": "Қызметтік ақпаратты қай жерде талқылауға БОЛМАЙДЫ?", "options": ["Кеңседе әріптестермен", "Көпшілік орындарда және әлеуметтік желілерде", "Ресми жиналыста", "Комплаенс офицермен кеңесте"], "correct": 1 } + { + "q": "Инсайдерлік ақпарат дегеніміз не?", + "options": [ + "Әріптестер туралы қауесет", + "Компания туралы әлі жарияланбаған қызметтік деректер", + "Әлеуметтік желідегі ақпарат", + "Жалпыға белгілі жаңалықтар" + ], + "correct": 1 + }, + { + "q": "Жаңа сюжет эфирге шыққанға дейін оны танысқа айтуға бола ма?", + "options": [ + "Иә, егер ол жақын таныс болса", + "Жоқ, инсайдерлік ақпаратты таратуға тыйым салынады", + "Иә, бірақ тек SMS арқылы", + "Болады, себебі бұл жай ғана жаңалық" + ], + "correct": 1 + }, + { + "q": "Дереккөздің атын басшылыққа ашуға бола ма?", + "options": [ + "Иә, міндетті түрде", + "Жоқ, журналист дереккөзді құпия ұстайды", + "Тек түскі үзілісте айтуға болады", + "Тек жазбаша түрде" + ], + "correct": 1 + }, + { + "q": "Инсайдерлік ақпаратты таратқаны үшін не болады?", + "options": [ + "Ештеңе болмайды", + "Тәртіптік жауапкершілік, ауыр жағдайда заң бойынша жауапкершілік", + "Тек сөгіс беріледі", + "Бонус өседі" + ], + "correct": 1 + }, + { + "q": "Қызметтік ақпаратты қай жерде талқылауға БОЛМАЙДЫ?", + "options": [ + "Кеңседе әріптестермен", + "Көпшілік орындарда және әлеуметтік желілерде", + "Ресми жиналыста", + "Комплаенс офицермен кеңесте" + ], + "correct": 1 + } ] } ], "complaints": [], "testResults": [], "directMessages": [] -} +} \ No newline at end of file diff --git a/index.html b/index.html index bf12e4c..d816683 100644 --- a/index.html +++ b/index.html @@ -23,6 +23,13 @@
+ +

Демо: админ — 1001 / Admin123 · қызметкер — 2001 / Worker123

diff --git a/script.js b/script.js index 48b022a..3e9951a 100644 --- a/script.js +++ b/script.js @@ -25,6 +25,7 @@ var NAV = [ { id: "laws", ico: "⚖️", title: "Заң нормалары" }, { id: "complaints", ico: "✉️", title: "Шағым жәшігі" }, { id: "tests", ico: "📝", title: "Тесттер" }, + { id: "users", ico: "👥", title: "Қызметкерлер", admin: true }, { id: "stats", ico: "📊", title: "Дашборд", admin: true }, ]; @@ -172,6 +173,28 @@ function lsGet(k, def) { try { var v = JSON.parse(localStorage.getItem("qcom_" + function lsSet(k, v) { localStorage.setItem("qcom_" + k, JSON.stringify(v)); } /* ---------------- КІРУ ---------------- */ +document.getElementById("forgotBtn").addEventListener("click", function () { + document.getElementById("recoverBox").classList.toggle("hidden"); +}); + +document.getElementById("recoverGo").addEventListener("click", async function () { + var login = document.getElementById("recoverLogin").value.trim(); + var msg = document.getElementById("recoverMsg"); + if (!login) { msg.textContent = "Логиніңізді жазыңыз"; return; } + var res = await apiPost("/api/recover-password", { login: login }); + if (res.ok) { + msg.innerHTML = "" + esc(res.name) + ", жаңа құпия сөзіңіз: " + esc(res.password) + ""; + } else if (res.offline) { + var mu = MOCK_USERS.find(function (u) { return u.login === login; }); + if (!mu) { msg.textContent = "Мұндай логин табылмады"; return; } + var np = "Qonaev" + Math.floor(1000 + Math.random() * 9000); + mu.password = np; + msg.innerHTML = "" + esc(mu.name) + ", жаңа құпия сөзіңіз: " + np + " (демо-режим)"; + } else { + msg.textContent = res.error || "Қате"; + } +}); + document.getElementById("loginForm").addEventListener("submit", async function (e) { e.preventDefault(); var login = document.getElementById("loginNum").value.trim(); @@ -208,9 +231,11 @@ async function loadServerData() { if (cp.ok) state.complaints = cp.data; var tr = await apiGet("/api/test-results"); if (tr.ok) state.testResults = tr.data; + var dm = await apiGet("/api/direct-messages"); + if (dm.ok) state.directMessages = dm.data; if (state.user && state.user.role === "admin") { - var dm = await apiGet("/api/direct-messages"); - if (dm.ok) state.directMessages = dm.data; + var us = await apiGet("/api/users"); + if (us.ok) state.users = us.data; } } @@ -229,17 +254,28 @@ function enterApp() { function buildNav() { var nav = document.getElementById("nav"); nav.innerHTML = ""; + var newCount = newItemsCount(); NAV.forEach(function (item) { if (item.admin && state.user.role !== "admin") return; var b = document.createElement("button"); b.className = "nav-item" + (item.admin ? " admin-only" : ""); b.dataset.id = item.id; b.innerHTML = '' + item.ico + "" + item.title + ""; + if (state.user.role === "admin" && (item.id === "complaints" || item.id === "stats") && newCount > 0) { + b.innerHTML += '' + newCount + ""; + } b.addEventListener("click", function () { go(item.id); }); nav.appendChild(b); }); } +function newItemsCount() { + var n = 0; + (state.complaints || []).forEach(function (c) { if (c.status === "new") n++; }); + (state.directMessages || []).forEach(function (m) { if (m.status === "new") n++; }); + return n; +} + function go(page) { state.curPage = page; document.querySelectorAll(".nav-item").forEach(function (n) { @@ -257,6 +293,7 @@ function go(page) { else if (page === "laws") renderLaws(c); else if (page === "complaints") renderComplaints(c); else if (page === "tests") renderTests(c); + else if (page === "users") renderUsers(c); else if (page === "stats") renderStats(c); } @@ -276,8 +313,18 @@ function renderHome(el) { html += '
' + (state.content ? state.content.videos.length : 0) + "
Видео сабақ
"; html += ""; - html += '
📢 Хабарландырулар
'; - if (state.content && state.content.announcements.length) { + if (state.user.role === "admin") { + var newCount = newItemsCount(); + if (newCount > 0) { + html += '
'; + html += '

🔔 Жаңа хабарламалар: ' + newCount + "

"; + html += '

Шағым жәшігіне және жеке хаттарға жаңа өтініштер келді. Жауап беру үшін «Шағым жәшігі» немесе «Дашборд» бөліміне өтіңіз.

'; + html += ''; + html += "
"; + } + } + + html += '
📢 Хабарландырулар
'; if (state.content && state.content.announcements.length) { state.content.announcements.forEach(function (a) { html += '

' + esc(a.title) + '

📅 ' + a.date + '

' + esc(a.text) + "

"; }); @@ -469,8 +516,42 @@ function renderComplaints(el) { html += '
Менің шағымдарым' + (state.user.role === "admin" ? " және барлық өтініштер" : "") + "
"; html += '
'; + + html += '
Менің жеке хаттарым (комплаенс офицерге)
'; + html += '
'; el.innerHTML = html; renderComplaintList(); + renderMyDmList(); +} + +function renderMyDmList() { + var list = (state.directMessages || []).slice().sort(function (a, b) { return b.id - a.id; }); + if (state.user.role !== "admin") { + list = list.filter(function (m) { return m.authorLogin === state.user.login; }); + } + var html = ""; + list.forEach(function (m) { + var st = { "new": "Жаңа", "work": "Қаралуда", "done": "Шешілді" }[m.status] || m.status; + html += '
'; + html += '
' + esc(m.category) + ""; + html += '' + (m.anonymous ? "Аноним" : esc(m.authorName)) + ' · ' + st + "
"; + html += "

" + esc(m.text) + "

"; + if (m.reply) { + html += '
📨 Комплаенс офицердің жауабы (' + (m.replyDate || "") + '):

' + esc(m.reply) + "

"; + } + if (state.user.role === "admin") { + html += '
'; + html += ''; + html += ''; + if (m.status !== "done") { + if (m.status !== "work") html += ''; + html += ''; + } + html += "
"; + } + html += "
"; + }); + document.getElementById("dmList").innerHTML = html || '
Хаттар жоқ — комплаенс офицерге жаза аласыз
'; } function renderComplaintList() { @@ -486,10 +567,19 @@ function renderComplaintList() { html += '' + st + ""; html += '' + (c.anonymous ? "Аноним" : esc(c.authorName)) + " · " + c.date + ""; html += "

" + esc(c.text) + "

"; - if (state.user.role === "admin" && c.status !== "done") { - html += '
'; - if (c.status !== "work") html += ''; - html += ''; + + if (c.reply) { + html += '
📨 Жауап (' + c.replyDate + '):

' + esc(c.reply) + "

"; + } + + if (state.user.role === "admin") { + html += '
'; + html += ''; + html += ''; + if (c.status !== "done") { + if (c.status !== "work") html += ''; + html += ''; + } html += "
"; } html += "
"; @@ -497,6 +587,25 @@ function renderComplaintList() { document.getElementById("compList").innerHTML = html || '
Әзірге шағым жоқ
'; } +async function replyComplaint(id) { + var reply = document.getElementById("replyComp_" + id).value.trim(); + if (!reply) { alert("Жауап мәтінін жазыңыз"); return; } + if (state.offline) { + var c = state.complaints.find(function (x) { return x.id === id; }); + if (c) { + c.reply = reply; + c.replyDate = new Date().toISOString().slice(0, 10); + if (c.status === "new") c.status = "work"; + lsSet("complaints", state.complaints); + } + renderComplaintList(); + return; + } + var res = await apiPost("/api/complaints/" + id + "/reply", { reply: reply }); + if (res.ok) { await loadServerData(); renderComplaintList(); } + else alert(res.error || "Қате"); +} + async function sendComplaint() { var category = document.getElementById("compCat").value; var text = document.getElementById("compText").value.trim(); @@ -551,18 +660,113 @@ async function sendDirectMsg() { authorLogin: anonymous ? null : state.user.login, authorName: anonymous ? null : state.user.name, }); + state.directMessages = list; lsSet("directMessages", list); document.getElementById("dmMsg").innerHTML = '
✅ Хат жіберілді (демо-режимде жергілікті сақталды)
'; document.getElementById("dmText").value = ""; + renderMyDmList(); return; } var res = await apiPost("/api/direct-messages", { category: category, text: text, anonymous: anonymous }); if (res.ok) { document.getElementById("dmMsg").innerHTML = '
✅ Хатыңыз комплаенс офицерге жіберілді
'; document.getElementById("dmText").value = ""; + await loadServerData(); + renderMyDmList(); } else document.getElementById("dmMsg").innerHTML = '
' + esc(res.error || "Қате") + "
"; } +/* ---------------- ҚЫЗМЕТКЕРЛЕР (АККАУНТТАР, админ) ---------------- */ +async function renderUsers(el) { + var html = pageTitle("👥 Қызметкерлер және аккаунттар"); + html += '

Логиндер мен құпия сөздер users.json файлында сақталады. Мұнда жаңа қызметкер қосуға, құпия сөз ауыстыруға және аккаунтты өшіруге болады.

'; + + html += '

➕ Жаңа қызметкер қосу

'; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += ''; + html += '
'; + + html += '
'; + el.innerHTML = html; + loadUsersList(); +} + +async function loadUsersList() { + var users; + if (state.offline) { + users = MOCK_USERS; + } else { + var r = await apiGet("/api/users"); + if (!r.ok) return; + users = r.data; + } + var html = '
'; + users.forEach(function (u) { + html += ""; + html += '"; + html += ''; + html += '"; + }); + html += "
ЛогинАты-жөніРөлҚұпия сөз
" + esc(u.login) + "" + esc(u.name) + "' + (u.role === "admin" ? "Админ" : "Қызметкер") + "' + esc(u.password) + ''; + html += '' + (u.role !== "admin" ? '' : "") + "
"; + document.getElementById("usersList").innerHTML = html; +} + +async function addUser() { + var login = document.getElementById("uLogin").value.trim(); + var name = document.getElementById("uName").value.trim(); + var pass = document.getElementById("uPass").value.trim(); + var role = document.getElementById("uRole").value; + var msg = document.getElementById("uMsg"); + if (!login || !name || !pass) { msg.innerHTML = '
Барлық өрістерді толтырыңыз
'; return; } + if (state.offline) { + MOCK_USERS.push({ login: login, name: name, password: pass, role: role }); + msg.innerHTML = '
✅ Қосылды (демо)
'; + loadUsersList(); + return; + } + var res = await apiPost("/api/users", { login: login, name: name, password: pass, role: role }); + if (res.ok) { + msg.innerHTML = '
✅ Қызметкер қосылды
'; + document.getElementById("uLogin").value = ""; + document.getElementById("uName").value = ""; + document.getElementById("uPass").value = ""; + loadUsersList(); + } else msg.innerHTML = '
' + esc(res.error || "Қате") + "
"; +} + +async function delUser(login) { + if (!confirm("«" + login + "» аккаунтын өшіру керек пе?")) return; + if (state.offline) { + var i = MOCK_USERS.findIndex(function (u) { return u.login === login; }); + if (i >= 0) MOCK_USERS.splice(i, 1); + loadUsersList(); + return; + } + var res = await fetch(apiUrl() + "/api/users/" + encodeURIComponent(login), { + method: "DELETE", headers: { "Authorization": state.token ? "Bearer " + state.token : "" }, + }); + if (res.ok) loadUsersList(); else alert("Қате"); +} + +async function resetUserPass(login) { + var np = prompt("«" + login + "» үшін жаңа құпия сөз:"); + if (!np) return; + if (state.offline) { + var u = MOCK_USERS.find(function (x) { return x.login === login; }); + if (u) u.password = np; + loadUsersList(); + return; + } + var res = await apiPost("/api/users/" + encodeURIComponent(login) + "/password", { password: np }); + if (res.ok) loadUsersList(); else alert(res.error || "Қате"); +} + /* ---------------- ТЕСТТЕР ---------------- */ function renderTests(el) { if (state.curTest !== null) { renderTestRun(el); return; } @@ -732,7 +936,11 @@ async function renderStats(el) { var chtml = ""; cl.forEach(function (c) { var st = { "new": "Жаңа", "work": "Қаралуда", "done": "Шешілді" }[c.status] || c.status; - chtml += '
' + esc(c.category) + "" + (c.anonymous ? "Аноним" : esc(c.authorName)) + ' · ' + st + "

" + esc(c.text) + "

"; + chtml += '
' + esc(c.category) + "" + (c.anonymous ? "Аноним" : esc(c.authorName)) + ' · ' + st + "

" + esc(c.text) + "

"; + if (c.reply) { + chtml += '
📨 Жауап:

' + esc(c.reply) + "

"; + } + chtml += "
"; }); document.getElementById("statsComplaints").innerHTML = chtml || '
Шағым жоқ
'; @@ -745,17 +953,44 @@ async function renderStats(el) { dhtml += '
'; dhtml += '
' + esc(m.category) + "" + (m.anonymous ? "Аноним" : esc(m.authorName)) + ' · ' + st + "
"; dhtml += '

' + esc(m.text) + "

"; - if (m.status !== "done") { - dhtml += '
'; - if (m.status !== "work") dhtml += ''; - dhtml += '
'; + if (m.reply) { + dhtml += '
📨 Жауап:

' + esc(m.reply) + "

"; } - dhtml += "
"; + dhtml += '
'; + dhtml += ''; + dhtml += ''; + if (m.status !== "done") { + if (m.status !== "work") dhtml += ''; + dhtml += ''; + } + dhtml += "
"; }); document.getElementById("statsDm").innerHTML = dhtml || '
Жеке хаттар жоқ
'; } } +async function replyDm(id) { + var reply = document.getElementById("replyDm_" + id).value.trim(); + if (!reply) { alert("Жауап мәтінін жазыңыз"); return; } + if (state.offline) { + var list = lsGet("directMessages", []); + var m = list.find(function (x) { return x.id === id; }); + if (m) { + m.reply = reply; + m.replyDate = new Date().toISOString().slice(0, 10); + if (m.status === "new") m.status = "work"; + lsSet("directMessages", list); + } + renderStats(document.getElementById("content")); + return; + } + var res = await apiPost("/api/direct-messages/" + id + "/reply", { reply: reply }); + if (res.ok) { + await loadServerData(); + renderStats(document.getElementById("content")); + } else alert(res.error || "Қате"); +} + async function setDmStatus(id, status) { if (state.offline) { var list = lsGet("directMessages", []); diff --git a/server.js b/server.js index b196b88..34c38cd 100644 --- a/server.js +++ b/server.js @@ -5,14 +5,22 @@ const crypto = require("crypto"); const PORT = process.env.PORT || 3000; const DATA_FILE = path.join(__dirname, "data.json"); +const USERS_FILE = path.join(__dirname, "users.json"); function load() { try { return JSON.parse(fs.readFileSync(DATA_FILE, "utf8")); } - catch (e) { return { users: [], announcements: [], org: [], contacts: [], insider: [], giftRules: {}, videos: [], laws: [], tests: [], complaints: [], testResults: [], directMessages: [] }; } + catch (e) { return { announcements: [], org: [], contacts: [], insider: [], giftRules: {}, videos: [], laws: [], tests: [], complaints: [], testResults: [], directMessages: [] }; } } function save(d) { fs.writeFileSync(DATA_FILE, JSON.stringify(d, null, 2)); } +function loadUsers() { + try { return JSON.parse(fs.readFileSync(USERS_FILE, "utf8")); } + catch (e) { return []; } +} +function saveUsers(users) { + fs.writeFileSync(USERS_FILE, JSON.stringify(users, null, 2)); +} const sessions = new Map(); @@ -48,13 +56,68 @@ http.createServer(async (req, res) => { // ---------- ЛОГИН ---------- if (req.method === "POST" && url === "/api/login") { const b = await body(req); - const u = db.users.find((x) => x.login === String(b.login) && x.password === String(b.password)); + const u = loadUsers().find((x) => x.login === String(b.login) && x.password === String(b.password)); if (!u) return send(res, 401, { ok: false, error: "Логин немесе құпия сөз қате" }); const token = crypto.randomBytes(24).toString("hex"); sessions.set(token, u); return send(res, 200, { ok: true, token, user: publicUser(u) }); } + // ---------- ПАРОЛЬДЫ ҚАЛПЫНА КЕЛТІРУ ---------- + if (req.method === "POST" && url === "/api/recover-password") { + const b = await body(req); + const users = loadUsers(); + const u = users.find((x) => x.login === String(b.login)); + if (!u) return send(res, 404, { ok: false, error: "Мұндай логин табылмады" }); + const newPass = "Qonaev" + Math.floor(1000 + Math.random() * 9000); + u.password = newPass; + saveUsers(users); + return send(res, 200, { ok: true, password: newPass, name: u.name }); + } + + // ---------- АККАУНТТАРДЫ БАСҚАРУ (админ) ---------- + if (req.method === "GET" && url === "/api/users") { + if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); + return send(res, 200, { ok: true, data: loadUsers().map((x) => ({ + login: x.login, name: x.name, role: x.role, password: x.password, + })) }); + } + + if (req.method === "POST" && url === "/api/users") { + if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); + const b = await body(req); + if (!b.login || !b.name || !b.password) return send(res, 400, { ok: false, error: "Логин, аты-жөні және құпия сөз міндетті" }); + const users = loadUsers(); + if (users.find((x) => x.login === String(b.login))) return send(res, 400, { ok: false, error: "Мұндай логин бар" }); + users.push({ login: String(b.login), password: String(b.password), name: String(b.name), role: b.role === "admin" ? "admin" : "user" }); + saveUsers(users); + return send(res, 200, { ok: true }); + } + + const uDelMatch = url.match(/^\/api\/users\/([^/]+)$/); + if (req.method === "DELETE" && uDelMatch) { + if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); + const users = loadUsers(); + const i = users.findIndex((x) => x.login === uDelMatch[1]); + if (i < 0) return send(res, 404, { ok: false, error: "Табылмады" }); + users.splice(i, 1); + saveUsers(users); + return send(res, 200, { ok: true }); + } + + const uPassMatch = url.match(/^\/api\/users\/([^/]+)\/password$/); + if (req.method === "POST" && uPassMatch) { + if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); + const b = await body(req); + const users = loadUsers(); + const u = users.find((x) => x.login === uPassMatch[1]); + if (!u) return send(res, 404, { ok: false, error: "Табылмады" }); + if (!b.password) return send(res, 400, { ok: false, error: "Жаңа құпия сөзді жазыңыз" }); + u.password = String(b.password); + saveUsers(users); + return send(res, 200, { ok: true }); + } + // ---------- АУТЕНТИФИКАЦИЯ ---------- const user = getUser(req); const isApi = url.startsWith("/api/"); @@ -114,6 +177,20 @@ http.createServer(async (req, res) => { return send(res, 200, { ok: true }); } + const compReplyMatch = url.match(/^\/api\/complaints\/(\d+)\/reply$/); + if (req.method === "POST" && compReplyMatch) { + if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); + const b = await body(req); + const c = db.complaints.find((x) => x.id === Number(compReplyMatch[1])); + if (!c) return send(res, 404, { ok: false, error: "Табылмады" }); + if (!b.reply) return send(res, 400, { ok: false, error: "Жауап мәтінін жазыңыз" }); + c.reply = String(b.reply).slice(0, 3000); + c.replyDate = new Date().toISOString().slice(0, 10); + if (c.status === "new") c.status = "work"; + save(db); + return send(res, 200, { ok: true }); + } + const stMatch = url.match(/^\/api\/complaints\/(\d+)\/status$/); if (req.method === "POST" && stMatch) { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); @@ -157,8 +234,9 @@ http.createServer(async (req, res) => { // ---------- КОМПЛАЕНС ОФИЦЕРГЕ ЖЕКЕ ХАТ ---------- if (req.method === "GET" && url === "/api/direct-messages") { - if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); - return send(res, 200, { ok: true, data: db.directMessages || [] }); + let list = db.directMessages || []; + if (user.role !== "admin") list = list.filter((m) => m.authorLogin === user.login); + return send(res, 200, { ok: true, data: list }); } if (req.method === "POST" && url === "/api/direct-messages") { @@ -180,6 +258,20 @@ http.createServer(async (req, res) => { return send(res, 200, { ok: true }); } + const dmReplyMatch = url.match(/^\/api\/direct-messages\/(\d+)\/reply$/); + if (req.method === "POST" && dmReplyMatch) { + if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); + const b = await body(req); + const m = (db.directMessages || []).find((x) => x.id === Number(dmReplyMatch[1])); + if (!m) return send(res, 404, { ok: false, error: "Табылмады" }); + if (!b.reply) return send(res, 400, { ok: false, error: "Жауап мәтінін жазыңыз" }); + m.reply = String(b.reply).slice(0, 3000); + m.replyDate = new Date().toISOString().slice(0, 10); + if (m.status === "new") m.status = "work"; + save(db); + return send(res, 200, { ok: true }); + } + const dmMatch = url.match(/^\/api\/direct-messages\/(\d+)\/status$/); if (req.method === "POST" && dmMatch) { if (user.role !== "admin") return send(res, 403, { ok: false, error: "Құқық жоқ" }); @@ -208,7 +300,7 @@ http.createServer(async (req, res) => { const dms = db.directMessages || []; const dmOpen = dms.filter((m) => m.status !== "done").length; return send(res, 200, { ok: true, data: { - users: db.users.length, + users: loadUsers().length, complaintsTotal: db.complaints.length, complaintsOpen: byStatus.new + byStatus.work, testsTotal: db.testResults.length, diff --git a/style.css b/style.css index 968e422..8180c1a 100644 --- a/style.css +++ b/style.css @@ -241,6 +241,37 @@ tr:last-child td{border-bottom:none} .gift-item:last-child{border-bottom:none} @media (max-width:700px){.gift-grid{grid-template-columns:1fr}} +/* ===== БЕЙДЖ В НАВИГАЦИИ ===== */ +.nav-badge{ + margin-left:auto;background:var(--red);color:#fff;font-size:11px;font-weight:800; + min-width:20px;height:20px;border-radius:999px;display:inline-flex;align-items:center; + justify-content:center;padding:0 6px; +} +@media (max-width:900px){ + .nav-badge{position:absolute;top:4px;right:6px;margin:0} + .nav-item{position:relative} +} + +/* ===== ОТВЕТ / ПЕРЕПИСКА ===== */ +.reply-box{ + background:var(--cyan-50);border-left:3px solid var(--cyan);border-radius:8px; + padding:10px 14px;margin-top:10px;font-size:14px; +} +.reply-box p{margin:4px 0 0;color:var(--gray-500)} +.reply-input{ + flex:1;min-width:180px;padding:8px 12px;border:1.5px solid #DFE4EA;border-radius:8px; + font-size:13px;outline:none;font-family:inherit; +} +.reply-input:focus{border-color:var(--accent)} + +/* ===== АККАУНТТАР (ҚЫЗМЕТКЕРЛЕР) ===== */ +.pass-row{display:flex;align-items:center;gap:8px} +.pass-text{font-family:monospace;background:var(--gray-100);padding:3px 8px;border-radius:6px;font-size:13px} +.login-sub{font-size:13px;color:var(--gray-500);margin-bottom:8px} +#recoverBox{background:var(--gray-50);border:1px dashed #DFE4EA;border-radius:10px;padding:12px} +#recoverBox input{width:100%;padding:10px 12px;border:1.5px solid #DFE4EA;border-radius:10px;font-size:14px;outline:none} +#recoverBox input:focus{border-color:var(--accent)} + @media (max-width:900px){ .grid-4{grid-template-columns:repeat(2,1fr)} .grid-3{grid-template-columns:repeat(2,1fr)} diff --git a/users.json b/users.json new file mode 100644 index 0000000..1b68764 --- /dev/null +++ b/users.json @@ -0,0 +1,6 @@ +[ + { "login": "1001", "password": "Admin123", "name": "Асет (Комплаенс офицер)", "role": "admin" }, + { "login": "2001", "password": "Worker123", "name": "Айгүл Серікқызы", "role": "user" }, + { "login": "2002", "password": "Worker123", "name": "Бауыржан Нұрланұлы", "role": "user" }, + { "login": "2003", "password": "Worker123", "name": "Дана Қайратқызы", "role": "user" } +]