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
];
}
}