Browse Source

Better teams overview.

pull/925/head
Sebastian 3 years ago
parent
commit
44e9c14961
  1. 1
      backend/i18n/frontend_en.json
  2. 1
      backend/i18n/frontend_it.json
  3. 1
      backend/i18n/frontend_nl.json
  4. 1
      backend/i18n/frontend_zh.json
  5. 1
      backend/i18n/source/frontend_en.json
  6. 28
      frontend/src/app/features/apps/pages/apps-page.component.html
  7. 38
      frontend/src/app/features/apps/pages/apps-page.component.scss
  8. 38
      frontend/src/app/features/apps/pages/apps-page.component.ts
  9. 50
      frontend/src/app/features/apps/pages/team.component.html
  10. 26
      frontend/src/app/features/apps/pages/team.component.scss
  11. 20
      frontend/src/app/features/teams/pages/dashboard/cards/apps-card.component.html
  12. 6
      frontend/src/app/features/teams/pages/dashboard/cards/apps-card.component.ts
  13. 2
      frontend/src/app/theme/_vars.scss

1
backend/i18n/frontend_en.json

@ -992,6 +992,7 @@
"start.madeByCopyright": "Sebastian Stehle and Contributors, 2016-2021",
"teams.create": "Create",
"teams.createFailed": "Failed to create team. Please reload.",
"teams.empty": "This team has no apps yet.",
"teams.leave": "Leave team",
"teams.leaveConfirmText": "Do you really want to leave this team?",
"teams.leaveConfirmTitle": "Leave team.",

1
backend/i18n/frontend_it.json

@ -992,6 +992,7 @@
"start.madeByCopyright": "Sebastian Stehle e Collaboratori, 2016-2020",
"teams.create": "Create",
"teams.createFailed": "Failed to create team. Please reload.",
"teams.empty": "This team has no apps yet.",
"teams.leave": "Leave team",
"teams.leaveConfirmText": "Do you really want to leave this team?",
"teams.leaveConfirmTitle": "Leave team.",

1
backend/i18n/frontend_nl.json

@ -992,6 +992,7 @@
"start.madeByCopyright": "Sebastian Stehle en medewerkers, 2016-2020",
"teams.create": "Create",
"teams.createFailed": "Failed to create team. Please reload.",
"teams.empty": "This team has no apps yet.",
"teams.leave": "Leave team",
"teams.leaveConfirmText": "Do you really want to leave this team?",
"teams.leaveConfirmTitle": "Leave team.",

1
backend/i18n/frontend_zh.json

@ -992,6 +992,7 @@
"start.madeByCopyright": "Sebastian Stehle 和贡献者,2016-2021",
"teams.create": "Create",
"teams.createFailed": "Failed to create team. Please reload.",
"teams.empty": "This team has no apps yet.",
"teams.leave": "Leave team",
"teams.leaveConfirmText": "Do you really want to leave this team?",
"teams.leaveConfirmTitle": "Leave team.",

1
backend/i18n/source/frontend_en.json

@ -992,6 +992,7 @@
"start.madeByCopyright": "Sebastian Stehle and Contributors, 2016-2021",
"teams.create": "Create",
"teams.createFailed": "Failed to create team. Please reload.",
"teams.empty": "This team has no apps yet.",
"teams.leave": "Leave team",
"teams.leaveConfirmText": "Do you really want to leave this team?",
"teams.leaveConfirmTitle": "Leave team.",

28
frontend/src/app/features/apps/pages/apps-page.component.html

@ -9,27 +9,25 @@
</div>
</div>
<ng-container *ngIf="appsState.apps | async; let apps">
<ng-container *ngIf="groupedApps | async; let groups">
<div class="apps-section">
<div class="empty" *ngIf="apps.length === 0">
<div class="empty" *ngIf="groups.length === 0">
<h3 class="empty-headline">{{ 'apps.empty' | sqxTranslate }}</h3>
</div>
<h3 *ngIf="apps.length > 0">{{ 'common.apps' | sqxTranslate }}</h3>
<sqx-app *ngFor="let app of apps; trackBy: trackByApp"
[app]="app" (leave)="leaveApp($event)">
</sqx-app>
</div>
</ng-container>
<div class="team" *ngFor="let group of groups; trackByGroup">
<div class="team-header" *ngIf="group.team">
<sqx-team [team]="group.team" (leave)="leaveTeam($event)"></sqx-team>
</div>
<ng-container *ngIf="teamsState.teams | async; let teams">
<div class="teams-section">
<h3 *ngIf="teams.length > 0">{{ 'common.teams' | sqxTranslate }}</h3>
<div class="team-body" [class.padded]="group.team">
<sqx-app *ngFor="let app of group.apps; trackBy: trackByApp" [app]="app" (leave)="leaveApp($event)"></sqx-app>
<sqx-team *ngFor="let team of teams; trackBy: trackByTeam"
[team]="team" (leave)="leaveTeam($event)">
</sqx-team>
<small class="team-empty" *ngIf="group.apps.length === 0">
{{ 'teams.empty' | sqxTranslate }}
</small>
</div>
</div>
</div>
</ng-container>

