Skip to content

Commit

Permalink
Merge pull request #8147 from ever-co/develop
Browse files Browse the repository at this point in the history
Stage
  • Loading branch information
evereq authored Sep 3, 2024
2 parents cab4c5b + 9fc806a commit 309ad93
Show file tree
Hide file tree
Showing 41 changed files with 670 additions and 50 deletions.
18 changes: 16 additions & 2 deletions .github/workflows/desktop-timer-app-stage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,20 @@ jobs:
echo "Environment Variable Names:"
printenv | cut -d= -f1
- name: Print environment variables that are large
shell: powershell
run: |
# List all environment variables
foreach ($envVar in [System.Environment]::GetEnvironmentVariables().Keys) {
# Get the value of the environment variable
$value = [System.Environment]::GetEnvironmentVariable($envVar)
# Check if the value length is greater than 100 bytes
if ([Text.Encoding]::UTF8.GetByteCount($value) -gt 100) {
Write-Output $envVar
}
}
- name: Build Desktop Timer App
run: 'yarn build:desktop-timer:windows:release:gh'
env:
Expand Down Expand Up @@ -282,7 +296,7 @@ jobs:
GOROOT_1_22_X64: ''
GRADLE_HOME: ''
# HOMEDRIVE: ''
# HOMEPATH: ''
HOMEPATH: ''
IEWebDriver: ''
ImageOS: ''
ImageVersion: ''
Expand All @@ -300,7 +314,7 @@ jobs:
# npm_config_prefix: ''
NUMBER_OF_PROCESSORS: ''
OS: ''
# PATHEXT: ''
PATHEXT: ''
PERFLOG_LOCATION_SETTING: ''
PGBIN: ''
PGDATA: ''
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { Query } from '@datorama/akita';
import { Observable } from 'rxjs';
import { IDateRangePicker } from '../../shared/features/date-range-picker/date-picker.interface';
import { IMonthlyRecapState, MonthlyRecapStore } from './monthly.store';

