Skip to content

Commit

Permalink
add numbers/graph-bar to month report
Browse files Browse the repository at this point in the history
  • Loading branch information
bseber committed Oct 11, 2024
1 parent 4658798 commit b759802
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

import java.util.List;

record GraphMonthDto(String yearMonth, List<GraphWeekDto> weekReports, Double maxHoursWorked) {
record GraphMonthDto(
String yearMonth,
List<GraphWeekDto> weekReports,
Double maxHoursWorked,
String workedWorkingHours,
String shouldWorkingHours,
String hoursDelta,
boolean hoursDeltaNegative,
double hoursWorkedRatio
) {

public Double graphLegendMaxHour() {

Expand Down
41 changes: 37 additions & 4 deletions src/main/java/de/focusshift/zeiterfassung/report/ReportMonth.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
package de.focusshift.zeiterfassung.report;

import de.focusshift.zeiterfassung.timeentry.ShouldWorkingHours;
import de.focusshift.zeiterfassung.timeentry.WorkDuration;
import de.focusshift.zeiterfassung.workingtime.PlannedWorkingHours;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.YearMonth;
import java.util.Collection;
import java.util.List;

import static java.math.RoundingMode.CEILING;
import static java.util.function.Predicate.not;

record ReportMonth(YearMonth yearMonth, List<ReportWeek> weeks) {

public PlannedWorkingHours plannedWorkingHours() {
return weeks.stream()
.map(ReportWeek::plannedWorkingHours)
.reduce(PlannedWorkingHours.ZERO, PlannedWorkingHours::plus);
}

public ShouldWorkingHours shouldWorkingHours() {
return weeks.stream()
.map(ReportWeek::shouldWorkingHours)
.reduce(ShouldWorkingHours.ZERO, ShouldWorkingHours::plus);
}

public WorkDuration averageDayWorkDuration() {

final double averageMinutes = weeks.stream()
Expand All @@ -29,9 +44,27 @@ public WorkDuration averageDayWorkDuration() {
return new WorkDuration(duration);
}

public PlannedWorkingHours plannedWorkingHours() {
return weeks.stream()
.map(ReportWeek::plannedWorkingHours)
.reduce(PlannedWorkingHours.ZERO, PlannedWorkingHours::plus);
public WorkDuration workDuration() {
return weeks
.stream()
.map(ReportWeek::workDuration)
.reduce(WorkDuration.ZERO, WorkDuration::plus);
}

public BigDecimal workedHoursRatio() {

final double planned = shouldWorkingHours().durationInMinutes().toMinutes();
final double worked = workDuration().durationInMinutes().toMinutes();

if (worked == 0) {
return BigDecimal.ZERO;
}

if (planned == 0) {
return BigDecimal.ONE;
}

final BigDecimal ratio = BigDecimal.valueOf(worked).divide(BigDecimal.valueOf(planned), 2, CEILING);
return ratio.compareTo(BigDecimal.ONE) > 0 ? BigDecimal.ONE : ratio;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import de.focus_shift.launchpad.api.HasLaunchpad;
import de.focusshift.zeiterfassung.timeclock.HasTimeClock;
import de.focusshift.zeiterfassung.timeentry.ShouldWorkingHours;
import de.focusshift.zeiterfassung.timeentry.WorkDuration;
import de.focusshift.zeiterfassung.user.DateFormatter;
import de.focusshift.zeiterfassung.usermanagement.UserLocalId;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -18,8 +20,11 @@
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.math.BigDecimal;
import java.math.MathContext;
import java.time.Clock;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.YearMonth;
import java.util.List;
import java.util.Locale;
Expand Down Expand Up @@ -142,7 +147,17 @@ private GraphMonthDto toGraphMonthDto(ReportMonth reportMonth) {
.mapToDouble(value -> value)
.max().orElse(0.0);

return new GraphMonthDto(yearMonth, graphWeekDtos, maxHoursWorked);
final WorkDuration workDuration = reportMonth.workDuration();
final ShouldWorkingHours shouldWorkingHours = reportMonth.shouldWorkingHours();
final String shouldWorkingHoursString = durationToTimeString(shouldWorkingHours.duration());
final String workedWorkingHoursString = durationToTimeString(workDuration.duration());

final Duration deltaDuration = workDuration.duration().minus(shouldWorkingHours.duration());
final String deltaHours = durationToTimeString(deltaDuration);

final double weekRatio = reportMonth.workedHoursRatio().multiply(BigDecimal.valueOf(100), new MathContext(2)).doubleValue();

return new GraphMonthDto(yearMonth, graphWeekDtos, maxHoursWorked, workedWorkingHoursString, shouldWorkingHoursString, deltaHours, deltaDuration.isNegative(), weekRatio);
}

private DetailMonthDto toDetailMonthDto(ReportMonth reportMonth, Locale locale) {
Expand All @@ -165,4 +180,10 @@ private static Optional<YearMonth> yearMonth(int year, int month) {
return Optional.empty();
}
}

private static String durationToTimeString(Duration duration) {
// use positive values to format duration string
// negative value is handled in template
return String.format("%02d:%02d", Math.abs(duration.toHours()), Math.abs(duration.toMinutesPart()));
}
}
65 changes: 62 additions & 3 deletions src/main/resources/templates/reports/user-report-month.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,68 @@
>Dieser Monat</th:block
>
</span>
<span class="font-bold" th:text="${monthReport.yearMonth}">
Dezember 2021
</span>
<div th:id="${'month-banner-'}" class="flex items-center">
<div class="flex-1 w-full">
<div
class="relative flex justify-between gap-x-2 flex-wrap text-sm"
>
<span class="font-bold" th:text="${monthReport.yearMonth}">
Oktober 2024
</span>
<p
class="tabular-nums text-sm text-gray-800 font-medium sm:flex-1 sm:absolute sm:left-0 sm:right-0 sm:text-center"
>
<span class="inline-flex items-center">
<th:block th:if="${monthReport.hoursDeltaNegative}"
>-</th:block
>
<th:block th:if="${not monthReport.hoursDeltaNegative}"
>+</th:block
>
<span
class="font-bold tabular-nums"
th:text="${monthReport.hoursDelta}"
>
01:30
</span>
<th:block
th:if="${not monthReport.hoursDeltaNegative and monthReport.hoursDelta != '00:00'}"
>
<svg
th:replace="~{icons/trending-up::svg(className='w-4 h-4 ml-1')}"
></svg>
</th:block>
</span>
</p>
<p class="sr-only sm:not-sr-only">
Geleistet:
<span
th:text="${monthReport.workedWorkingHours}"
class="font-bold tabular-nums"
>
41:58
</span>
(<th:block
th:text="#{time-entry.overview.week.group.hours-bar.should}"
>Soll: </th:block
><span
th:text="${monthReport.shouldWorkingHours}"
class="font-bold tabular-nums"
>
40:00 </span
>)
</p>
</div>
<div class="translate-y-1 rounded border border-gray-300">
<div
class="h-2 rounded-full bg-gradient-to-r from-gray-200 to-gray-600"
th:styleappend="${'width:' + monthReport.hoursWorkedRatio + '%'}"
>
<div></div>
</div>
</div>
</div>
</div>
</div>
<div is="z-report-graph">
<svg
Expand Down
2 changes: 0 additions & 2 deletions src/main/resources/templates/reports/user-report-week.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@
<p
class="tabular-nums text-sm text-gray-800 font-medium sm:flex-1 sm:absolute sm:left-0 sm:right-0 sm:text-center"
>
<!-- <span class="inline-flex items-center">+01:58</span>-->
<span class="inline-flex items-center">
<th:block th:if="${weekReport.hoursDeltaNegative}"
>-</th:block
Expand Down Expand Up @@ -137,7 +136,6 @@
</div>
</div>
</div>
<div class="time-entry-box__controls"></div>
</div>
</div>
<div is="z-report-graph">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,33 @@ void ensureReportMonth() throws Exception {
final ReportMonth reportMonth = new ReportMonth(
YearMonth.of(2023, 2),
List.of(
fourtyHourWeek(user, LocalDate.of(2023, 1, 30))
new ReportWeek(
LocalDate.of(2023, 1, 30),
List.of(
zeroHoursDay(LocalDate.of(2023, 1, 30), user),
zeroHoursDay(LocalDate.of(2023, 1, 31), user),
eightHoursDay(LocalDate.of(2023, 2, 1), user),
eightHoursDay(LocalDate.of(2023, 2, 2), user),
eightHoursDay(LocalDate.of(2023, 2, 3), user),
zeroHoursDay(LocalDate.of(2023, 2, 4), user),
zeroHoursDay(LocalDate.of(2023, 2, 5), user)
)
),
fourtyHourWeek(user, LocalDate.of(2023, 2, 6)),
fourtyHourWeek(user, LocalDate.of(2023, 2, 13)),
fourtyHourWeek(user, LocalDate.of(2023, 2, 20)),
new ReportWeek(
LocalDate.of(2023, 2, 27),
List.of(
eightHoursDay(LocalDate.of(2023, 2, 27), user),
eightHoursDay(LocalDate.of(2023, 2, 28), user),
zeroHoursDay(LocalDate.of(2023, 3, 1), user),
zeroHoursDay(LocalDate.of(2023, 3, 2), user),
zeroHoursDay(LocalDate.of(2023, 3, 3), user),
zeroHoursDay(LocalDate.of(2023, 3, 4), user),
zeroHoursDay(LocalDate.of(2023, 3, 5), user)
)
)
)
);

Expand All @@ -101,23 +127,104 @@ void ensureReportMonth() throws Exception {
5,
"date-range",
List.of(
new GraphDayDto(true, "M", "Montag", "30.01.2023", 8d, 8d),
new GraphDayDto(true, "D", "Dienstag", "31.01.2023", 8d, 8d),
new GraphDayDto(true, "M", "Montag", "30.01.2023", 0d, 0d),
new GraphDayDto(true, "D", "Dienstag", "31.01.2023", 0d, 0d),
new GraphDayDto(false, "M", "Mittwoch", "01.02.2023", 8d, 8d),
new GraphDayDto(false, "D", "Donnerstag", "02.02.2023", 8d, 8d),
new GraphDayDto(false, "F", "Freitag", "03.02.2023", 8d, 8d),
new GraphDayDto(false, "S", "Samstag", "04.02.2023", 0d, 0d),
new GraphDayDto(false, "S", "Sonntag", "05.02.2023", 0d, 0d)
),
8d,
"24:00",
"24:00",
"00:00",
false,
100d
),
new GraphWeekDto(
6,
"date-range",
List.of(
new GraphDayDto(false, "M", "Montag", "06.02.2023", 8d, 8d),
new GraphDayDto(false, "D", "Dienstag", "07.02.2023", 8d, 8d),
new GraphDayDto(false, "M", "Mittwoch", "08.02.2023", 8d, 8d),
new GraphDayDto(false, "D", "Donnerstag", "09.02.2023", 8d, 8d),
new GraphDayDto(false, "F", "Freitag", "10.02.2023", 8d, 8d),
new GraphDayDto(false, "S", "Samstag", "11.02.2023", 0d, 0d),
new GraphDayDto(false, "S", "Sonntag", "12.02.2023", 0d, 0d)
),
8d,
"40:00",
"40:00",
"00:00",
false,
100d
),
new GraphWeekDto(
7,
"date-range",
List.of(
new GraphDayDto(false, "M", "Montag", "13.02.2023", 8d, 8d),
new GraphDayDto(false, "D", "Dienstag", "14.02.2023", 8d, 8d),
new GraphDayDto(false, "M", "Mittwoch", "15.02.2023", 8d, 8d),
new GraphDayDto(false, "D", "Donnerstag", "16.02.2023", 8d, 8d),
new GraphDayDto(false, "F", "Freitag", "17.02.2023", 8d, 8d),
new GraphDayDto(false, "S", "Samstag", "18.02.2023", 0d, 0d),
new GraphDayDto(false, "S", "Sonntag", "19.02.2023", 0d, 0d)
),
8d,
"40:00",
"40:00",
"00:00",
false,
100d
),
new GraphWeekDto(
8,
"date-range",
List.of(
new GraphDayDto(false, "M", "Montag", "20.02.2023", 8d, 8d),
new GraphDayDto(false, "D", "Dienstag", "21.02.2023", 8d, 8d),
new GraphDayDto(false, "M", "Mittwoch", "22.02.2023", 8d, 8d),
new GraphDayDto(false, "D", "Donnerstag", "23.02.2023", 8d, 8d),
new GraphDayDto(false, "F", "Freitag", "24.02.2023", 8d, 8d),
new GraphDayDto(false, "S", "Samstag", "25.02.2023", 0d, 0d),
new GraphDayDto(false, "S", "Sonntag", "26.02.2023", 0d, 0d)
),
8d,
"40:00",
"40:00",
"00:00",
false,
100d
),
new GraphWeekDto(
9,
"date-range",
List.of(
new GraphDayDto(false, "M", "Montag", "27.02.2023", 8d, 8d),
new GraphDayDto(false, "D", "Dienstag", "28.02.2023", 8d, 8d),
new GraphDayDto(true, "M", "Mittwoch", "01.03.2023", 0d, 0d),
new GraphDayDto(true, "D", "Donnerstag", "02.03.2023", 0d, 0d),
new GraphDayDto(true, "F", "Freitag", "03.03.2023", 0d, 0d),
new GraphDayDto(true, "S", "Samstag", "04.03.2023", 0d, 0d),
new GraphDayDto(true, "S", "Sonntag", "05.03.2023", 0d, 0d)
),
8d,
"16:00",
"16:00",
"00:00",
false,
100d
)
),
8d
8d,
"160:00",
"160:00",
"00:00",
false,
100d
);

perform(
Expand Down Expand Up @@ -380,6 +487,15 @@ private ReportDay eightHoursDay(LocalDate date, User user) {
);
}

private ReportDay zeroHoursDay(LocalDate date, User user) {
return new ReportDay(
date,
Map.of(user.userIdComposite(), PlannedWorkingHours.ZERO),
Map.of(user.userIdComposite(), List.of()),
Map.of(user.userIdComposite(), List.of())
);
}

private ReportDayEntry reportDayEntry(User user, LocalDate date) {
return new ReportDayEntry(user, "", date.atStartOfDay().plusHours(8).atZone(UTC), date.atStartOfDay().plusHours(16).atZone(UTC), false);
}
Expand Down

0 comments on commit b759802

Please sign in to comment.