diff --git a/web/holidaySummary.php b/web/holidaySummary.php index 517c5bf83..752db8d7a 100644 --- a/web/holidaySummary.php +++ b/web/holidaySummary.php @@ -66,7 +66,7 @@ Scheduled (hours) Pending (hours) % planned - {{week}} + {{ weeksStartDays.find(x => x.week == week)?.weekStartDate }}
{{week}} @@ -94,3 +94,4 @@ + diff --git a/web/js/holidaySummary.js b/web/js/holidaySummary.js index 0727a9bc4..3b63303bf 100644 --- a/web/js/holidaySummary.js +++ b/web/js/holidaySummary.js @@ -18,153 +18,160 @@ */ const xmlHeaders = { - method: 'GET', - mode: 'same-origin', - cache: 'no-cache', - credentials: 'same-origin', - headers: { - 'Content-Type': 'text/xml' - }, - referrerPolicy: 'no-referrer', -} + method: 'GET', + mode: 'same-origin', + cache: 'no-cache', + credentials: 'same-origin', + headers: { + 'Content-Type': 'text/xml' + }, + referrerPolicy: 'no-referrer' +}; var app = new Vue({ - el: '#holidaySummaryReport', - data() { - return { - weeks: {}, - displayData: {}, - isLoading: true, - isLoadingProjects: true, - projectUsers: {}, - projectsList: [], - allProjects: [], - autocompleteIsActive: false, - searchProject: "", - activeProject: 0, - year: new Date().getFullYear() - }; - }, - created() { - this.fetchSummary(); + el: '#holidaySummaryReport', + data() { + return { + weeks: {}, + weeksStartDays: {}, + displayData: {}, + isLoading: true, + isLoadingProjects: true, + projectUsers: {}, + projectsList: [], + allProjects: [], + autocompleteIsActive: false, + searchProject: '', + activeProject: 0, + year: new Date().getFullYear() + }; + }, + created() { + this.fetchSummary(); + }, + computed: { + downloadUrl() { + let url = `services/getHolidaySummary.php?format=csv&users=${Object.keys(this.displayData).join(',')}`; + return this.year ? `${url}&year=${this.year}` : url; + } + }, + methods: { + async fetchProjects() { + let url = 'services/getProjectsService.php?active=true&users=true'; + const res = await fetch(url, xmlHeaders); + const body = await res.text(); + parser = new DOMParser(); + xmlDoc = parser.parseFromString(body, 'text/xml'); + const projects = xmlDoc.getElementsByTagName('project'); + let parsedProjects = []; + for (var i = 0; i < projects.length; i++) { + parsedProjects.push({ + id: projects[i].getElementsByTagName('id')[0].innerHTML, + name: projects[i].getElementsByTagName('fullDescription')[0].innerHTML + }); + const users = projects[i].getElementsByTagName('user'); + let projectUsers = []; + for (var j = 0; j < users.length; j++) { + projectUsers.push(users[j].getElementsByTagName('login')[0].innerHTML); + } + this.projectUsers[parsedProjects[i].id] = projectUsers; + projectUsers.sort(); + } + this.isLoadingProjects = false; + this.projectsList = parsedProjects; + this.allProjects = parsedProjects; }, - computed: { - downloadUrl() { - let url = `services/getHolidaySummary.php?format=csv&users=${Object.keys(this.displayData).join(",")}`; - return this.year ? `${url}&year=${this.year}` : url; + async fetchSummary() { + let params = new URLSearchParams(window.location.search); + let year = params.get('year'); + let url = 'services/getHolidaySummary.php'; + if (year) { + url += `?year=${year}`; + this.year = year; + } + const res = await fetch(url, { + method: 'GET', + mode: 'same-origin', + cache: 'no-cache', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json' }, + referrerPolicy: 'no-referrer' + }); + const body = await res.json(); + this.isLoading = false; + this.weeks = Object.keys(body.weeks); + this.weeksStartDays = body.weeksStartDays; + this.displayData = body.holidays; + this.rawData = body.holidays; + await this.fetchProjects(); }, - methods: { - async fetchProjects() { - let url = 'services/getProjectsService.php?active=true&users=true'; - const res = await fetch(url, xmlHeaders); - const body = await res.text(); - parser = new DOMParser(); - xmlDoc = parser.parseFromString(body, "text/xml"); - const projects = xmlDoc.getElementsByTagName("project"); - let parsedProjects = []; - for (var i = 0; i < projects.length; i++) { - parsedProjects.push({ - id: projects[i].getElementsByTagName("id")[0].innerHTML, - name: projects[i].getElementsByTagName("fullDescription")[0].innerHTML, - }); - const users = projects[i].getElementsByTagName("user"); - let projectUsers = []; - for (var j = 0; j < users.length; j++) { - projectUsers.push(users[j].getElementsByTagName("login")[0].innerHTML); - } - this.projectUsers[parsedProjects[i].id] = projectUsers; - projectUsers.sort() - } - this.isLoadingProjects = false; - this.projectsList = parsedProjects; - this.allProjects = parsedProjects; - }, - async fetchSummary() { - let params = new URLSearchParams(window.location.search); - let year = params.get('year'); - let url = 'services/getHolidaySummary.php'; - if (year) { - url += `?year=${year}`; - this.year = year; - } - const res = await fetch(url, { - method: 'GET', - mode: 'same-origin', - cache: 'no-cache', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json' - }, - referrerPolicy: 'no-referrer', - }); - const body = await res.json(); - this.isLoading = false; - this.weeks = Object.keys(body.weeks); - this.displayData = body.holidays; - this.rawData = body.holidays; - await this.fetchProjects(); - }, - // TODO: we should implement the autocomplete as a - // reusable Single File Component, but before that we need to improve our - // static files bundling. - onSelectProject(projectIndex) { - if (!this.projectsList[projectIndex]) return; - const project = this.projectsList[projectIndex]; - this.searchProject = project.name; - this.autocompleteIsActive = false; - this.projectsList = this.allProjects; - this.displayData = {}; - // Diplays only users from the project - let users = this.projectUsers[project.id]; - users.sort() - for (let i = 0; i < users.length; i++) { - if (this.rawData[users[i]]) { - this.displayData[users[i]] = this.rawData[users[i]]; - } - } - }, - showOptions() { - this.autocompleteIsActive = true; - }, - hideOptions(event) { - if (!event.relatedTarget?.classList.contains('autocompleteItemBtn')) { - this.autocompleteIsActive = false; - } - this.activeProject = 0; - }, - prevProject() { - if (this.activeProject > 0) { - this.activeProject--; - } else { - this.activeProject = this.projectsList.length - 1; - } - this.scrollAutocomplete(); - }, - nextProject() { - if (this.activeProject < this.projectsList.length - 1) { - this.activeProject++; - } else { - this.activeProject = 0; - } - this.scrollAutocomplete(); - }, - scrollAutocomplete() { - if (!document.getElementsByClassName('autocompleteItemBtn')[this.activeProject]) return; - const elementHeight = document.getElementsByClassName('autocompleteItemBtn')[this.activeProject].offsetHeight; - const offSet = document.getElementsByClassName('autocompleteItemBtn')[this.activeProject].offsetTop + elementHeight; - const clientHeight = document.getElementById('projectsDropdown').clientHeight; - document.getElementById('projectsDropdown').scrollTop = offSet - clientHeight; - }, - filterProject(event) { - this.autocompleteIsActive = true; - if (!event.target.value) { - // reset list of users when no project is selected - this.displayData = this.rawData; - this.projectsList = this.allProjects; - } else { - this.projectsList = this.allProjects.filter(project => project.name.toLowerCase().includes(event.target.value.toLowerCase())); - } - }, + // TODO: we should implement the autocomplete as a + // reusable Single File Component, but before that we need to improve our + // static files bundling. + onSelectProject(projectIndex) { + if (!this.projectsList[projectIndex]) return; + const project = this.projectsList[projectIndex]; + this.searchProject = project.name; + this.autocompleteIsActive = false; + this.projectsList = this.allProjects; + this.displayData = {}; + // Diplays only users from the project + let users = this.projectUsers[project.id]; + users.sort(); + for (let i = 0; i < users.length; i++) { + if (this.rawData[users[i]]) { + this.displayData[users[i]] = this.rawData[users[i]]; + } + } + }, + showOptions() { + this.autocompleteIsActive = true; + }, + hideOptions(event) { + if (!event.relatedTarget?.classList.contains('autocompleteItemBtn')) { + this.autocompleteIsActive = false; + } + this.activeProject = 0; + }, + prevProject() { + if (this.activeProject > 0) { + this.activeProject--; + } else { + this.activeProject = this.projectsList.length - 1; + } + this.scrollAutocomplete(); + }, + nextProject() { + if (this.activeProject < this.projectsList.length - 1) { + this.activeProject++; + } else { + this.activeProject = 0; + } + this.scrollAutocomplete(); + }, + scrollAutocomplete() { + if (!document.getElementsByClassName('autocompleteItemBtn')[this.activeProject]) return; + const elementHeight = + document.getElementsByClassName('autocompleteItemBtn')[this.activeProject].offsetHeight; + const offSet = + document.getElementsByClassName('autocompleteItemBtn')[this.activeProject].offsetTop + + elementHeight; + const clientHeight = document.getElementById('projectsDropdown').clientHeight; + document.getElementById('projectsDropdown').scrollTop = offSet - clientHeight; + }, + filterProject(event) { + this.autocompleteIsActive = true; + if (!event.target.value) { + // reset list of users when no project is selected + this.displayData = this.rawData; + this.projectsList = this.allProjects; + } else { + this.projectsList = this.allProjects.filter((project) => + project.name.toLowerCase().includes(event.target.value.toLowerCase()) + ); + } } -}) + } +}); diff --git a/web/services/HolidayService.php b/web/services/HolidayService.php index 6f3dcff23..aab4baedd 100644 --- a/web/services/HolidayService.php +++ b/web/services/HolidayService.php @@ -22,7 +22,8 @@ use Phpreport\Model\facade; -if (!defined('PHPREPORT_ROOT')) define('PHPREPORT_ROOT', __DIR__ . '/../../'); +if (!defined('PHPREPORT_ROOT')) + define('PHPREPORT_ROOT', __DIR__ . '/../../'); include_once(PHPREPORT_ROOT . '/web/services/WebServicesFunctions.php'); include_once(PHPREPORT_ROOT . '/model/facade/ProjectsFacade.php'); @@ -31,8 +32,7 @@ include_once(PHPREPORT_ROOT . '/model/vo/UserVO.php'); require_once(PHPREPORT_ROOT . '/util/LoginManager.php'); -class HolidayService -{ +class HolidayService { private \LoginManager $loginManager; private string $sid; @@ -44,9 +44,9 @@ public function __construct( $this->sid = $sid; } - function isWeekend(string $date): bool + function isWeekend(string $date) : bool { - return (date('N', strtotime($date)) >= 6); + return(date('N', strtotime($date)) >= 6); } /** Group dates into date ranges @@ -78,7 +78,7 @@ function isWeekend(string $date): bool * ] * ] */ - public function datesToRanges(array $vacations): array + public function datesToRanges(array $vacations) : array { if (count($vacations) == 0) { return []; @@ -113,7 +113,7 @@ public function datesToRanges(array $vacations): array /** * Function used to pretty print time. From hours to Days d hours:minutes */ - static function formatHours(float $time, float $journey, int $limit): string + static function formatHours(float $time, float $journey, int $limit) : string { $negative = ($time < 0); $work_days = false; @@ -153,11 +153,11 @@ static function formatHours(float $time, float $journey, int $limit): string return $formatedHours; } - static function getWeeksFromYear($year = NULL): array + static function getWeeksFromYear($year = NULL) : array { $weeks = []; $year = $year ?? date('Y'); - $first_week = (int)date('W', mktime(0, 0, 0, 1, 1, $year)); + $first_week = (int) date('W', mktime(0, 0, 0, 1, 1, $year)); $last_week = (int) date('W', mktime(0, 0, 0, 12, 31, $year)); if ($first_week == 52 || $first_week == 53) { $weeks[($year - 1) . "W" . $first_week] = 0; @@ -177,9 +177,10 @@ static function getWeeksFromYear($year = NULL): array return $weeks; } - static function groupByWeeks(array $leavesDetails, $weeks = []): array + static function groupByWeeks(array $leavesDetails, $weeks = []) : array { - if (count($leavesDetails) == 0) return []; + if (count($leavesDetails) == 0) + return []; $dates = array_keys($leavesDetails); $previous_week = date("o\WW", strtotime($dates[0])); $weeks[$previous_week] = $leavesDetails[$dates[0]]['amount'] ?? 1; @@ -196,13 +197,17 @@ static function groupByWeeks(array $leavesDetails, $weeks = []): array return $weeks; } - static function mapHalfLeaves($dates, array $journeyHistories): array + static function mapHalfLeaves($dates, array $journeyHistories) : array { foreach ($dates as $day => $duration) { - $validJourney = array_filter($journeyHistories, fn ($history) => $history->dateBelongsToHistory( - date_create($day) - )); - if (count($validJourney) == 0) continue; + $validJourney = array_filter( + $journeyHistories, + fn($history) => $history->dateBelongsToHistory( + date_create($day) + ) + ); + if (count($validJourney) == 0) + continue; $validJourney = array_pop($validJourney); if (($validJourney->getJourney() * 60) > ($duration['end'] - $duration['init'])) { $dates[$day]['isPartialLeave'] = True; @@ -212,7 +217,7 @@ static function mapHalfLeaves($dates, array $journeyHistories): array return $dates; } - static function getDaysBetweenDates(string $init, string $end): array + static function getDaysBetweenDates(string $init, string $end) : array { $begin = new \DateTime($init); $end = new \DateTime($end); @@ -229,7 +234,7 @@ static function getDaysBetweenDates(string $init, string $end): array return $dates; } - public function getUserVacationsRanges(string $init = NULL, string $end = NULL, $sid = NULL, $userLogin = NULL): array + public function getUserVacationsRanges(string $init = NULL, string $end = NULL, $sid = NULL, $userLogin = NULL) : array { if (!$this->loginManager::isLogged($sid)) { return ['error' => 'User not logged in']; @@ -257,12 +262,12 @@ public function getUserVacationsRanges(string $init = NULL, string $end = NULL, ]; } - public function deleteVacations(array $daysToDelete, \UserVO $userVO, int $holidayProjectId): array + public function deleteVacations(array $daysToDelete, \UserVO $userVO, int $holidayProjectId) : array { $failed = []; foreach ($daysToDelete as &$day) { $tasks = \TasksFacade::GetUserTasksByLoginDate($userVO, new \DateTime($day)); - $holidayTasks = array_filter($tasks, fn ($task) => $task->getProjectId() == $holidayProjectId); + $holidayTasks = array_filter($tasks, fn($task) => $task->getProjectId() == $holidayProjectId); if (\TasksFacade::DeleteReports($holidayTasks) == -1) $failed[] = $day; } @@ -273,17 +278,21 @@ public function deleteVacations(array $daysToDelete, \UserVO $userVO, int $holid ]; } - public function createVacations(array $daysToCreate, \UserVO $userVO, int $holidayProjectId, string $description = ''): array + public function createVacations(array $daysToCreate, \UserVO $userVO, int $holidayProjectId, string $description = '') : array { $journeyHistories = \UsersFacade::GetUserJourneyHistories($userVO->getLogin()); $failed = []; foreach ($daysToCreate as &$day) { - if ($this->isWeekend($day)) continue; + if ($this->isWeekend($day)) + continue; $currentDay = date_create($day); - $validJourney = array_filter($journeyHistories, fn ($history) => $history->dateBelongsToHistory( - $currentDay - )); + $validJourney = array_filter( + $journeyHistories, + fn($history) => $history->dateBelongsToHistory( + $currentDay + ) + ); if (count($validJourney) != 1) { $failed[] = $day; continue; @@ -309,7 +318,7 @@ public function createVacations(array $daysToCreate, \UserVO $userVO, int $holid ]; } - public function updateUserVacations(array $vacations, string $init, string $end): array + public function updateUserVacations(array $vacations, string $init, string $end) : array { if (!$this->loginManager::isLogged()) { return ['error' => 'User not logged in']; @@ -327,11 +336,13 @@ public function updateUserVacations(array $vacations, string $init, string $end) $userVO->setLogin($_SESSION['user']->getLogin()); $userVO->setId($_SESSION['user']->getId()); - $existingVacations = array_keys(\UsersFacade::GetScheduledHolidays( - date_create($init), - date_create($end), - $userVO - )); + $existingVacations = array_keys( + \UsersFacade::GetScheduledHolidays( + date_create($init), + date_create($end), + $userVO + ) + ); $holidayProjectId = \TasksFacade::GetVacationsProjectId(); $daysToDelete = array_diff($existingVacations, $vacations); $resultDeleted = $this->deleteVacations($daysToDelete, $userVO, $holidayProjectId); @@ -346,7 +357,7 @@ public function updateUserVacations(array $vacations, string $init, string $end) ]; } - public function updateLongLeaves(string $init, string $end, string $user, string $projectId, string $description = ''): array + public function updateLongLeaves(string $init, string $end, string $user, string $projectId, string $description = '') : array { if (!$this->loginManager::isLogged($this->sid)) { return ['error' => 'User not logged in']; @@ -390,7 +401,7 @@ public function syncCalDAVCalendar(array $datesRanges = []) return ['message' => 'Calendar synced']; } - public function retrieveHolidaysSummary(string $year = NULL): array + public function retrieveHolidaysSummary(string $year = NULL) : array { if (!$this->loginManager::isLogged()) { return ['error' => 'User not logged in']; @@ -406,6 +417,13 @@ public function retrieveHolidaysSummary(string $year = NULL): array $users = \UsersFacade::GetAllActiveUsers($filterEmployees = true); $weeks = $this::getWeeksFromYear($year); + $weeksStartDays = []; + foreach (array_keys($weeks) as $week) { + $yearOfWeek = explode('W', $week)[0]; + $weekNumber = explode('W', $week)[1]; + $weekStartDate = date('M d', strtotime($yearOfWeek . '-W' . $weekNumber . '-1')); + $weeksStartDays[] = array("week" => $yearOfWeek . "W" . $weekNumber, "weekStartDate" => $weekStartDate); + } $holidays = []; foreach ($users as &$user) { $holidays[$user->getLogin()] = \UsersFacade::GetHolidaySummaryReport( @@ -420,7 +438,8 @@ public function retrieveHolidaysSummary(string $year = NULL): array asort($holidays); return [ "holidays" => $holidays, - "weeks" => $weeks + "weeks" => $weeks, + "weeksStartDays" => $weeksStartDays ]; } }