diff --git a/app/Http/Controllers/MenuController.php b/app/Http/Controllers/MenuController.php new file mode 100644 index 0000000..a70a8b3 --- /dev/null +++ b/app/Http/Controllers/MenuController.php @@ -0,0 +1,133 @@ +userLogin = 'dgavrilov'; + } + + + /** + * Получение всех записей из таблицы с приложениями для меню + * TODO + * передавай флаг isActive и используй его для получения записей с приложениями меню архивных и не архивных + * + * @return mixed + */ + public function getApps() + { + //Приложения старого мэджик из скрипта Config_AD + $oldApps = $this->getOldApps(); + #Гаврилов + //НОВЫЕ ПРИЛОЖЕНИЯ НЕ ИСПОЛЬЗУЕШЬ + //Приложения нового мэджик из базы данных + #Гаврилов + //отрисовывать только то, что может видеть пользователь + $newApps = Models\MagicApps::get()->toArray(); + + return response()->json($oldApps); + } + + + /** + * Метод получает конфиг со старыми приложениями Magic + * + * @return string + */ + public function getOldApps() + { + $oldAppConf = json_decode(file_get_contents('http://cs/magic/return_config.AD.php')); + + return $oldAppConf; + } + + + /** + * Метод должен проверить есть ли избранные приложения у пользователя. Если есть, их необходимо обновить либо добавив, либо убрав то приложение, по которому кликнул пользователь. Если избранных приложений нет - вставить новую запись + * + * @return boolean + */ + public function updateUserFavApp(Request $rqst) + { + #Гаврилов + //ВНИЗУ ИСПОЛЬЗУЕТСЯ ЛОГИН DGAVRILOV, ПЕРЕПИШИ НА ЛОГИН ПОЛЬЗОВАТЕЛЯ MAGIC + $userFavApp = $this->getUserFavApp('dgavrilov'); + $appName = $rqst->all()['appName']; + $newFavAppList = null; + //Если массив с избранными приложениями пустой - пользователь не добавил ни 1 приложения мэджик в избранное + if (!$userFavApp) { + $newFavAppList = $appName; + } else { + $currentFavApp = $userFavApp; + if (in_array($appName, $currentFavApp) !== false) { + $newFavAppList = implode(';', array_filter($currentFavApp, function($el) use($appName) {return $el !== $appName;})); + } else { + $newFavAppList = implode(';', array_merge($currentFavApp, [$appName])); + } + } + + if ($newFavAppList) { + #Гаврилов + //ПРИ ВЫЗОВЕ ИЗ REACT НЕ ОБНОВЛЯЕТСЯ ПОЛЕ UPDATE_AT + $this->insertFavApp($newFavAppList); + //Если список приложений пуст - просто удаляем запись с избранными приложениями пользователя + } else { + $this->delAllUserFavApp('dgavrilov'); + } + return $this->getUserFavApp('dgavrilov'); + } + + #Гаврилов + //try catch для отслеживания ошибок вставки записей в БД? + + /** + * Метод вставки новой записи с избранными приложениями + * + * @return boolean + */ + public function insertFavApp($appName) + { + Models\UserFavApp::updateOrCreate( + ['user_login' => $this->userLogin], + ['fav_apps' => $appName] + ); + + return true; + } + + + /** + * Метод удаляет запись с избранными приложениями пользователя + * + * @param string $userLogin логин пользователя + * @return boolean + */ + public function delAllUserFavApp($userLogin) + { + Models\UserFavApp::where('user_login', $userLogin)->delete(); + + return true; + } + + + /** + * Метод получает избранные приложения пользователя + * + * @param string $userLogin логин пользователя + * @return array + */ + public function getUserFavApp(string $userLogin): array + { + #Гаврилов + //ЗДЕСЬ ВМЕСТО DGAVRILOV ДОЛЖНО БЫТЬ ОБРАЩЕНИЕ К КУКАСАМ, ЧТОБЫ БРАТЬ ЛОГИН ОТТУДА + $userFavApp = Models\UserFavApp::where('user_login', $userLogin)->first(); + + return $userFavApp ? explode(';', $userFavApp['fav_apps']) : []; + } +} diff --git a/app/Models/AppRoles.php b/app/Models/AppRoles.php new file mode 100644 index 0000000..4f8bd18 --- /dev/null +++ b/app/Models/AppRoles.php @@ -0,0 +1,18 @@ +belongsTo(MagicApps::class, 'app_id', 'id'); + } +} diff --git a/app/Models/MagicApps.php b/app/Models/MagicApps.php new file mode 100644 index 0000000..6497177 --- /dev/null +++ b/app/Models/MagicApps.php @@ -0,0 +1,19 @@ +hasMany(AppRoles::class, 'app_id', 'id') + ->orderBy('role_priority', 'asc'); + } +} diff --git a/app/Models/UserFavApp.php b/app/Models/UserFavApp.php new file mode 100644 index 0000000..04740af --- /dev/null +++ b/app/Models/UserFavApp.php @@ -0,0 +1,17 @@ +#menu__left-block{ + flex-basis: 15%; + + &>.menu__left-block__call-app{ + display: flex; + + &>.fa{ + flex-basis: 20%; + } + } + } +} + +.switcher-container{ + position: fixed; + top: 0; + right: 0; + display: flex; + justify-content: flex-end; + align-items: center; + + &>.switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; + margin: 9px; + + &>.switcher__favorite-app { + opacity: 0; + width: 0; + height: 0; + + &.showFav + .slider{ + background-color: var(--color_ruby); + + &:before{ + transform: translateX(26px); + color: var(--color_ruby); + padding: 2px 0 0 0; + align-content: baseline; + } + } + } + + &>.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s cubic-bezier(0,1,0.5,1); + border-radius: 4px; + + @import url('https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css'); + &:before { + position: absolute; + content: "\f006"; + font-family: FontAwesome; + text-align: center; + font-size: 1.2rem; + height: 26px; + width: 26px; + left: 4px; + bottom: 4px; + background-color: white; + -webkit-transition: .4s; + transition: .4s cubic-bezier(0,1,0.5,1); + border-radius: 3px; + } + + &.round { + border-radius: 34px; + + &:before { + border-radius: 50%; + } + } + } + } +} + + + + +.switcher__favorite-app.showFav + .slider { + background-color: var(--color_ruby); +} +.switcher__favorite-app.showFav + .slider:before { + transform: translateX(26px); +} +/* Rounded sliders */ +/* .slider.round { + border-radius: 34px; + + &:before { + border-radius: 50%; + } +} */ + +#round { + border-radius: 34px; + + &:before { + border-radius: 50%; + } +} + +#menu__app-container__app-block{ + /* display: grid; + grid-template-columns: 50% 50%; */ + column-count: 2; + + >.apps-block__proc{ + margin-bottom: 20px; + + >.proc__title{ + font-size: 1.1rem; + padding: 10px 0; + font-weight: 600; + } + + >.proc__title.proc-hide{ + display: none; + } + } + + .script-list__el{ + display: flex; + + >.script-list__el__script-name{ + margin-bottom: 5px; + transition: 0.2s; + &:hover{ + transform: translateY(-1px); + } + + >a{ + text-decoration: none; + color: black; + + &:hover{ + color: var(--color_ruby); + } + } + } + + >.fa-star{ + margin-left: 5px; + cursor: pointer; + opacity: 0.2; + transition: 0.3s; + } + + >.fa-star:hover{ + opacity: 1; + } + + >.fa-star.favorite{ + opacity: 1; + color: var(--color_ruby); + } + } + + .script-list__el.script-hide{ + display: none; + } } diff --git a/resources/js/app.tsx b/resources/js/app.tsx new file mode 100644 index 0000000..5b23c21 --- /dev/null +++ b/resources/js/app.tsx @@ -0,0 +1,14 @@ +import { createRoot } from 'react-dom/client'; +import '@SharePoint/rencredit_uikit/dist/static/fonts/mont/Mont.css'; +import '@fortawesome/fontawesome-free/css/all.css'; +import { UIKitThemeProvider } from '@SharePoint/rencredit_uikit'; +import MenuApp from './components/MenuApp.tsx'; // Создайте этот файл, если используете React +import React from 'react'; + +const container:HTMLElement = document.getElementById('root')!; +const root = createRoot(container); +root.render( + + + +); diff --git a/resources/js/components/MenuApp.tsx b/resources/js/components/MenuApp.tsx new file mode 100644 index 0000000..16e61b1 --- /dev/null +++ b/resources/js/components/MenuApp.tsx @@ -0,0 +1,231 @@ +import { Button } from '@SharePoint/rencredit_uikit'; +import React, { useEffect, useState } from "react"; + +function MenuApp () +{ + //Приложения и процессы Magic для отображения в меню + const [menuApps, setMenuApps] = useState<{ + 'proccesses': object, + 'scripts': object + }>({ + 'proccesses': {}, + 'scripts': {} + }); + //TODO + //ВНЕДРИ В КОНТРОЛЛЕРЕ ПРОВЕРКУ ДОСТУПОВ, А НЕ ВОЗВРАЩАЙ ВСЕ ПОДРЯД ПРИЛОЖЕНИЯ + //Избранные приложения пользователя + const [favApps, setFavApps] = useState([]); + //Массив избраннных процессов, в приложениях которых есть хотя бы одно избранное + const [favProcs, setFavProcs] = useState([]); + //Состояния видимости скрипта + const [hideScriptClass, setHideScriptClass] = useState(''); + //const [sanctumToken, setSanctumToken] = useState(''); + + const sanctumToken = () => { + const metaTag = document.getElementById('sanctum_token_block') as HTMLMetaElement; + return metaTag.dataset.token; + } + + //Получаем при рендере страницы все приложения Magic и все избранные приложения пользователя + useEffect( () => { + Promise.all( + [ + fetch('api/magic_apps').then(menuAppsRes => menuAppsRes.json()), + //TODO + //СЮДА ПОТЯГИВАЙ ЛОГИН ПОЛЬЗОВАТЕЛЯ, А НЕ DGAVRILOV + fetch('api/user_fav_app/dgavrilov').then(favAppsRes => favAppsRes.json()) + ] + ).then( + ([ + responseMenuApp, + responseFavApp + ]) => { + setMenuApps(responseMenuApp); + setFavApps(responseFavApp); + } + ) + }, []); + + + //Обновление видимости процесса (собрания приложений) в зависимости от того находится ли в избранном хотя бы одно приложение + useEffect( () => { + if (menuApps === null) { return } + //Массив с процессами, в которых есть хотя бы одно избранное приложение + const favProcsArr:string[] = []; + //Мы собираем все избранные приложения в массив-состояние, чтобы при изменении состояния каждого приложения (например при переключении switcher) проверять входит ли это приложение в избранные. Если входит - его родительский процесс не прячем, так как в его дочерних приложениях есть избранные + Object.entries(menuApps.proccesses).forEach( (procData) => { + //Флаг - есть ли в приложениях процесса хотя бы 1 избранное + const hasFavorite: boolean = procData[1].tabs.split(';').some( (tab: string) => { + return favApps.includes(tab); + }) + if (hideScriptClass !== 'script-hide' || hasFavorite) { + favProcsArr.push(procData[0]); + } + }) + setFavProcs(favProcsArr); + }, [hideScriptClass, menuApps, favApps]); + + //console.log(sanctumToken()) + + //ГАВРИЛОВ + //ПОЧЕМУ КОД НИЖЕ ИСПОЛНЯЕТСЯ ПОСТОЯННО ПОКА НЕ БУДЕТ ПОЛУЧЕН КОНТЕНТ ДЛЯ СТРАНИЦЫ ИЗ ПРОМИСОВ ВЫШЕ, А НЕ ОТРАБАТЫВАЕТ ТОЛЬКО ПРИ РЕНДЕРИНГЕ СТРАНИЦЫ + //Уместно ли отслеживать состояние до получения запросов fetch в подобном виде? + if (menuApps === null || favApps === null) { + return прелоадер + } else { + return ( + + ) + } +} + + +//Гаврилов +//ПЕРЕПИСАТЬ АРГУМЕНТЫ ПОД ОБЪЕКТ СО СВОЙСТВАМИ, ИСПОЛЬЗОВАТЬ ...obj +/** + * + * @param {int} appIndex ключ для экземлпяра компонента + * @param {string} appName уникальное имя скрипта + * @param {string} appTitle название скрипта в меню + * @param {string} favIconClassName класс для иконки избранного + * @param {string} hideAppClass класс для видимости приложения + * @param {string} setUpdateFavApps функция для обновления состояния избранны приложений (при снятия, установки признака избранности) + * @returns + */ +function AppElem({ + appIndex, + appName, + appTitle, + appUrl, + favIconClassName, + hideAppClass, + setUpdateFavApps +} : { + appIndex: string, + appName: string, + appTitle: string, + appUrl: string, + favIconClassName: string, + hideAppClass: string, + setUpdateFavApps: string +}) { + const [appElemClass, changeFav] = useState(favIconClassName); + const callChangeFav = ( changeAppName: string ) => { + fetch('api/user_fav_app', { + method: 'post', + body: JSON.stringify({ appName: changeAppName }), + headers: { + 'content-type': 'application/json' + } + } + ) + .then( (response) => response.json() ) + .then( (response) => { + //Если скрипт был избранным - удаляем из избранного и наоборот + appElemClass == 'favorite' ? changeFav('not_favorite') : changeFav('favorite'); + //Возвращаем обновленный список избранных приложений пользователя + setUpdateFavApps(response?.fav_apps?.split( ';' ) || []) + } ) + } + + return ( + //Если приложение в избранном - оно всегда должно быть "видимо", даже если переключатель в положении Скрыть неизбранные приложения +
+
{ appTitle }
+ {/* Анонимная функция нужна для создания функции смены состояния для каждого компонента в отдельности, в противном случае вызов функции в одном экземпляре компонента вызывает функцию изменения для каждого экземпляра компонента */} + callChangeFav(appName) }> +
+ ) +} + + +//TODO +//ВЫНЕСИ В ПАПКУ COMPONENTS/MAIN +/** + * Компонент переключателя В ДАННОМ СЛУЧАЕ видимости скриптов (видны только избранные или все) + * @param {string} switcherId идентификатор переключателя (для стилей css), так как в будущем, на странице можно будет размещать несколько переключателей + * @param {function} toggleAppsVisible функция обновления состояния hideScriptClass + * @returns + */ +function Switcher({ switcherId, toggleAppsVisible }) { + const [favSwitcherClass, setFavSwitcherClass] = useState('showAll'); + + //Переключение состояния переключателя после рендеринга страницы + useEffect( () => { + //Получаем из локал сторадж состояние переключателя + const savedState = localStorage.getItem('magicMenuFavSwitcher'); + if (savedState) { + setFavSwitcherClass(savedState); + } + }, []); + + //При изменении состояния Переключателя изменяется состояние всех приложени (переключается их видимость в зависимости от состояния переключателя - только избранные или все) + useEffect( () => { + //Синхронизируем зависимые компоненты + toggleAppsVisible(favSwitcherClass === 'showFav' ? 'script-hide' : ''); + }, [favSwitcherClass] ); + + const callToggleFavSwitcher = () => { + let switcherState = (favSwitcherClass === 'showFav' ? 'showAll' : 'showFav'); + //favSwitcherClass => favSwitcherClass = ... - функциональное обновление. Здесь оно не особо нужно, но чтобы не забыть + //Меняем класс switcher, а также меняем состояние видимости всех приложений (в зависимости от того избранные они или нет) + setFavSwitcherClass( favSwitcherClass => { + if (favSwitcherClass === 'showFav') { + toggleAppsVisible('script-hide') + } else { + toggleAppsVisible('') + } + localStorage.setItem('magicMenuFavSwitcher', switcherState) + + return switcherState; + } ) + } + + return ( +
+ +
+ ) +} + +export default MenuApp; diff --git a/resources/views/menu_start.blade.php b/resources/views/menu_start.blade.php new file mode 100644 index 0000000..b6ccd81 --- /dev/null +++ b/resources/views/menu_start.blade.php @@ -0,0 +1,17 @@ + + + + + + Laravel + React + + + @viteReactRefresh + @vite(['resources/css/app.css', 'resources/js/app.jsx']) + + + + +
+ + \ No newline at end of file diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..7edc9b0 --- /dev/null +++ b/routes/api.php @@ -0,0 +1,23 @@ +name('magic_menu');