38
frontend/src/app/features/apps/pages/apps-page.component.scss

@ -2,12 +2,6 @@
@import 'vars';
.apps-section {
@include clearfix;
padding: 2rem 1.25rem 0 $size-sidebar-width + .25rem;
}
.teams-section {
@include clearfix;
padding: 2rem 1.25rem 0 $size-sidebar-width + .25rem;
}
@ -17,10 +11,6 @@
overflow-y: auto;
}
%no-decoration {
text-decoration: none;
}
:host ::ng-deep {
.card {
@include hover-visible('.deeplinks', inline);
@ -83,23 +73,39 @@
&-href {
cursor: pointer;
&:hover {
@extend %no-decoration;
@include box-shadow-outer(0, 3px, 16px, .2);
&:active,
&:focus {
text-decoration: none;
}
&:focus {
@extend %no-decoration;
outline: none;
}
&:active {
@extend %no-decoration;
&:hover {
@include box-shadow-outer(0, 3px, 16px, .2);
}
}
}
}
.team {
margin-top: 1rem;
&:first-child {
margin: 0;
}
&-body {
@include clearfix;
&.padded {
padding-bottom: 1rem;
padding-top: 1rem;
}
}
}
.info {
color: $color-border-dark;
}

38
frontend/src/app/features/apps/pages/apps-page.component.ts

@ -6,11 +6,13 @@
*/
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { combineLatest } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { AppDto, AppsState, AuthService, DialogModel, FeatureDto, LocalStoreService, NewsService, OnboardingService, TeamDto, TeamsState, TemplateDto, TemplatesState, UIOptions, UIState } from '@app/shared';
import { Settings } from '@app/shared/state/settings';
type GroupedApps = { team?: TeamDto; apps: AppDto[] };
@Component({
selector: 'sqx-apps-page',
styleUrls: ['./apps-page.component.scss'],
@ -27,18 +29,42 @@ export class AppsPageComponent implements OnInit {
public info = '';
public templates: Observable<TemplateDto[]> =
public templates =
this.templatesState.templates.pipe(
map(x => x.filter(t => t.isStarter)));
public groupedApps =
combineLatest([
this.appsState.apps,
this.teamsState.teams,
]).pipe(map(([apps, teams]) => {
const grouped: GroupedApps[] = [{ apps: [] }];
for (const team of teams) {
grouped.push({ team, apps: [] });
}
for (const app of apps) {
const group = grouped.find(x => x.team?.id === app.teamId) || grouped[0];
group.apps.push(app);
}
if (grouped[0].apps.length === 0) {
grouped.shift();
}
return grouped;
}));
constructor(
public readonly appsState: AppsState,
public readonly authState: AuthService,
public readonly uiState: UIState,
public readonly teamsState: TeamsState,
private readonly appsState: AppsState,
private readonly localStore: LocalStoreService,
private readonly newsService: NewsService,
private readonly onboardingService: OnboardingService,
private readonly teamsState: TeamsState,
private readonly templatesState: TemplatesState,
private readonly uiOptions: UIOptions,
) {
@ -92,7 +118,7 @@ export class AppsPageComponent implements OnInit {
return app.id;
}
public trackByTeam(_index: number, team: TeamDto) {
return team.id;
public trackByGroup(_index: number, group: GroupedApps) {
return group.team?.id || '0';
}
}

50
frontend/src/app/features/apps/pages/team.component.html

@ -1,29 +1,27 @@
<div class="card card-href card-team" [routerLink]="['/app/teams', team.id]">
<div class="card-body">
<div class="row g-0">
<div class="col col-12">
<h3 class="card-title">{{team.name}}</h3>
<div class="card-text card-links truncate">
<a [routerLink]="['/app/teams', team.id]" sqxStopClick>{{ 'common.edit' | sqxTranslate }}</a>
</div>
</div>
<div class="team-header">
<div class="row align-items-center">
<div class="col">
<h3>{{team.name}}</h3>
</div>
<div class="col-auto">
<a class="link" [routerLink]="['/app/teams', team.id]" sqxStopClick>{{ 'common.edit' | sqxTranslate }}</a>
</div>
<div class="col-auto">
<button type="button" class="btn btn-sm btn-text-secondary" (click)="dropdown.toggle()" sqxStopClick #buttonOptions>
<i class="icon-dots"></i>
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<sqx-dropdown-menu [sqxAnchoredTo]="buttonOptions" [scrollY]="true">
<a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="leave.emit(team)"
confirmTitle="i18n:teams.leaveConfirmTitle"
confirmText="i18n:teams.leaveConfirmText"
confirmRememberKey="leaveApp">
{{ 'teams.leave' | sqxTranslate }}
</a>
</sqx-dropdown-menu>
</ng-container>
</div>
<button type="button" class="btn btn-sm btn-text-secondary" (click)="dropdown.toggle()" sqxStopClick #buttonOptions>
<i class="icon-dots"></i>
</button>
<ng-container *sqxModal="dropdown;closeAlways:true">
<sqx-dropdown-menu [sqxAnchoredTo]="buttonOptions" [scrollY]="true">
<a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="leave.emit(team)"
confirmTitle="i18n:teams.leaveConfirmTitle"
confirmText="i18n:teams.leaveConfirmText"
confirmRememberKey="leaveApp">
{{ 'teams.leave' | sqxTranslate }}
</a>
</sqx-dropdown-menu>
</ng-container>
</div>
</div>

26
frontend/src/app/features/apps/pages/team.component.scss

@ -1,14 +1,28 @@
@import 'mixins';
@import 'vars';
.btn {
@include absolute(1rem, 1rem);
h3 {
@include truncate;
}
.card-body {
position: relative;
.row {
flex-wrap: nowrap;
}
.card-title {
padding-right: 2rem;
.col {
overflow: hidden;
}
.team-header {
border-bottom: 1px solid $color-border;
}
.link {
font-size: $font-small;
font-weight: normal;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}

20
frontend/src/app/features/teams/pages/dashboard/cards/apps-card.component.html

@ -1,13 +1,19 @@
<div class="card card">
<div class="card-header">{{ 'common.apps' | sqxTranslate }}</div>
<div class="card-body">
<div class="row" *ngFor="let app of snapshot.apps">
<div class="col">
{{app.displayName}}
<ng-container *ngIf="snapshot.apps; let apps">
<small class="team-empty" *ngIf="apps.length === 0">
{{ 'teams.empty' | sqxTranslate }}
</small>
<div class="row" *ngFor="let app of apps">
<div class="col">
{{app.displayName}}
</div>
<div class="col-auto">
<a [routerLink]="['/app', app.name]" sqxStopClick>{{ 'common.edit' | sqxTranslate }}</a>
</div>
</div>
<div class="col-auto">
<a [routerLink]="['/app', app.name]" sqxStopClick>{{ 'common.edit' | sqxTranslate }}</a>
</div>
</div>
</ng-container>
</div>
</div>

6
frontend/src/app/features/teams/pages/dashboard/cards/apps-card.component.ts

@ -10,7 +10,7 @@ import { AppDto, AppsService, StatefulComponent, TeamDto } from '@app/shared';
interface State {
// The apps for this team.
apps: ReadonlyArray<AppDto>;
apps?: ReadonlyArray<AppDto>;
}
@Component({
@ -26,9 +26,7 @@ export class AppsCardComponent extends StatefulComponent<State> implements OnIni
constructor(changeDetector: ChangeDetectorRef,
private readonly appsService: AppsService,
) {
super(changeDetector, {
apps: [],
});
super(changeDetector, {});
}
public ngOnInit() {

2
frontend/src/app/theme/_vars.scss

@ -10,7 +10,7 @@ $color-border-darker: darken($color-border, 15%);
$color-title: #000;
$color-text: #373a3c;
$color-text-decent: #6c707f;
$color-text-decent: #545863;
$color-tooltip: #1a2129;
$color-code-background: #f5f7f9;

Loading…
Cancel
Save