@Injectable({ providedIn: 'root' })
export class MonthlyRecapQuery extends Query<IMonthlyRecapState> {
public readonly range$: Observable<IDateRangePicker> = this.select((state) => state.range);
public readonly state$: Observable<IMonthlyRecapState> = this.select();
public readonly isLoading$: Observable<boolean> = this.selectLoading();

constructor(protected store: MonthlyRecapStore) {
super(store);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Injectable } from '@angular/core';
import { ICountsStatistics, IGetCountsStatistics, IGetTimeLogInput, ReportDayData } from '@gauzy/contracts';
import { Observable } from 'rxjs';
import { RequestQuery } from '../../+state/request/request.query';
import { Store, TimeTrackerDateManager, ToastrNotificationService } from '../../../services';
import { TimesheetService, TimesheetStatisticsService } from '../../services/timesheet';
import { IDateRangePicker } from '../../shared/features/date-range-picker/date-picker.interface';
import { MonthlyRecapQuery } from './monthly.query';
import { IMonthlyRecapState, MonthlyRecapStore } from './monthly.store';

@Injectable({
providedIn: 'root'
})
export class MonthlyRecapService {
constructor(
private readonly timesheetStatisticsService: TimesheetStatisticsService,
private readonly timesheetService: TimesheetService,
private readonly notificationService: ToastrNotificationService,
private readonly monthlyQuery: MonthlyRecapQuery,
private readonly monthlyStore: MonthlyRecapStore,
private readonly requestQuery: RequestQuery,
private readonly store: Store
) {}

public update(state: Partial<IMonthlyRecapState>) {
this.monthlyStore.update(state);
}

public get state$(): Observable<IMonthlyRecapState> {
return this.monthlyQuery.state$;
}

public get range$(): Observable<IDateRangePicker> {
return this.monthlyQuery.range$;
}

public get range(): IDateRangePicker {
return this.monthlyQuery.getValue().range;
}

public get count(): ICountsStatistics {
return this.monthlyQuery.getValue().count;
}

public get monthlyActivities(): ReportDayData[] {
return this.monthlyQuery.getValue().monthlyActivities;
}

public async getMonthActivities(): Promise<void> {
try {
this.monthlyStore.setLoading(true);
const { organizationId, tenantId, user } = this.store;
const employeeIds = [user.employee.id];
const timeZone = user.timeZone;
const timeFormat = user.timeFormat;
const request: IGetTimeLogInput = {
...this.requestQuery.request,
...this.range,
organizationId,
employeeIds,
tenantId,
timeFormat,
timeZone,
unitOfTime: 'month'
};
const monthlyActivities = await this.timesheetService.getWeeklyReportChart(request);
this.monthlyStore.update({ monthlyActivities });
} catch (error) {
this.notificationService.error(error.message || 'An error occurred while fetching tasks.');
this.monthlyStore.setError(error);
} finally {
this.monthlyStore.setLoading(false);
}
}

public async getCounts(): Promise<void> {
try {
this.monthlyStore.setLoading(true);
const { organizationId, tenantId, user } = this.store;
const employeeIds = [user.employee.id];
const request: IGetCountsStatistics = {
...this.requestQuery.request,
...this.range,
organizationId,
employeeIds,
onlyMe: true,
tenantId,
todayStart: TimeTrackerDateManager.startToday,
todayEnd: TimeTrackerDateManager.endToday
};
const count = await this.timesheetStatisticsService.getCounts(request);
this.monthlyStore.update({ count });
} catch (error) {
this.notificationService.error(error.message || 'An error occurred while fetching tasks.');
this.monthlyStore.setError(error);
} finally {
this.monthlyStore.setLoading(false);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core';
import { Store, StoreConfig } from '@datorama/akita';
import { ICountsStatistics, ReportDayData } from '@gauzy/contracts';
import { TimeTrackerDateManager } from '../../../services';
import { IDateRangePicker } from '../../shared/features/date-range-picker/date-picker.interface';

export interface IMonthlyRecapState {
count: ICountsStatistics;
range: IDateRangePicker;
monthlyActivities: ReportDayData[];
}

export function createInitialState(): IMonthlyRecapState {
return {
monthlyActivities: [],
range: {
startDate: TimeTrackerDateManager.startCurrentMonth,
endDate: TimeTrackerDateManager.endCurrentMonth
},
count: {
projectsCount: 0,
employeesCount: 0,
weekActivities: 0,
weekDuration: 0,
todayActivities: 0,
todayDuration: 0
}
};
}

@StoreConfig({ name: '_monthlyRecap' })
@Injectable({ providedIn: 'root' })
export class MonthlyRecapStore extends Store<IMonthlyRecapState> {
constructor() {
super(createInitialState());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<ngx-date-range-picker unitOfTime="month" [dates]="selectedDateRange$ | async" [isLockDatePicker]="true" [arrows]="true"
[isSingleDatePicker]="false" (rangeChanges)="onRangeChange($event)" class="medium full"></ngx-date-range-picker>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { map } from 'rxjs';
import { MonthlyRecapService } from '../../+state/monthly.service';
import { IDateRangePicker } from '../../../shared/features/date-range-picker/date-picker.interface';

@Component({
selector: 'ngx-monthly-calendar',
templateUrl: './monthly-calendar.component.html',
styleUrls: ['./monthly-calendar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MonthlyCalendarComponent {
constructor(private readonly monthlyRecapService: MonthlyRecapService) {}

public get selectedDateRange$() {
return this.monthlyRecapService.state$.pipe(map((state) => state.range));
}

public onRangeChange(range: IDateRangePicker) {
this.monthlyRecapService.update({ range });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<div class="week-wrapper">
<div>
<div class="week-range">
{{(range$ | async).startDate | dateTime : 'MMMM'}}
</div>
</div>
<ng-container *ngIf="(monthlyActivities$ | async)?.sum; else noMonthlyData">
<div class="weeks row" *ngFor="let month of monthWeekdays">
<div class="week col">{{ getWeekRange(month.week) }}</div>
<ngx-progress-status class="report-progress col-6"
[percentage]="((sumPerWeek(month, monthlyActivities$ | async) * 100)) / ((monthlyActivities$ | async)?.sum ?? 1)"></ngx-progress-status>
<div class="col hours text-end">{{ sumPerWeek(month, monthlyActivities$ | async) | durationFormat : 'h[h]
m[m]
s[s]':{trim:'both'} }}
</div>
</div>
</ng-container>
</div>

<ng-template #noMonthlyData>
<ngx-no-data-message [message]="'TIMER_TRACKER.RECAP.NO_MONTHLY_ACTIVITY' | translate"></ngx-no-data-message>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@import 'report';

.activity {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}

.weeks {
display: flex;
align-items: center;
justify-content: space-between;
border-top: 0.5px solid var(--select-filled-control-disabled-text-color);
padding-top: 1rem;
color: var(--gauzy-text-color-1);
font-weight: 500;

.week,
.hours {
text-wrap: nowrap;
}
}

.week-range {
font-size: 16px;
font-weight: 400;
line-height: 16px;
letter-spacing: -0.009em;
color: var(--gauzy-text-color-2);
}

.week-wrapper {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
border-radius: var(--border-radius);
background-color: var(--gauzy-card-3);
height: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ReportDayData } from '@gauzy/contracts';
import { map, tap } from 'rxjs';
import { MonthlyRecapService } from '../../+state/monthly.service';
import { updateMonthWeeks, weekDateRange } from '../../../shared/features/date-range-picker';

export interface IMonthWeekdays {
week: string;
days: string[];
}

@Component({
selector: 'ngx-monthly-progress',
templateUrl: './monthly-progress.component.html',
styleUrls: ['./monthly-progress.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MonthlyProgressComponent {
public monthWeekdays: IMonthWeekdays[] = [];

constructor(private readonly monthlyRecapService: MonthlyRecapService) {}

public get monthlyActivities$() {
return this.monthlyRecapService.state$.pipe(map((state) => state.monthlyActivities[0]));
}

public get range$() {
return this.monthlyRecapService.state$.pipe(
tap((state) => {
const { monthWeekdays } = updateMonthWeeks(state.range);
this.monthWeekdays = monthWeekdays;
}),
map((state) => state.range)
);
}

public sumPerWeek(month: IMonthWeekdays, data: ReportDayData): void {
return month.days.reduce((acc, curr) => {
const sum = data?.dates?.[curr]?.sum || 0;
return acc + sum;
}, 0);
}

public getWeekRange(number: number): string {
return weekDateRange(number);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<nb-card class="main-report-wrapper">
<nb-card-header class="p-3 main-report-header">
<ng-container *ngIf="isLoading$ | async">
<nb-icon class="loader" size="medium" icon="loader-outline"></nb-icon>
</ng-container>
<div class="tools ml-auto">
<ngx-monthly-calendar></ngx-monthly-calendar>
<ngx-filter></ngx-filter>
</div>
</nb-card-header>
<nb-card-body class="main-report-body recap-monthly">
<ngx-monthly-statistic></ngx-monthly-statistic>
<ngx-monthly-progress class="h-100"></ngx-monthly-progress>
</nb-card-body>
</nb-card>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@import '../../../features/recap/recap.component.scss';

.recap-monthly {
display: flex;
flex-direction: column;
gap: 1rem;
background-color: var(--gauzy-card-2);
padding: 1rem;
border-radius: var(--border-radius);
height: 100%;
}
Loading

0 comments on commit 309ad93

Please sign in to comment.