diff --git a/Application/Timerecording/Application.php b/Application/Timerecording/Application.php new file mode 100644 index 0000000..31cd8a4 --- /dev/null +++ b/Application/Timerecording/Application.php @@ -0,0 +1,466 @@ +app = $app; + $this->app->appName = 'Timerecording'; + $this->config = $config; + UriFactory::setQuery('/app', \strtolower($this->app->appName)); + } + + /** + * Rendering timerecording. + * + * @param HttpRequest $request Request + * @param HttpResponse $response Response + * + * @return void + * + * @since 1.0.0 + */ + public function run(HttpRequest $request, HttpResponse $response) : void + { + $this->app->l11nManager = new L11nManager($this->app->appName); + + $pageView = new TimerecordingView($this->app->l11nManager, $request, $response); + $head = new Head(); + + $pageView->setData('head', $head); + $response->set('Content', $pageView); + + /* Timerecording only allows GET */ + if ($request->getMethod() !== RequestMethod::GET) { + $this->create406Response($response, $pageView); + + return; + } + + $this->app->dbPool = new DatabasePool(); + $this->app->router = new WebRouter(); + $this->app->router->importFromFile(__DIR__ . '/Routes.php'); + $this->app->router->add( + '/timerecording/e403', + function() use ($request, $response) { + $view = new View($this->app->l11nManager, $request, $response); + $view->setTemplate('/Web/Timerecording/Error/403_inline'); + $response->getHeader()->setStatusCode(RequestStatusCode::R_403); + + return $view; + }, + RouteVerb::GET + ); + + $this->app->sessionManager = new HttpSession(60); + $this->app->cookieJar = new CookieJar(); + $this->app->moduleManager = new ModuleManager($this->app, __DIR__ . '/../../Modules'); + $this->app->dispatcher = new Dispatcher($this->app); + + $this->app->dbPool->create('select', $this->config['db']['core']['masters']['select']); + + /* Database OK? */ + if ($this->app->dbPool->get()->getStatus() !== DatabaseStatus::OK) { + $this->create503Response($response, $pageView); + + return; + } + + /* CSRF token OK? */ + if ($request->getData('CSRF') !== null + && !\hash_equals($this->app->sessionManager->get('CSRF'), $request->getData('CSRF')) + ) { + $response->getHeader()->setStatusCode(RequestStatusCode::R_403); + + return; + } + + /** @var ConnectionAbstract $con */ + $con = $this->app->dbPool->get(); + DataMapperAbstract::setConnection($con); + + $this->app->cachePool = new CachePool(); + $this->app->appSettings = new CoreSettings($con); + $this->app->eventManager = new EventManager($this->app->dispatcher); + $this->app->accountManager = new AccountManager($this->app->sessionManager); + $this->app->l11nServer = LocalizationMapper::get(1); + + $this->app->orgId = $this->getApplicationOrganization($request, $this->config); + $pageView->setData('orgId', $this->app->orgId); + + $aid = Auth::authenticate($this->app->sessionManager); + $request->getHeader()->setAccount($aid); + $response->getHeader()->setAccount($aid); + + $account = $this->loadAccount($request); + + if (!($account instanceof NullAccount)) { + $response->getHeader()->setL11n($account->getL11n()); + } elseif ($this->app->sessionManager->get('language') !== null) { + $response->getHeader()->getL11n() + ->loadFromLanguage( + $this->app->sessionManager->get('language'), + $this->app->sessionManager->get('country') ?? '*' + ); + } elseif ($this->app->cookieJar->get('language') !== null) { + $response->getHeader()->getL11n() + ->loadFromLanguage( + $this->app->cookieJar->get('language'), + $this->app->cookieJar->get('country') ?? '*' + ); + } + + UriFactory::setQuery('/lang', $response->getHeader()->getL11n()->getLanguage()); + + $this->loadLanguageFromPath( + $response->getHeader()->getL11n()->getLanguage(), + __DIR__ . '/lang/' . $response->getHeader()->getL11n()->getLanguage() . '.lang.php' + ); + + $response->getHeader()->set('content-language', $response->getHeader()->getL11n()->getLanguage(), true); + + /* Create html head */ + $this->initResponseHead($head, $request, $response); + + /* Handle not logged in */ + if ($account->getId() < 1) { + $this->createLoggedOutResponse($response, $head, $pageView); + + return; + } + + /* No reading permission */ + if (!$account->hasPermission(PermissionType::READ, $this->app->orgId, $this->app->appName, 'Dashboard')) { + $this->create403Response($response, $pageView); + + return; + } + + $this->app->moduleManager->initRequestModules($request); + $this->createDefaultPageView($request, $response, $pageView); + + $dispatched = $this->app->dispatcher->dispatch( + $this->app->router->route( + $request->getUri()->getRoute(), + $request->getData('CSRF'), + $request->getRouteVerb(), + $this->app->appName, + $this->app->orgId, + $account, + $request->getData() + ), + $request, + $response + ); + $pageView->addData('dispatch', $dispatched); + } + + /** + * Get application organization + * + * @param HttpRequest $request Client request + * @param array $config App config + * + * @return int Organization id + * + * @since 1.0.0 + */ + private function getApplicationOrganization(HttpRequest $request, array $config) : int + { + return (int) ( + $request->getData('u') ?? ( + $config['domains'][$request->getUri()->getHost()]['org'] ?? $this->app->appSettings->get( + Settings::DEFAULT_ORGANIZATION + ) ?? 1 + ) + ); + } + + /** + * Create 406 response. + * + * @param HttpResponse $response Response + * @param View $pageView View + * + * @return void + * + * @since 1.0.0 + */ + private function create406Response(HttpResponse $response, View $pageView) : void + { + $response->getHeader()->setStatusCode(RequestStatusCode::R_406); + $pageView->setTemplate('/Web/Timerecording/Error/406'); + $this->loadLanguageFromPath( + $response->getHeader()->getL11n()->getLanguage(), + __DIR__ . '/Error/lang/' . $response->getHeader()->getL11n()->getLanguage() . '.lang.php' + ); + } + + /** + * Create 406 response. + * + * @param HttpResponse $response Response + * @param View $pageView View + * + * @return void + * + * @since 1.0.0 + */ + private function create503Response(HttpResponse $response, View $pageView) : void + { + $response->getHeader()->setStatusCode(RequestStatusCode::R_503); + $pageView->setTemplate('/Web/Timerecording/Error/503'); + $this->loadLanguageFromPath( + $response->getHeader()->getL11n()->getLanguage(), + __DIR__ . '/Error/lang/' . $response->getHeader()->getL11n()->getLanguage() . '.lang.php' + ); + } + + /** + * Load theme language from path + * + * @param string $language Language name + * @param string $path Language path + * + * @return void + * + * @since 1.0.0 + */ + private function loadLanguageFromPath(string $language, string $path) : void + { + /* Load theme language */ + if (($absPath = \realpath($path)) === false) { + throw new PathException($path); + } + + /** @noinspection PhpIncludeInspection */ + $themeLanguage = include $absPath; + $this->app->l11nManager->loadLanguage($language, '0', $themeLanguage); + } + + /** + * Load permission + * + * @param HttpRequest $request Current request + * + * @return Account + * + * @since 1.0.0 + */ + private function loadAccount(HttpRequest $request) : Account + { + $account = AccountMapper::getWithPermissions($request->getHeader()->getAccount()); + $this->app->accountManager->add($account); + + return $account; + } + + /** + * Create 406 response. + * + * @param HttpResponse $response Response + * @param View $pageView View + * + * @return void + * + * @since 1.0.0 + */ + private function create403Response(HttpResponse $response, View $pageView) : void + { + $response->getHeader()->setStatusCode(RequestStatusCode::R_403); + $pageView->setTemplate('/Web/Timerecording/Error/403'); + $this->loadLanguageFromPath( + $response->getHeader()->getL11n()->getLanguage(), + __DIR__ . '/Error/lang/' . $response->getHeader()->getL11n()->getLanguage() . '.lang.php' + ); + } + + /** + * Initialize response head + * + * @param Head $head Head to fill + * @param HttpRequest $request Request + * @param HttpResponse $response Response + * + * @return void + * + * @since 1.0.0 + */ + private function initResponseHead(Head $head, HttpRequest $request, HttpResponse $response) : void + { + /* Load assets */ + $head->addAsset(AssetType::CSS, 'Resources/fontawesome/css/font-awesome.min.css'); + $head->addAsset(AssetType::CSS, 'cssOMS/styles.css'); + $head->addAsset(AssetType::CSS, '//fonts.googleapis.com/css?family=Roboto:100,300,300i,400,700,900'); + + // Framework + $head->addAsset(AssetType::JS, 'jsOMS/Utils/oLib.js'); + $head->addAsset(AssetType::JS, 'jsOMS/UnhandledException.js'); + $head->addAsset(AssetType::JS, 'Web/Timerecording/js/timerecording.js', ['type' => 'module']); + $head->addAsset(AssetType::JSLATE, 'Modules/Navigation/Controller.js', ['type' => 'module']); + + $script = ''; + $response->getHeader()->set( + 'content-security-policy', + 'base-uri \'self\'; script-src \'self\' blob: \'sha256-' + . \base64_encode(\hash('sha256', $script, true)) + . '\'; worker-src \'self\'', + true + ); + + if ($request->hasData('debug')) { + $head->addAsset(AssetType::CSS, 'cssOMS/debug.css'); + } + + $css = \file_get_contents(__DIR__ . '/css/timerecording-small.css'); + if ($css === false) { + $css = ''; + } + + $css = \preg_replace('!\s+!', ' ', $css); + $head->setStyle('core', $css ?? ''); + $head->setTitle('Orange Management Timerecording'); + } + + /** + * Create logged out response + * + * @param HttpResponse $response Response + * @param Head $head Head to fill + * @param View $pageView View + * + * @return void + * + * @since 1.0.0 + */ + private function createLoggedOutResponse(HttpResponse $response, Head $head, View $pageView) : void + { + $response->getHeader()->setStatusCode(RequestStatusCode::R_403); + $pageView->setTemplate('/Web/Timerecording/login'); + $head->addAsset(AssetType::JS, 'Web/Timerecording/js/login.js', ['type' => 'module']); + } + + /** + * Create default page view + * + * @param HttpRequest $request Request + * @param HttpResponse $response Response + * @param TimerecordingView $pageView View + * + * @return void + * + * @since 1.0.0 + */ + private function createDefaultPageView(HttpRequest $request, HttpResponse $response, TimerecordingView $pageView) : void + { + $pageView->setOrganizations(UnitMapper::getAll()); + $pageView->setProfile(ProfileMapper::getFor($request->getHeader()->getAccount(), 'account')); + $pageView->setData('nav', $this->getNavigation($request, $response)); + + $pageView->setTemplate('/Web/Timerecording/index'); + } + + /** + * Create navigation + * + * @param HttpRequest $request Request + * @param HttpResponse $response Response + * + * @return View + * + * @since 1.0.0 + */ + private function getNavigation(HttpRequest $request, HttpResponse $response) : View + { + /** @var \Modules\Navigation\Controller\TimerecordingController $navController */ + $navController = $this->app->moduleManager->get('Navigation'); + $navController->loadLanguage($request, $response); + + return $navController->getView($request, $response); + } +} diff --git a/Application/Timerecording/Error/403_inline.tpl.php b/Application/Timerecording/Error/403_inline.tpl.php new file mode 100644 index 0000000..e76b530 --- /dev/null +++ b/Application/Timerecording/Error/403_inline.tpl.php @@ -0,0 +1 @@ +Inline \ No newline at end of file diff --git a/Application/Timerecording/Routes.php b/Application/Timerecording/Routes.php new file mode 100644 index 0000000..b9ed83e --- /dev/null +++ b/Application/Timerecording/Routes.php @@ -0,0 +1,24 @@ + [ + 0 => [ + 'dest' => '\Modules\HumanResourceTimeRecording\Controller\TimerecordingController:viewDashboard', + 'verb' => 1, + 'permission' => [ + 'module' => 'HumanResourceTimeRecording', + 'type' => 2, + 'state' => 1, + ], + ], + ], + '^.*/timerecording/dashboard.*$' => [ + 0 => [ + 'dest' => '\Modules\HumanResourceTimeRecording\Controller\TimerecordingController:viewDashboard', + 'verb' => 1, + 'permission' => [ + 'module' => 'HumanResourceTimeRecording', + 'type' => 2, + 'state' => 1, + ], + ], + ], +]; \ No newline at end of file diff --git a/Application/Timerecording/Themes/Akebi/timerecording-small.css b/Application/Timerecording/Themes/Akebi/timerecording-small.css new file mode 100644 index 0000000..7562f51 --- /dev/null +++ b/Application/Timerecording/Themes/Akebi/timerecording-small.css @@ -0,0 +1,190 @@ +:root { + --main-background: rgb(46, 26, 90); + --main-background-highlight: rgb(158, 81, 197); + --input-border: rgba(166, 135, 232, .4); + --input-border-active: rgba(166, 135, 232, .7); + --input-color: rgb(116, 67, 161); + --input-color-active: rgb(94, 52, 133); + --input-background: rgba(255, 255, 255); + --input-background-active: rgba(255, 255, 255); + --input-icon-color: rgba(166, 135, 232, .6); + --input-icon-color-active: rgba(166, 135, 232, 1); + --button-main-background: rgba(166, 135, 232, .6); + --button-main-background-active: rgba(166, 135, 232, .8); + --button-main-color: rgba(255, 255, 255, .9); + --text-on-background-color: rgba(255, 255, 255, 0.7); + --text-on-background-color-2: rgba(255, 255, 255, 0.85); + --nav-category-background: rgb(43, 58, 101); + --nav-category-background-highlight: rgb(113, 43, 145); + --nav-category-background-hover: rgb(120, 50, 153); + --nav-sub-background: rgb(72, 39, 102); + --nav-sub-background-highlight: rgb(94, 52, 133); + --nav-sub-background-hover: rgb(116, 67, 161); + --nav-header-background: rgb(72, 39, 102); + --nav-header-background-highlight: rgb(94, 52, 133); + --nav-header-background-hover: rgb(116, 67, 161); + --nav-content-hover: rgb(177, 97, 218); + --font-family: 'Roboto', sans-serif; + --button-colored-background: rgb(158, 81, 197); + --button-colored-background-hover: rgb(177, 97, 218); + --table-caption-background: rgb(255, 255, 255); + --table-head-background: rgb(236, 232, 255); + --table-row-background: rgb(255, 255, 255); + --table-row-background-alt: rgb(255, 255, 255); + --table-row-background-hover: rgb(220, 211, 255); + --link-color: rgb(72, 39, 102); + --link-hover: rgb(158, 81, 197); + --badge-size: .55rem; + --badge-color: rgb(255, 255, 255); + --badge-background: rgb(158, 81, 197); + --box-border: rgb(218, 218, 218); } + + html, body { + width: 100%; + height: 100%; + min-width: 100%; + max-width: 100%; + overflow: hidden; + font-family: var(--font-family); + color: #000; } + + body { + display: flex; flex-direction: column;} + +header { + background: #f8f8f8; + border-bottom: 1px solid var(--box-border); + padding: 1rem; + box-sizing: border-box; + display: flex; + align-items: center; + flex-flow: row; + flex: 0; } + header > form { + display: flex; + flex: 1; + padding: 0 5px 0 5px; + max-width: 800px; } + header .inputWrapper { + flex: 1; } + header input[type=text] { + width: 100%; + background: white; + border: 1px solid var(--input-border); + text-shadow: none; + box-shadow: none; + transition: border 500ms ease-out; + outline: none; + box-sizing: border-box; + padding-left: 2rem; } + + #logo { + flex: 1; + text-align: right; } + #logo select { + background: none; + color: rgba(255, 255, 255, 0.8); + font-size: .8rem; } + + .ham-trigger { + display: flex; + color: #000; + align-items: center; + flex: 0; + margin-right: 5px; } + .ham-trigger i { + font-size: 1.5rem; } + + nav .ham-trigger { + color: var(--text-on-background-color-2); + margin: 0 0 0 5px; + display: none; } + + #t-nav-container { + margin-left: auto; } + + #content { + flex: 1; + padding-left: 1rem; + overflow-y: auto; } + + #t-nav { + font-size: .8rem; + color: #000; + font-weight: bold; } + #t-nav a { + padding: 0 5px 0 5px; + line-height: 25px; } + #t-nav i { + margin-right: 5px; } + #t-nav li { + display: inline; } + #t-nav li:hover { + color: var(--link-hover); } + + main { + display: flex; + flex-direction: column; + height: 100%; + background: #f1f1f1; + flex: 1; + box-sizing: border-box; } + + #dim { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #000; + opacity: 0.5; + z-index: 5; } + + #u-box { + display: flex; + align-items: center; + padding: 0 1rem 0 1rem; + height: 60px; + border-bottom: 1px solid var(--input-border); + box-sizing: border-box; } + #u-box img { + width: 40px; + height: 40px; + border-radius: 50%; } + #u-box a { + display: inline-block; } + + #app-message-container { + position: absolute; + margin: 0 auto; + right: 0; + top: 0; + padding: 85px 10px 0 0; + } + + .log-msg { + z-index: 11; + position: relative; + margin: 0 auto; + right: 0; + top: 0; + margin-bottom: 10px; + } + + @media only screen and (max-width: 500px) { + nav .ham-trigger { + display: flex; } } + @media only screen and (max-width: 600px) { +header { + flex-flow: column; + height: auto; + padding: 1rem; } + header form { + width: 100%; } + + #t-nav-container { + order: -1; + margin-bottom: .5rem; } } + @media only screen and (max-width: 1000px) { + #t-nav .link { + display: none; } } diff --git a/Application/Timerecording/Themes/Akebi/timerecording-small.scss b/Application/Timerecording/Themes/Akebi/timerecording-small.scss new file mode 100644 index 0000000..7252f9d --- /dev/null +++ b/Application/Timerecording/Themes/Akebi/timerecording-small.scss @@ -0,0 +1,153 @@ +@import "backend_vars"; + +html, body { + width: 100%; + height: 100%; + min-width: 100%; + max-width: 100%; + overflow: hidden; + font-family: var(--font-family); + color: #000; +} + +body { + display: flex; + flex-direction: column; +} + +header { + background: rgb(248, 248, 248); + border-bottom: 1px solid var(--box-border); + padding: 1rem; + box-sizing: border-box; + display: flex; + align-items: center; + flex-flow: row; + flex: 0; + + > form { + display: flex; + flex: 1; + padding: 0 5px 0 5px; + max-width: 800px; + } + + .inputWrapper { + flex: 1; + } + + input[type=text] { + width: 100%; + background: rgba(255, 255, 255, 1); + border: 1px solid var(--input-border); + text-shadow: none; + box-shadow: none; + transition : border 500ms ease-out; + outline: none; + box-sizing: border-box; + padding-left: 2rem; + } +} + +#logo { + flex: 1; + text-align: right; + + select { + background: none; + color: rgba(255, 255, 255, 0.8); + font-size: .8rem; + } +} + +#t-nav-container { + margin-left: auto; +} + +#content { + flex: 1; + padding-left: 1rem; + overflow-y: auto; +} + +#t-nav { + font-size: .8rem; + color: #000; + font-weight: bold; + + a { + padding: 0 5px 0 5px; + line-height: 25px; + } + + i { + margin-right: 5px; + } + + li { + display: inline; + + &:hover { + color: var(--link-hover); + } + } +} + +main { + display: flex; + flex-direction: column; + height: 100%; + background: #f1f1f1; + + flex: 1; + box-sizing: border-box; +} + +#dim { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #000; + opacity: 0.5; + z-index: 5; +} + +#u-box { + display: flex; + align-items: center; + padding: 0 1rem 0 1rem; + height: 60px; + border-bottom: 1px solid var(--input-border); + box-sizing: border-box; + + img { + width: 40px; + height: 40px; + border-radius: 50%; + } + + a { + display: inline-block; + } +} + +#app-message-container { + position: absolute; + margin: 0 auto; + right: 0; + top: 0; + padding: 85px 10px 0 0; + + .log-msg { + z-index: 11; + position: relative; + margin: 0 auto; + right: 0; + top: 0; + margin-bottom: 10px; + } +} + +@import "timerecording_media"; \ No newline at end of file diff --git a/Application/Timerecording/Themes/Akebi/timerecording_media.css b/Application/Timerecording/Themes/Akebi/timerecording_media.css new file mode 100644 index 0000000..cc491bf --- /dev/null +++ b/Application/Timerecording/Themes/Akebi/timerecording_media.css @@ -0,0 +1,39 @@ +@media only screen and (min-width: 1101px) { + .b-1 { + width: 31.5%; + width: calc(33.3% - 10px); } + + .b-2 { + width: 48.5%; + width: calc(50% - 10px); } + + .b-3 { + width: 63%; + width: calc(66.6% - 10px); } } +@media only screen and (min-width: 801px) and (max-width: 1100px) { + .b-1 { + width: 48.5%; + width: calc(50% - 10px); } } +@media only screen and (max-width: 1100px) { + .b-2, .b-3 { + width: 100%; + width: calc(100% - 10px); } } +@media only screen and (min-width: 801px) { + .b-4 { + width: 48.5%; + width: calc(50% - 10px); } } +@media only screen and (max-width: 800px) { + .b-1, .b-3 { + width: 100%; + width: calc(100% - 10px); } + + #logo { + display: none; } + + #s-nav { + width: 100%; } + + #s-bar input[type=text] { + margin-left: 35px; + width: 80%; + width: calc(100% - 90px); } } diff --git a/Application/Timerecording/Themes/Akebi/timerecording_media.scss b/Application/Timerecording/Themes/Akebi/timerecording_media.scss new file mode 100644 index 0000000..089e346 --- /dev/null +++ b/Application/Timerecording/Themes/Akebi/timerecording_media.scss @@ -0,0 +1,22 @@ +@media only screen and (max-width: 600px) { + header { + flex-flow: column; + height: auto; + padding: 1rem; + + form { + width: 100%; + } + } + + nav { + order: -1; + margin-bottom: .5rem; + } +} + +@media only screen and (max-width: 1000px) { + nav .link { + display: none; + } +} \ No newline at end of file diff --git a/Application/Timerecording/Themes/Akebi/timerecording_vars.css b/Application/Timerecording/Themes/Akebi/timerecording_vars.css new file mode 100644 index 0000000..1f5ee22 --- /dev/null +++ b/Application/Timerecording/Themes/Akebi/timerecording_vars.css @@ -0,0 +1,24 @@ +:root { + --main-background: rgb(46, 26, 90); + --main-background-highlight: rgb(158, 81, 197); + --input-border: rgba(166, 135, 232, .4); + --input-border-active: rgba(166, 135, 232, .7); + --input-color: rgba(166, 135, 232, .6); + --input-color-active: rgba(166, 135, 232, .8); + --input-icon-color: rgba(166, 135, 232, .6); + --input-icon-color-active: rgba(166, 135, 232, 1); + --button-main-background: rgba(166, 135, 232, .6); + --button-main-background-active: rgba(166, 135, 232, .8); + --button-main-color: rgba(255, 255, 255, .9); + --text-on-background-color: rgba(255, 255, 255, 0.7); + --nav-category-background: rgb(43, 58, 101); + --nav-category-background-highlight: rgb(113, 43, 145); + --nav-category-background-hover: rgb(120, 50, 153); + --nav-sub-background: rgb(72, 39, 102); + --nav-sub-background-highlight: rgb(94, 52, 133); + --nav-sub-background-hover: rgb(116, 67, 161); + --nav-header-background: rgb(72, 39, 102); + --nav-header-background-highlight: rgb(94, 52, 133); + --nav-header-background-hover: rgb(116, 67, 161); + --font-family: 'Roboto', sans-serif; + --box-border-color: rgb(202, 202, 202); } diff --git a/Application/Timerecording/Themes/Akebi/timerecording_vars.scss b/Application/Timerecording/Themes/Akebi/timerecording_vars.scss new file mode 100644 index 0000000..d13715e --- /dev/null +++ b/Application/Timerecording/Themes/Akebi/timerecording_vars.scss @@ -0,0 +1,55 @@ +:root { + --main-background: rgb(46, 26, 90); + --main-background-highlight: rgb(158, 81, 197); + + --input-border: rgba(166, 135, 232, .4); + --input-border-active: rgba(166, 135, 232, .7); + --input-color: rgb(116, 67, 161); + --input-color-active: rgb(94, 52, 133); + --input-background: rgba(255, 255, 255); + --input-background-active: rgba(255, 255, 255); + + --input-icon-color: rgba(166, 135, 232, .6); + --input-icon-color-active: rgba(166, 135, 232, 1); + + --button-main-background: rgba(166, 135, 232, .6); + --button-main-background-active: rgba(166, 135, 232, .8); + --button-main-color: rgba(255, 255, 255, .9); + + --text-on-background-color: rgba(255, 255, 255, 0.7); + --text-on-background-color-2: rgba(255, 255, 255, 0.85); + + --nav-category-background: rgb(43, 58, 101); + --nav-category-background-highlight: rgb(113, 43, 145); + --nav-category-background-hover: rgb(120, 50, 153); + + --nav-sub-background: rgb(72, 39, 102); + --nav-sub-background-highlight: rgb(94, 52, 133); + --nav-sub-background-hover: rgb(116, 67, 161); + + --nav-header-background: rgb(72, 39, 102); + --nav-header-background-highlight: rgb(94, 52, 133); + --nav-header-background-hover: rgb(116, 67, 161); + + --nav-content-hover: rgb(177, 97, 218); + + --font-family: 'Roboto', sans-serif; + + --button-colored-background: rgb(158, 81, 197); + --button-colored-background-hover: rgb(177, 97, 218); + + --table-caption-background: rgb(255, 255, 255); + --table-head-background: rgb(236, 232, 255); + --table-row-background: rgb(255, 255, 255); + --table-row-background-alt: rgb(255, 255, 255); + --table-row-background-hover: rgb(220, 211, 255); + + --link-color: rgb(72, 39, 102); + --link-hover: rgb(158, 81, 197); + + --badge-size: .55rem; + --badge-color: rgb(255, 255, 255); + --badge-background: rgb(158, 81, 197); + + --box-border: rgb(218, 218, 218); +} diff --git a/Application/Timerecording/TimerecordingView.php b/Application/Timerecording/TimerecordingView.php new file mode 100644 index 0000000..25e9fb4 --- /dev/null +++ b/Application/Timerecording/TimerecordingView.php @@ -0,0 +1,116 @@ +nav = $nav; + } + + /** + * Set user profile. + * + * @param Profile $profile user account + * + * @return void + * + * @since 1.0.0 + * @codeCoverageIgnore + */ + public function setProfile(Profile $profile) : void + { + $this->profile = $profile; + } + + /** + * Get profile image + * + * @return string Profile image link + * + * @since 1.0.0 + */ + public function getProfileImage() : string + { + if ($this->profile === null || $this->profile->getImage()->getPath() === '') { + return UriFactory::build('Web/Timerecording/img/user_default_' . \mt_rand(1, 6) . '.png'); + } + + return UriFactory::build($this->profile->getImage()->getPath()); + } + + /** + * Set organizations + * + * @param Unit[] $organizations Organizations + * + * @return void + * + * @since 1.0.0 + * @codeCoverageIgnore + */ + public function setOrganizations(array $organizations) : void + { + $this->organizations = $organizations; + } +} diff --git a/Application/Timerecording/css/timerecording-small.css b/Application/Timerecording/css/timerecording-small.css new file mode 100644 index 0000000..7562f51 --- /dev/null +++ b/Application/Timerecording/css/timerecording-small.css @@ -0,0 +1,190 @@ +:root { + --main-background: rgb(46, 26, 90); + --main-background-highlight: rgb(158, 81, 197); + --input-border: rgba(166, 135, 232, .4); + --input-border-active: rgba(166, 135, 232, .7); + --input-color: rgb(116, 67, 161); + --input-color-active: rgb(94, 52, 133); + --input-background: rgba(255, 255, 255); + --input-background-active: rgba(255, 255, 255); + --input-icon-color: rgba(166, 135, 232, .6); + --input-icon-color-active: rgba(166, 135, 232, 1); + --button-main-background: rgba(166, 135, 232, .6); + --button-main-background-active: rgba(166, 135, 232, .8); + --button-main-color: rgba(255, 255, 255, .9); + --text-on-background-color: rgba(255, 255, 255, 0.7); + --text-on-background-color-2: rgba(255, 255, 255, 0.85); + --nav-category-background: rgb(43, 58, 101); + --nav-category-background-highlight: rgb(113, 43, 145); + --nav-category-background-hover: rgb(120, 50, 153); + --nav-sub-background: rgb(72, 39, 102); + --nav-sub-background-highlight: rgb(94, 52, 133); + --nav-sub-background-hover: rgb(116, 67, 161); + --nav-header-background: rgb(72, 39, 102); + --nav-header-background-highlight: rgb(94, 52, 133); + --nav-header-background-hover: rgb(116, 67, 161); + --nav-content-hover: rgb(177, 97, 218); + --font-family: 'Roboto', sans-serif; + --button-colored-background: rgb(158, 81, 197); + --button-colored-background-hover: rgb(177, 97, 218); + --table-caption-background: rgb(255, 255, 255); + --table-head-background: rgb(236, 232, 255); + --table-row-background: rgb(255, 255, 255); + --table-row-background-alt: rgb(255, 255, 255); + --table-row-background-hover: rgb(220, 211, 255); + --link-color: rgb(72, 39, 102); + --link-hover: rgb(158, 81, 197); + --badge-size: .55rem; + --badge-color: rgb(255, 255, 255); + --badge-background: rgb(158, 81, 197); + --box-border: rgb(218, 218, 218); } + + html, body { + width: 100%; + height: 100%; + min-width: 100%; + max-width: 100%; + overflow: hidden; + font-family: var(--font-family); + color: #000; } + + body { + display: flex; flex-direction: column;} + +header { + background: #f8f8f8; + border-bottom: 1px solid var(--box-border); + padding: 1rem; + box-sizing: border-box; + display: flex; + align-items: center; + flex-flow: row; + flex: 0; } + header > form { + display: flex; + flex: 1; + padding: 0 5px 0 5px; + max-width: 800px; } + header .inputWrapper { + flex: 1; } + header input[type=text] { + width: 100%; + background: white; + border: 1px solid var(--input-border); + text-shadow: none; + box-shadow: none; + transition: border 500ms ease-out; + outline: none; + box-sizing: border-box; + padding-left: 2rem; } + + #logo { + flex: 1; + text-align: right; } + #logo select { + background: none; + color: rgba(255, 255, 255, 0.8); + font-size: .8rem; } + + .ham-trigger { + display: flex; + color: #000; + align-items: center; + flex: 0; + margin-right: 5px; } + .ham-trigger i { + font-size: 1.5rem; } + + nav .ham-trigger { + color: var(--text-on-background-color-2); + margin: 0 0 0 5px; + display: none; } + + #t-nav-container { + margin-left: auto; } + + #content { + flex: 1; + padding-left: 1rem; + overflow-y: auto; } + + #t-nav { + font-size: .8rem; + color: #000; + font-weight: bold; } + #t-nav a { + padding: 0 5px 0 5px; + line-height: 25px; } + #t-nav i { + margin-right: 5px; } + #t-nav li { + display: inline; } + #t-nav li:hover { + color: var(--link-hover); } + + main { + display: flex; + flex-direction: column; + height: 100%; + background: #f1f1f1; + flex: 1; + box-sizing: border-box; } + + #dim { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #000; + opacity: 0.5; + z-index: 5; } + + #u-box { + display: flex; + align-items: center; + padding: 0 1rem 0 1rem; + height: 60px; + border-bottom: 1px solid var(--input-border); + box-sizing: border-box; } + #u-box img { + width: 40px; + height: 40px; + border-radius: 50%; } + #u-box a { + display: inline-block; } + + #app-message-container { + position: absolute; + margin: 0 auto; + right: 0; + top: 0; + padding: 85px 10px 0 0; + } + + .log-msg { + z-index: 11; + position: relative; + margin: 0 auto; + right: 0; + top: 0; + margin-bottom: 10px; + } + + @media only screen and (max-width: 500px) { + nav .ham-trigger { + display: flex; } } + @media only screen and (max-width: 600px) { +header { + flex-flow: column; + height: auto; + padding: 1rem; } + header form { + width: 100%; } + + #t-nav-container { + order: -1; + margin-bottom: .5rem; } } + @media only screen and (max-width: 1000px) { + #t-nav .link { + display: none; } } diff --git a/Application/Timerecording/css/timerecording-small.scss b/Application/Timerecording/css/timerecording-small.scss new file mode 100644 index 0000000..7252f9d --- /dev/null +++ b/Application/Timerecording/css/timerecording-small.scss @@ -0,0 +1,153 @@ +@import "backend_vars"; + +html, body { + width: 100%; + height: 100%; + min-width: 100%; + max-width: 100%; + overflow: hidden; + font-family: var(--font-family); + color: #000; +} + +body { + display: flex; + flex-direction: column; +} + +header { + background: rgb(248, 248, 248); + border-bottom: 1px solid var(--box-border); + padding: 1rem; + box-sizing: border-box; + display: flex; + align-items: center; + flex-flow: row; + flex: 0; + + > form { + display: flex; + flex: 1; + padding: 0 5px 0 5px; + max-width: 800px; + } + + .inputWrapper { + flex: 1; + } + + input[type=text] { + width: 100%; + background: rgba(255, 255, 255, 1); + border: 1px solid var(--input-border); + text-shadow: none; + box-shadow: none; + transition : border 500ms ease-out; + outline: none; + box-sizing: border-box; + padding-left: 2rem; + } +} + +#logo { + flex: 1; + text-align: right; + + select { + background: none; + color: rgba(255, 255, 255, 0.8); + font-size: .8rem; + } +} + +#t-nav-container { + margin-left: auto; +} + +#content { + flex: 1; + padding-left: 1rem; + overflow-y: auto; +} + +#t-nav { + font-size: .8rem; + color: #000; + font-weight: bold; + + a { + padding: 0 5px 0 5px; + line-height: 25px; + } + + i { + margin-right: 5px; + } + + li { + display: inline; + + &:hover { + color: var(--link-hover); + } + } +} + +main { + display: flex; + flex-direction: column; + height: 100%; + background: #f1f1f1; + + flex: 1; + box-sizing: border-box; +} + +#dim { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #000; + opacity: 0.5; + z-index: 5; +} + +#u-box { + display: flex; + align-items: center; + padding: 0 1rem 0 1rem; + height: 60px; + border-bottom: 1px solid var(--input-border); + box-sizing: border-box; + + img { + width: 40px; + height: 40px; + border-radius: 50%; + } + + a { + display: inline-block; + } +} + +#app-message-container { + position: absolute; + margin: 0 auto; + right: 0; + top: 0; + padding: 85px 10px 0 0; + + .log-msg { + z-index: 11; + position: relative; + margin: 0 auto; + right: 0; + top: 0; + margin-bottom: 10px; + } +} + +@import "timerecording_media"; \ No newline at end of file diff --git a/Application/Timerecording/css/timerecording_media.css b/Application/Timerecording/css/timerecording_media.css new file mode 100644 index 0000000..cc491bf --- /dev/null +++ b/Application/Timerecording/css/timerecording_media.css @@ -0,0 +1,39 @@ +@media only screen and (min-width: 1101px) { + .b-1 { + width: 31.5%; + width: calc(33.3% - 10px); } + + .b-2 { + width: 48.5%; + width: calc(50% - 10px); } + + .b-3 { + width: 63%; + width: calc(66.6% - 10px); } } +@media only screen and (min-width: 801px) and (max-width: 1100px) { + .b-1 { + width: 48.5%; + width: calc(50% - 10px); } } +@media only screen and (max-width: 1100px) { + .b-2, .b-3 { + width: 100%; + width: calc(100% - 10px); } } +@media only screen and (min-width: 801px) { + .b-4 { + width: 48.5%; + width: calc(50% - 10px); } } +@media only screen and (max-width: 800px) { + .b-1, .b-3 { + width: 100%; + width: calc(100% - 10px); } + + #logo { + display: none; } + + #s-nav { + width: 100%; } + + #s-bar input[type=text] { + margin-left: 35px; + width: 80%; + width: calc(100% - 90px); } } diff --git a/Application/Timerecording/css/timerecording_media.scss b/Application/Timerecording/css/timerecording_media.scss new file mode 100644 index 0000000..089e346 --- /dev/null +++ b/Application/Timerecording/css/timerecording_media.scss @@ -0,0 +1,22 @@ +@media only screen and (max-width: 600px) { + header { + flex-flow: column; + height: auto; + padding: 1rem; + + form { + width: 100%; + } + } + + nav { + order: -1; + margin-bottom: .5rem; + } +} + +@media only screen and (max-width: 1000px) { + nav .link { + display: none; + } +} \ No newline at end of file diff --git a/Application/Timerecording/css/timerecording_vars.css b/Application/Timerecording/css/timerecording_vars.css new file mode 100644 index 0000000..1f5ee22 --- /dev/null +++ b/Application/Timerecording/css/timerecording_vars.css @@ -0,0 +1,24 @@ +:root { + --main-background: rgb(46, 26, 90); + --main-background-highlight: rgb(158, 81, 197); + --input-border: rgba(166, 135, 232, .4); + --input-border-active: rgba(166, 135, 232, .7); + --input-color: rgba(166, 135, 232, .6); + --input-color-active: rgba(166, 135, 232, .8); + --input-icon-color: rgba(166, 135, 232, .6); + --input-icon-color-active: rgba(166, 135, 232, 1); + --button-main-background: rgba(166, 135, 232, .6); + --button-main-background-active: rgba(166, 135, 232, .8); + --button-main-color: rgba(255, 255, 255, .9); + --text-on-background-color: rgba(255, 255, 255, 0.7); + --nav-category-background: rgb(43, 58, 101); + --nav-category-background-highlight: rgb(113, 43, 145); + --nav-category-background-hover: rgb(120, 50, 153); + --nav-sub-background: rgb(72, 39, 102); + --nav-sub-background-highlight: rgb(94, 52, 133); + --nav-sub-background-hover: rgb(116, 67, 161); + --nav-header-background: rgb(72, 39, 102); + --nav-header-background-highlight: rgb(94, 52, 133); + --nav-header-background-hover: rgb(116, 67, 161); + --font-family: 'Roboto', sans-serif; + --box-border-color: rgb(202, 202, 202); } diff --git a/Application/Timerecording/css/timerecording_vars.scss b/Application/Timerecording/css/timerecording_vars.scss new file mode 100644 index 0000000..d13715e --- /dev/null +++ b/Application/Timerecording/css/timerecording_vars.scss @@ -0,0 +1,55 @@ +:root { + --main-background: rgb(46, 26, 90); + --main-background-highlight: rgb(158, 81, 197); + + --input-border: rgba(166, 135, 232, .4); + --input-border-active: rgba(166, 135, 232, .7); + --input-color: rgb(116, 67, 161); + --input-color-active: rgb(94, 52, 133); + --input-background: rgba(255, 255, 255); + --input-background-active: rgba(255, 255, 255); + + --input-icon-color: rgba(166, 135, 232, .6); + --input-icon-color-active: rgba(166, 135, 232, 1); + + --button-main-background: rgba(166, 135, 232, .6); + --button-main-background-active: rgba(166, 135, 232, .8); + --button-main-color: rgba(255, 255, 255, .9); + + --text-on-background-color: rgba(255, 255, 255, 0.7); + --text-on-background-color-2: rgba(255, 255, 255, 0.85); + + --nav-category-background: rgb(43, 58, 101); + --nav-category-background-highlight: rgb(113, 43, 145); + --nav-category-background-hover: rgb(120, 50, 153); + + --nav-sub-background: rgb(72, 39, 102); + --nav-sub-background-highlight: rgb(94, 52, 133); + --nav-sub-background-hover: rgb(116, 67, 161); + + --nav-header-background: rgb(72, 39, 102); + --nav-header-background-highlight: rgb(94, 52, 133); + --nav-header-background-hover: rgb(116, 67, 161); + + --nav-content-hover: rgb(177, 97, 218); + + --font-family: 'Roboto', sans-serif; + + --button-colored-background: rgb(158, 81, 197); + --button-colored-background-hover: rgb(177, 97, 218); + + --table-caption-background: rgb(255, 255, 255); + --table-head-background: rgb(236, 232, 255); + --table-row-background: rgb(255, 255, 255); + --table-row-background-alt: rgb(255, 255, 255); + --table-row-background-hover: rgb(220, 211, 255); + + --link-color: rgb(72, 39, 102); + --link-hover: rgb(158, 81, 197); + + --badge-size: .55rem; + --badge-color: rgb(255, 255, 255); + --badge-background: rgb(158, 81, 197); + + --box-border: rgb(218, 218, 218); +} diff --git a/Application/Timerecording/img/favicon.ico b/Application/Timerecording/img/favicon.ico new file mode 100644 index 0000000..ec514e2 Binary files /dev/null and b/Application/Timerecording/img/favicon.ico differ diff --git a/Application/Timerecording/img/logo.png b/Application/Timerecording/img/logo.png new file mode 100644 index 0000000..27d7f3c Binary files /dev/null and b/Application/Timerecording/img/logo.png differ diff --git a/Application/Timerecording/index.tpl.php b/Application/Timerecording/index.tpl.php new file mode 100644 index 0000000..fe06b1d --- /dev/null +++ b/Application/Timerecording/index.tpl.php @@ -0,0 +1,69 @@ +getData('nav'); + +$nav->setTemplate('/Modules/Navigation/Theme/Backend/top'); +$top = $nav->render(); + +/** @var phpOMS\Model\Html\Head $head */ +$head = $this->getData('head'); + +/** @var array $dispatch */ +$dispatch = $this->getData('dispatch') ?? []; +?> + + +
+ + + + + + + = $head->getMeta()->render(); ?> + +
+