From 307f970d28b331f83fd5d6146cbffdcb5a5a2546 Mon Sep 17 00:00:00 2001 From: Macbook Date: Mon, 8 Dec 2025 23:35:14 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=B1=D1=89=D0=B8=D1=85=20=D1=81?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D0=B4=D0=BE=D0=B2=20=D0=B8=20=D1=83=D0=B4?= =?UTF-8?q?=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=83=D1=81=D1=82?= =?UTF-8?q?=D1=8B=D1=85=20=D0=BD=D0=B8=20=D1=80=D0=B0=D0=B7=D1=83=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BE=D0=B1=D1=89=D0=B8=D1=85?= =?UTF-8?q?=20=D1=81=D0=BA=D0=BB=D0=B0=D0=B4=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/__pycache__/__init__.cpython-313.pyc | Bin 1146 -> 1146 bytes api/routers/__init__.py | 4 + .../__pycache__/__init__.cpython-313.pyc | Bin 4123 -> 4315 bytes .../__pycache__/stocks.cpython-313.pyc | Bin 7403 -> 5509 bytes .../__pycache__/toolbox.cpython-313.pyc | Bin 0 -> 2360 bytes .../__pycache__/toolkit.cpython-313.pyc | Bin 0 -> 2418 bytes api/routers/stocks.py | 44 +- api/routers/toolbox.py | 38 ++ api/routers/toolkit.py | 46 ++ api/static/css/index.css | 114 ++++ api/static/js/api.js | 4 +- api/static/js/index.js | 491 +++++++++++++++++- api/static/js/toast.js | 68 +++ .../__pycache__/toolkit.cpython-313.pyc | Bin 13793 -> 14159 bytes db/handlers/__pycache__/user.cpython-313.pyc | Bin 15309 -> 15274 bytes db/handlers/user.py | 4 +- main.py | 4 +- 17 files changed, 755 insertions(+), 62 deletions(-) create mode 100644 api/routers/__pycache__/toolbox.cpython-313.pyc create mode 100644 api/routers/__pycache__/toolkit.cpython-313.pyc create mode 100644 api/routers/toolbox.py create mode 100644 api/routers/toolkit.py create mode 100644 api/static/js/toast.js diff --git a/api/__pycache__/__init__.cpython-313.pyc b/api/__pycache__/__init__.cpython-313.pyc index d59b572e6e8297cd3894561e6d59bf56f5a24d53..30102f8dbc47e369efb29165691a36fee3e3f669 100644 GIT binary patch delta 20 acmeyx@r#4|GcPX}0}z-WHQmUa&jJ8K0|kcw delta 20 acmeyx@r#4|GcPX}0}!Zdm~7M$qdvBRE;bPVaMxRHQxm17`x8 zBuJIiq23J!w(AU{7a2q+q%X+Y!MUOO0*L2~LwNEr{y-KtpbY?-rJPv+ delta 544 zcmZvZ&o2W(6vubAMRl~>AB1*OREgA~7aEb^g2q84)x?1&)!GTmvb@>F!P&i;gM>J6 zbCdW3+}tHjDIZWPs-}n2OmzlTh^>Fkf5(#T+EnF4tOWKSMkkRINIZ-7e zS`l@%fL7_6*}TgUv-zBx%zUeV_X;hN?{lT68ko{6k+ilKZ08M8ApL}jbCNQn)wt># z*xw^uqfbdqiA6*0JN?MJ=ZvD`5 z81TXm4^sbj6amuMiT%y^oB^03M@ut(NNuBAlvV&tOm(Sw7VBPJhJkV4@vw$5jO4`) z&4?$OP9@P8IdZvI`<))>6 qw%5wtYadG1pv4W%9=Mk8uz4sc4gZeciccdWvaxA#5i19ml=KDE7mFYO diff --git a/api/routers/__pycache__/stocks.cpython-313.pyc b/api/routers/__pycache__/stocks.cpython-313.pyc index a82c2e05633d65da6af50d5608fd463919651c9f..e75b5515f1d123370774f07cdef0d92be1f0ec56 100644 GIT binary patch delta 638 zcmaJp85(-1dq#5=9x&I{)|=X{*+;~co(^A9rKfyd(_ z>@8=eH|CXHZxF=Y6h=*#>CV$C9V1oWBsc?vv_2;PR!8oL@cunO$Gd_)M@|!Su zCMDMSo5N`pZ^KL4n!|Oz;)^N-?tvO^_A$A6H_^XmIyo2=}75npzr5J*nAfY;SEeZOXcM+|pUHA`V0u z#Bf5?A%+#v78Qq7@x53B9WO~b{K6$^WhgTGP<|TxAZxfMJz7c=b++#!{t_?@EW%Iw zDwEwS1d9^vYfuD!KU@%5b1(`;jz1VH0Ba5nC<^>xBFgF`&S8!OhS-tIsQEqJukt$q zhViZU8K}6ROv4D?RSXE=3+3_IUOAldU4T5U`sS6>6D~X3Ixybt`jQ-1P6yBke>R-k z2Q0|xr9FA3-dJmGug_Y`jC1~j{RESG?&7hw+6f=SjW412^YF*ec%KV{|C1W{;p72F YXbny%D^F(dj-GY*={0~6>pC;|4WoIAo&W#< delta 2207 zcmaJ?OKclO7@k?L?e%(h{aDA2V>@w^lEg`ygwnJ$ZBtUfgtUnvTbI&QszT$2w5c6- z9YJ`=ZV_rEglIG%dLtY_=>&>K2eu6YUnjk0_5S9GUUdk-U93xom%VxD9#x*Er;h<24@n z_u<>xy0SwHgFuuQ^y%aH-)kgdPVpp3QL9yCT)t@6k@+LRp zjN!6fn>}@ET%CLGSFU8mZt{in8upMe*LLhB#MOeE$@{KXs+ISWA$L3OBbxg`m$_S7 zuvzt{_b$6zEgjEt-L*rRrK-Xkh2_GhdcN?9emY7)KT}wS`C(z1yvq8?FPs#!f!&KR zZw^`zYC%!-I#O^CQkX6#jmJPy9it4vlFzMJ*1IQ)l4#UQJPz5(uFI-?z`D+-nK2sP z9HSf!Ku(Gw;<5-%xU(eQ`0p`R9AR6U{jc!kC!P;klh*cH;0d_nF|*^TU{s)K*d)|M zo|61*B0R>EVX=|vMZgDgwxsP($MqU)GCFA2m>#4tG8%LulzCd=tPm_|R~&Rb$_$GL zIpJ{9T-vVia6C>p9!Jsw_D>*`=9AngHT>I1@`^yk3hJdj3L}?xCY^T{FuVcMI23U= zvzswf@`^;)&5%&DXiOW>hdxkTRJId7+;JJ~Yc{aD&G4Qs24a{tNhkFf-fu>&Nlcq+ zhTWiCcfT{m@I-ZgiZ_WB*-W~i_~`og7TB4@>(?7KuaH9z;gLNY8ZlsA)R-t7W7>Wk zZMPi9vAPk}3qe$<8qGdLu1c-qKiV+2th%YqAcZ(USs0(5J^&y06PagHno7U1vNb!E zoldFr7-LM?dMq=W&Xygt*-fJ|Un7rBO{kC3Kz$TOsjFjVdi+@GR4Sc?m&4Q@pt@!X zK7gEB2N>g2flBC})m91|Kv|khW%tg}V&J8DctUkhA#Vy3Pp4CA*#`8$^mI&6&BtXE z^)z?VLMgf(H9^5F#eA}$;Iiw_$`4HCU}KuPnHn_!l!cn^0Nt@hi;CVc%xA9Z4;8Jy zT`Ik%8JOC;&?MET3x%4Ow&<>|{NQ&ie|F!QeFg91U$JA?+~J}-s=K3$e#6~fbZ;Wh z+nqVNsVH|{k-L`W40%UU+>wu8b$Uv!z`Uy@28&`u7bEA78{%C>F{X>LrFKKSCm%0S zuNGZyS&SQUXHo9f@GT2+MT%0}6{&5>W=P#duDif>{~&KD%I#O= zc0=wciX9a@vP!w%c_h@C`M!xK4F4mSrY>$jpIvxiadK%=Z{2n!ux({3pD6erDTTtY zD^FK`=vz-<-dYN@76To6pkt|bIjnc~f5sYt2j_*WuFyAvuf%!JZ8x~k`Ps$8dQ*?i z^#D_H29`SYrfr6E+qGch`DgWDM=6qPUfiiiy4S3hUTJ>l+;h6nvbf(6?ym4i4AfX% zxO=(H5c)2rJ{!{e6S|Q2TIgE~Aa8x89yK)P2j>sztiKf4Fn>^IeYG<`$9=^HOHMhT zxUmySeQN@8^jsG^Zr|Wg;8FO248fh98K@hIH{+E6j{Dh_`hm^BtXgqA$gbMLly1iH z5WCvqq4ZX7Ji>l%+e?`*f|z2+#E2E>kywCKHYvc;T0zU diff --git a/api/routers/__pycache__/toolbox.cpython-313.pyc b/api/routers/__pycache__/toolbox.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb7f3886ab2987c5b0acea054a3d148b648f36d1 GIT binary patch literal 2360 zcmbVNO>7fK6rR~#uh(|$ICe^YOkndzVrXL;lG5N%DNz~`TS_Z0mXH#)HFy(J96O!0 zQKL$g2~`O~6(ALnBT^4dZ=p&`RNA0$=y?|-Ijs1p)LytHvU=dwH@o(dMxjWIl?K2YhZI2}wALBADO^Ll}|TOq7jqgfnzD zYK!oMH*_v)j|fDFIEX_;jGRs?L9ACv+02=9 zClcvoN+w`?PG&Nx)0r#D7?nr7Z8p10!JF}`waOsHrtzb)ugcW*hD z5x~w_7NPU_20Lo2LTN6}b(UQdkb`88V~Od2nb*}JzO4>%+=c`++If_nz(HY@P+;r4 zl1*fDif)q$$q=18ok`B>d`^+cNK$uY&DR}DWD`1*ITy4MdUKtdN>5~Tc2dp~nv>2Z zlF6V=x5LvL&n^&InaQMK6E7^28si|C%cZgkeFBjV{ctD_9!dznMC94fkbFK=J};D* znF_mjK^&m9b!l$1H-xD3Nvy zpb8tu9(2Tt0K#yR4G6=$4zK`1{*{nr+Xo!ciNn~rp<}k1!#760S(KddRw~!>Z+*T1Ftpciz|Q==#1JG z5zKo=@RIf|^vBh=MeX7a5J3lBftWS*j`|zOU%+0|?bEWNB%tdW+@qKb+DR?F7UeGS zQ$b0ndw{=#vnNf8k0ebh^GP`+XXPMI_E5*YRM7t;-Ko&duh3Rt?pzPE3Z=inW|1^d zFOL~+k+c~mX}Gcsf|B%4Z{1DTb=Sgh!P{E&c4*#?MOVSwlOHLQO7(@79E)G6-ta@N z|0RhZ*R>Ywx*ybaFZHd;g}R|)%}_q_)Lyg6A@=~Y$s$M1wFXTHZ1TukKVSVy&KuP3 zeBkd~Y$*7{MR#}ywM{y|g?u04Ef4bazfjuY;&TJ_gU9iDBOdJI)&m1BFzz3~gJJG| zho9#1kc jg{FB$-P@OMQt`1z&7F&ZLUV5kc}&mn76Tdy!jb<1jukaE literal 0 HcmV?d00001 diff --git a/api/routers/__pycache__/toolkit.cpython-313.pyc b/api/routers/__pycache__/toolkit.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f6e933b8e0cc472b53f9098e62ce2c6accfa12c3 GIT binary patch literal 2418 zcmbUjNo*5W^v~+CJ$9T7#3W9@Bn9FG?64##P^eN-0}ff5!H71IkclV3;EcncF*L1^ z3>=8m3q7Fb)(EMRd&>otE>I{{&ogl=n<=Z>3%7(IByN3wY$phzs$wMnx6IrBdvCn# z@wgB?7dm#QzU2}6n-JB9t02o)0l9``BvXATMqw&OV>(D^x{rymn6+`HkBjk`w{f=5 z5fiWwb7E%@jZ!VBt6~bfMqR`Hs)JQ1b22YGdgxKQ1&s<_RQb&=N)|@NU9{|MM5o!) z)QT0)ge&BJMubANO+Szt(=9%onV0|zR3y6t%o<-C^Tsb`&bVz}43fjVWX!|wM`J!{+%#?x zhM6-j8$TO2fP$Cc!l7j+Clh$u5-XNZBolgKtdfkK5|kKShTn1%gHS69lCn~rP{=T? zQX{qqIfm&KbOmWaGE*%~5XI4O^?Ft$5R%!d#9QBT)we1=LL#xBVWEnx!WI;#tFnjT zqEA7z=csGU04E}qi8E~#(Imtfr`~(FX!xoJm3^md94q=ohR*AU)b=+J}&@)a3?KHGY2mwAV;t>f#;MTS0gF>G&S02u zUegnLRZwQGS%Vbr(tO;8MWL(4upeIsETn|E)a5kGz zL1`M3g=aaZ(upx;Qc?8~Z42N=!lpB4FzGa014js!CSkHEbv$D+6N-*Wku5J^A5N2a z>?koAUdv@O2hXdpws^pM)9H|iq07KERhj>u7o*s7@@ ziguLb2)UFM4nVmr&c-dVB2$(ia}|8;u{tAh@TNsz-(qDtazHs3sVGDeQ>h5H_p@g6 zAcgSMblEB3Co0PlhrlM8T^)ivkL?h4a$|si)}a$weQzYfCl$nVOQPq3CHg zJ*XD(VITCJwkIu|WS9YrZ(N|AYQLHZzfJg~YYCyZh0>LjBOMshhiJ^{+0> zP2?xc&D{(B?z^enpiw_m3N%4#wc%XsuUh{MSMqNz`rA!^d%kPF$?VvFhb{O&nh_t> z1n!G#NJJ5mt0FS?M&Um;C#`}~%IxaX#FCuZ&+G{wO`#XU=YRJU$%9co;kJ2Z3L zWa~@*jWfqgc1_7GXjCQySmi7HY!qg{ZtgOch~i9 z1L7V>MH%)U-$d{hD$23*ovu~QjMhaCc+d%M@Z zL(3q8Fjf)MK>yA!>h!!jI9WB_6q?F9SJ#kw>&} uC2%n?BO8qaxqv|pK5Yoj1quz@mY7y*O{uQ_1%b;9qKBv@8c -
-
+ +
@@ -128,19 +129,19 @@ function prepareTabs() {
-
+
-
-
-
- Загрузка... -
+ +
+
+
+ Загрузка...
-
+
- +
`).join('')}
@@ -462,6 +463,249 @@ function setupFilters(tabId, tools, categoriesMap) { } } +function addToolbox() { + // Проверяем, существует ли уже модальное окно + let modal = document.getElementById('addToolboxModal'); + + if (modal) { + modal.remove(); + } + + // Создаем модальное окно + modal = document.createElement('div'); + modal.className = 'modal fade'; + modal.id = 'addToolboxModal'; + modal.tabIndex = -1; + modal.setAttribute('aria-hidden', 'true'); + + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + + // Инициализация модального окна + const bsModal = new bootstrap.Modal(modal); + + // Получаем элементы формы + const form = modal.querySelector('#addToolboxForm'); + const titleInput = modal.querySelector('#toolboxTitle'); + const descriptionInput = modal.querySelector('#toolboxDescription'); + const submitBtn = modal.querySelector('#submitToolboxBtn'); + const spinner = modal.querySelector('#submitToolboxSpinner'); + + // Функция валидации формы + function validateForm() { + let isValid = true; + + // Валидация названия + if (titleInput.value.length < 3) { + titleInput.classList.add('is-invalid'); + isValid = false; + } else { + titleInput.classList.remove('is-invalid'); + titleInput.classList.add('is-valid'); + } + + // Валидация описания (необязательно, но если заполнено - проверяем) + if (descriptionInput.value.length > 0 && descriptionInput.value.length < 3) { + descriptionInput.classList.add('is-invalid'); + isValid = false; + } else if (descriptionInput.value.length >= 3) { + descriptionInput.classList.remove('is-invalid'); + descriptionInput.classList.add('is-valid'); + } else { + descriptionInput.classList.remove('is-invalid', 'is-valid'); + } + + submitBtn.disabled = !isValid; + return isValid; + } + + // Слушатели событий для валидации в реальном времени + titleInput.addEventListener('input', function () { + if (this.value.length >= 3) { + this.classList.remove('is-invalid'); + this.classList.add('is-valid'); + } else { + this.classList.remove('is-valid'); + if (this.value.length > 0) { + this.classList.add('is-invalid'); + } else { + this.classList.remove('is-invalid'); + } + } + validateForm(); + }); + + descriptionInput.addEventListener('input', function () { + if (this.value.length === 0) { + this.classList.remove('is-invalid', 'is-valid'); + } else if (this.value.length >= 3) { + this.classList.remove('is-invalid'); + this.classList.add('is-valid'); + } else { + this.classList.add('is-invalid'); + this.classList.remove('is-valid'); + } + validateForm(); + }); + + // Обработчик отправки формы + form.addEventListener('submit', async function (e) { + e.preventDefault(); + + if (!validateForm()) { + return; + } + + // Показываем индикатор загрузки и отключаем кнопку + submitBtn.disabled = true; + spinner.style.display = 'inline-block'; + + // Собираем данные + const toolboxData = { + title: titleInput.value.trim(), + description: descriptionInput.value.trim(), + monitoring: modal.querySelector('#toolboxMonitoring').checked + }; + + const userId = userData.id; + + try { + // Отправка данных (замените на ваш реальный endpoint) + const response = await apiRequest("/toolbox/", { toolboxData, userId }); + + if (response.status !== 'ok') { + throw new Error('Ошибка при добавлении склада'); + } + + // Успешная отправка + bsModal.hide(); + + // Показываем уведомление об успехе + showInfo('Склад успешно добавлен', 'success'); + + // Здесь можно добавить обновление списка складов + await uploadTab('toolbox'); + + } catch (error) { + console.error('Ошибка при добавлении склада:', error); + + // Возвращаем кнопку в исходное состояние + submitBtn.disabled = false; + spinner.style.display = 'none'; + + // Показываем сообщение об ошибке + showInfo('Ошибка при добавлении склада. Попробуйте еще раз.', 'error'); + + // Можно добавить более детальное сообщение об ошибке + const errorDiv = document.createElement('div'); + errorDiv.className = 'alert alert-danger mt-3'; + errorDiv.innerHTML = ` + Ошибка! Не удалось добавить склад. + Проверьте соединение и попробуйте еще раз. + `; + + const modalBody = modal.querySelector('.modal-body'); + if (!modalBody.querySelector('.alert')) { + modalBody.appendChild(errorDiv); + + // Убираем сообщение через 5 секунд + setTimeout(() => { + if (errorDiv.parentNode) { + errorDiv.remove(); + } + }, 5000); + } + } + }); + + // Очистка при закрытии модального окна + modal.addEventListener('hidden.bs.modal', () => { + // Сбрасываем форму + form.reset(); + + // Убираем стили валидации + titleInput.classList.remove('is-valid', 'is-invalid'); + descriptionInput.classList.remove('is-valid', 'is-invalid'); + + // Включаем кнопку + submitBtn.disabled = false; + spinner.style.display = 'none'; + + // Убираем сообщения об ошибках + const alerts = modal.querySelectorAll('.alert'); + alerts.forEach(alert => alert.remove()); + + // Удаляем модальное окно из DOM + setTimeout(() => { + if (modal.parentNode) { + modal.remove(); + } + }, 300); + }); + + // Показываем модальное окно + bsModal.show(); + + // Фокусируемся на первом поле + setTimeout(() => { + titleInput.focus(); + }, 500); +} + function renderToolboxTab(tabData) { currentToolboxData = tabData; const tabContent = document.getElementById(`toolbox-tab-content`); @@ -479,10 +723,11 @@ function renderToolboxTab(tabData) { } // Создаем навигацию по складам + const toolboxNav = `
${tabData.map((toolbox, index) => ` - +
+ + `; + if (!toolboxInfo.owner_id && accessData.manage_toolboxes) { + document.getElementById('deleteToolbox').addEventListener('click', function (e) { + e.preventDefault(); + deleteToolbox(toolboxId); + }); + } else { + document.getElementById('deleteToolbox').remove(); + } + return; + } // Находим информацию о выбранном складе - const toolboxInfo = currentToolboxData.find(t => t.id === toolboxId); + const toolboxOwn = toolboxInfo.owner_id === userData.id ? 'Мой склад' : toolboxInfo.owner_id ? 'Склад сотрудника' : 'Общий склад'; const quantityMonitoring = toolboxInfo.monitoring && accessData.view_all_toolboxes; @@ -649,6 +935,182 @@ async function loadToolboxContent(toolboxId) { } } +async function deleteToolbox(toolboxId) { + // Находим информацию о складе + const toolboxInfo = currentToolboxData.find(t => t.id === toolboxId); + if (!toolboxInfo) { + showInfo('Склад не найден', 'error'); + return; + } + + // Проверяем, существует ли уже модальное окно + let modal = document.getElementById('deleteToolboxModal'); + + if (modal) { + modal.remove(); + } + + // Создаем модальное окно подтверждения + modal = document.createElement('div'); + modal.className = 'modal fade'; + modal.id = 'deleteToolboxModal'; + modal.tabIndex = -1; + modal.setAttribute('aria-hidden', 'true'); + + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + + // Инициализация модального окна + const bsModal = new bootstrap.Modal(modal); + + // Получаем элементы + const confirmCheckbox = modal.querySelector('#confirmDeleteCheckbox'); + const confirmBtn = modal.querySelector('#confirmDeleteBtn'); + const deleteSpinner = modal.querySelector('#deleteSpinner'); + + // Активация кнопки при подтверждении + confirmCheckbox.addEventListener('change', function () { + confirmBtn.disabled = !this.checked; + }); + + // Обработчик кнопки удаления + confirmBtn.addEventListener('click', async function () { + if (!confirmCheckbox.checked) return; + + // Показываем индикатор загрузки и отключаем кнопку + confirmBtn.disabled = true; + deleteSpinner.style.display = 'inline-block'; + + try { + // Отправляем запрос на удаление + const userId = userData.id; + const resp = await apiRequest('/toolbox/', { toolboxId, userId }, 'DELETE'); + + // Проверяем успешность запроса + if (resp.status == 'ok') { + // Успешное удаление + bsModal.hide(); + showInfo('Склад успешно удален', 'success'); + + await uploadTab('toolbox'); + + } else { + // Обработка ошибок от сервера + let errorMessage = 'Не удалось удалить склад'; + + if (resp.message) { + errorMessage += ': ' + resp.message; + } + + // Показываем конкретное сообщение об ошибке + showInfo(errorMessage, 'error'); + + // Возвращаем кнопку в исходное состояние + confirmBtn.disabled = false; + deleteSpinner.style.display = 'none'; + } + + } catch (error) { + console.error('Ошибка при удалении склада:', error); + + // Возвращаем кнопку в исходное состояние + confirmBtn.disabled = false; + deleteSpinner.style.display = 'none'; + + // Показываем общее сообщение об ошибке + showInfo('Произошла ошибка при удалении склада. Попробуйте еще раз.', 'error'); + } + }); + + // Очистка при закрытии модального окна + modal.addEventListener('hidden.bs.modal', () => { + // Удаляем модальное окно из DOM + setTimeout(() => { + if (modal.parentNode) { + modal.remove(); + } + }, 300); + }); + + // Показываем модальное окно + bsModal.show(); +} + // Функция обработки данных склада function processToolboxData(toolboxData, toolboxId, quantityMonitoring) { const { stocks, toolkits, categories } = toolboxData; @@ -1009,7 +1471,7 @@ async function initializeToolboxTable(data, toolboxOwn, quantityMonitoring) { async function getToolkitStocks(toolkitId) { const userId = userData.id; const allToolboxes = accessData.view_all_toolboxes; - const resp = await apiRequest('/stocks/toolkit', { toolkitId, userId, allToolboxes }); + const resp = await apiRequest('/toolkit/', { toolkitId, userId, allToolboxes }); return resp.data; } @@ -1272,6 +1734,7 @@ async function showOperationModal(operation, selectedItem) { if (success) { bsModal.hide(); + showInfo(`Запрос на ${operationTitles[operation]} успешно создан`, 'success'); await loadToolboxContent(selectedItem.toolboxId); } else { showError('Ошибка выполнения операции'); diff --git a/api/static/js/toast.js b/api/static/js/toast.js new file mode 100644 index 0000000..a57791a --- /dev/null +++ b/api/static/js/toast.js @@ -0,0 +1,68 @@ +// Вспомогательная функция для показа уведомлений с использованием Bootstrap Toasts +export function showInfo(message, type = 'info') { + // Создаем контейнер для тостов если его нет + let toastContainer = document.getElementById('toast-container'); + if (!toastContainer) { + toastContainer = document.createElement('div'); + toastContainer.id = 'toast-container'; + toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3'; + toastContainer.style.cssText = ` + z-index: 99999; + max-width: 350px; + `; + document.body.appendChild(toastContainer); + } + + // Создаем уникальный ID для тоста + const toastId = 'toast-' + Date.now(); + + // Определяем классы в зависимости от типа + const typeConfig = { + 'success': { bgClass: 'bg-success', icon: 'bi-check-circle', delay: 5000 }, + 'error': { bgClass: 'bg-danger', icon: 'bi-exclamation-circle', delay: 10000 }, + 'info': { bgClass: 'bg-info', icon: 'bi-info-circle', delay: 3000 }, + 'warning': { bgClass: 'bg-warning', icon: 'bi-exclamation-triangle', delay: 8000 } + }; + + const config = typeConfig[type] || typeConfig.info; + + // Создаем тост + const toast = document.createElement('div'); + toast.className = `toast ${config.bgClass} text-white`; + toast.id = toastId; + toast.setAttribute('role', 'alert'); + toast.setAttribute('aria-live', 'assertive'); + toast.setAttribute('aria-atomic', 'true'); + + toast.innerHTML = ` +
+ + Уведомление + Только что + +
+
+ ${message} +
+ `; + + toastContainer.appendChild(toast); + + // Инициализируем и показываем тост + const bsToast = new bootstrap.Toast(toast, { + animation: true, + autohide: true, + delay: config.delay + }); + + bsToast.show(); + + // Удаляем тост после скрытия + toast.addEventListener('hidden.bs.toast', function () { + setTimeout(() => { + if (toast.parentNode) { + toast.remove(); + } + }, 300); + }); +} \ No newline at end of file diff --git a/db/handlers/__pycache__/toolkit.cpython-313.pyc b/db/handlers/__pycache__/toolkit.cpython-313.pyc index cfa9592c47049ac78a51908650277783ce00240c..fa66782743908a7794a1e78b13ebcec40447277e 100644 GIT binary patch delta 1665 zcmaKsZ)j6j6u|FIUjDtL>7SSWOVjqHOV`bg_Bso=sD^ z?^Us&+dk~ugvk{t2;w#dGK4t-PWH`x(3ZJLEZFj3n`1Hs9kd(6fiQMY5|JtFe)!#U z@A-4jJNLZ%%iQgp{m+UD98^f%Xb6`W@%@Oqx=4 zU?}fQ!eUqiG?qmu$ERw`q0uD&Kj-?G4_`XB_2qL}zLSQeg&mI)t@IPSK>v2w3?m4! z9Ara##nems%LZFV4k0TN(bS=&5i(9$o@J7(3bK-n;C`HN)80BKePW1L+n#-`h=Rz2 zQaXCZ*kVjpOj;8*`azw;nY8!mpWCE5pG8Y-qt(^57M!e{L_r+U(JezAy=}BwuA0%o zc{fV6(O-?ddTXkl)|>X?YC3KDv1-Y#sA*N63CQ#5SxE`dLGwLaOPejxW{sB>Imgtw z8A;PCD$#hhAgXd)R^O!)77smXS#IrN$7?kc3k%sfSshE~GFgfE)0s>S+gIE=8s?8; z7yX{{fmD}LWMd~I>fw}1J-&#k`YlN+M6eL^vi8)|-ep0IvY_fI)y8Fhswv+>Kw&S5?)R@ow7Qum{KJ zY{L&}_@{`MK{I5e!;7;e@9nS)y;BUUadXlMg;-%-yiudNFe1;W-YOCWPvhM2G?`V1 z9}EHj8^8_-(sPYLcZi7^zj!pekk0IvS+_KUt{yN9h|mX(Elx;W6JF>RDI=@Ne5+IC z>JKu#S6@6vGpnVhQb>S)(GtLC@>NL*~(uzA*)$QGZINz%&H{9REmi~EDch{ zWRd}g+83&qk?8wf9R4i-VONyn_AwKRdxZSj_MJMsKm~ugjlF}2D1hRfs30u@jscD{ z&`178`lbJ&H3vKMfQ9_00Sk5?0r`3Nu?Mc)g3u*iBRN8E1zQ|TY@yi>rxmH39NgJI z!5IEJzoYk#E&#@4CxE?`<@POx64_GYvd}MYg9g5koT9PtE+J@eE~}e}O_yf=f@w2D dPlq4kPI@fTg-_E9k#OZv4p;txfCS!~e*yhFilG1i delta 1278 zcmZvaUuauZ9LLWu_a?ddpEYT-tX-S5$(GoqNz-Oa(zPyIYa*7ib&0wKl(fmVko1oy zxA+H91W}kel;0jU_T+=Om!adn2_h5QKKKt6K`tm0eDJYTe2{{8ez(Et@H~9(_xI=g z?m55n`+D`~miu>?%g)4_do|NsnYisPNMP4aOHYFJGGi6C=)-%y0sLGZ!*k}GM@h1b ziP9NcwN(T7uG!aX%U@-zHZHPlK2{nP5qq^4AG3_O0>WoLtC&UhVi3>t`0$Fw2?@Mm z2|7F}QGFJU!eT#uVyS`y`>jC;;qATv&Rh56-9DG8I*g;%L3;(xx+^RN857pRef|)u zI3i48MOc_q*uqMfRR?g>Ixg9(L-?n)2wp5GKjN6}F8I)6KZeWph%9wfj<4CT!zhLv z1E!AwXB_L$gC983{!seE+;^ebw_QK^LO;YZ*DieHk7C#v#q7bTl-^PBJ?9IyhnX*a zFUAf`HrE3x(gC@@z$ZKWKN07*hZaz|u zTe&O*e~92GrvnYz7sqJS^PfuRV_-T;{`e1|*87O%>5i@G-3Tvp>=o-xeUvo(IP!;erxg5V^$31cYtr_QB>)a@JV z?X_BczA0YdAjQN~Fn*jcA<*#(gdSL&oW_}SUwLh<-D;|(+G@Ss;A4$?Q+-r>t6!A^ z^CBrO8UNQ6buX>M6SzN=gx9rF=mr$#D8-1S{NCQ z!<*W|@D>!x!W~!sub3`1SD*e@MWc?)&;L`WLMnlkNEWVYS0cM6n8VA5zkm`x5sSY3 z-@f1PxOimaC`QMQ7$pRBXZNnO=;vq|Fn5qXc; F`8QZ$9e4l$ diff --git a/db/handlers/__pycache__/user.cpython-313.pyc b/db/handlers/__pycache__/user.cpython-313.pyc index 375a4c1ee6987b9ad6ae8b5701a46c8b44117c36..a59aea7321561482b96d5f39e5b112abe7b3f758 100644 GIT binary patch delta 560 zcmXw#PiPZC6vp?>&Sp0u+qC&NnQcNVEhN!~uBkO;jf7OBm|fx_fMV6y%q1iDLU}p_j~ir49xd^f3R;~*mgq0^>(uQ zICErw(#eaM_?>mFlmHuN2+&dOVfT_Oj`uLOR` zhp7=*eEytQ&v;e3GxKoJgi{Or8}u~3UJaTsTq*jVHcEltprdK3INPqHF^)m7O2=l~ z!yyC+)@VrD42OtA;L*sLRono)5_yTBu21MfP}3)6P+T$4ozx7)b*gv`R4;l;wu#E= z-!f5OV_h7}U+IAkFU5!amCKymJmxed+&ni@D|+7&?=o4^n-)p71vBC)lV;&Eh-?s?gI?u-Qs(&O6BvCdscGg&r%T%IGi-0lcguO?{J^PkmWj# zHe7-Zqr)0ie0}{U8sjqfIXbSi84llC;4>O(IpmN5o(i3WpVq_01>e>q6ObdzKsT}t U#c8UT0ru_xKUHeTS95;xFDjRUPXGV_ delta 595 zcmX|-&uvwNw{qL#nNt25L%Vkr+1|jECBosMT8&)5J^F zo{Tr&#e*@?gFkvwU*gFNsSvV@3BAb8c=ks82b|^LByZ+3`Odt&_dVMGu`mCX_R z9XZ{JII4(OML8#fLk%&D6BYULTUuLdf9aB!v=jFxk4XnH`p)D2Kd5? z`U_AB=7&e)QvIY