mirror of
https://github.com/mempool/mempool.git
synced 2025-09-25 18:01:56 +02:00
cubo widget tweaks
This commit is contained in:
@@ -334,6 +334,7 @@
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||
</a>
|
||||
<app-simpleproof-cubo-widget [label]="widget.props.label" [key]="widget.props.key" [widget]="true"></app-simpleproof-cubo-widget>
|
||||
<div style="margin-top: -0.6rem;"><a [routerLink]="['/sp/cubo' | relativeUrl]" i18n="dashboard.view-more">View more »</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -29,10 +29,22 @@
|
||||
i18n-placeholder="simpleproof.search_placeholder"
|
||||
>
|
||||
</div>
|
||||
<table class="table table-borderless" [class.table-fixed]="widget">
|
||||
<table class="table table-borderless table-fixed">
|
||||
<thead>
|
||||
<th class="filename text-left" [ngClass]="{'widget': widget}" i18n="simpleproof.student_name">Student Name</th>
|
||||
<th class="id text-left" [ngClass]="{'widget': widget}" i18n="simpleproof.id_code">ID</th>
|
||||
<th class="student text-left sortable" [ngClass]="{'widget': widget}" (click)="$event.stopPropagation(); sortBy('student_name')" style="cursor: pointer;">
|
||||
<span i18n="simpleproof.student_name">Student Name</span>
|
||||
<div class="sort-icons ml-1">
|
||||
<fa-icon [icon]="['fas', 'caret-up']" [fixedWidth]="true" [class.active]="sortField === 'student_name' && sortDirection === 'asc'" (click)="$event.stopPropagation(); sortBy('student_name', 'asc')"></fa-icon>
|
||||
<fa-icon [icon]="['fas', 'caret-down']" [fixedWidth]="true" [class.active]="sortField === 'student_name' && sortDirection === 'desc'" (click)="$event.stopPropagation(); sortBy('student_name', 'desc')"></fa-icon>
|
||||
</div>
|
||||
</th>
|
||||
<th class="id text-left sortable" [ngClass]="{'widget': widget}" (click)="$event.stopPropagation(); sortBy('id_code')" style="cursor: pointer;">
|
||||
<span i18n="simpleproof.id_code">ID</span>
|
||||
<div class="sort-icons ml-1">
|
||||
<fa-icon [icon]="['fas', 'caret-up']" [fixedWidth]="true" [class.active]="sortField === 'id_code' && sortDirection === 'asc'" (click)="$event.stopPropagation(); sortBy('id_code', 'asc')"></fa-icon>
|
||||
<fa-icon [icon]="['fas', 'caret-down']" [fixedWidth]="true" [class.active]="sortField === 'id_code' && sortDirection === 'desc'" (click)="$event.stopPropagation(); sortBy('id_code', 'desc')"></fa-icon>
|
||||
</div>
|
||||
</th>
|
||||
<th class="proof text-right" [ngClass]="{'widget': widget}" i18n="simpleproof.proof">Proof</th>
|
||||
</thead>
|
||||
<tbody *ngIf="verifiedPage; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||
@@ -50,19 +62,34 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngFor="let item of verifiedPage">
|
||||
<td class="filename text-left" [class]="widget ? 'widget' : ''">{{ item.student_name }}</td>
|
||||
<td class="id text-left" [class]="widget ? 'widget' : ''">
|
||||
@if (item.sanitized_download_url) {
|
||||
<a [href]="item.sanitized_download_url" target="_blank">
|
||||
<span>{{ item.id_code }}</span>
|
||||
<span class="icon ml-2">
|
||||
<fa-icon [icon]="['fas', 'file-pdf']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
</a>
|
||||
} @else {
|
||||
{{ item.id_code }}
|
||||
}
|
||||
</td>
|
||||
@if (widget) {
|
||||
<td class="student text-left" [class]="widget ? 'widget' : ''">
|
||||
@if (item.sanitized_download_url) {
|
||||
<a [href]="item.sanitized_download_url" target="_blank">
|
||||
<span>{{ item.student_name }}</span>
|
||||
<span class="icon ml-2">
|
||||
<fa-icon [icon]="['fas', 'file-pdf']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
</a>
|
||||
} @else {
|
||||
{{ item.student_name }}
|
||||
}
|
||||
</td>
|
||||
} @else {
|
||||
<td class="student text-left" [class]="widget ? 'widget' : ''">{{ item.student_name }}</td>
|
||||
<td class="id text-left" [class]="widget ? 'widget' : ''">
|
||||
@if (item.sanitized_download_url) {
|
||||
<a [href]="item.sanitized_download_url" target="_blank">
|
||||
<span>{{ item.id_code }}</span>
|
||||
<span class="icon ml-2">
|
||||
<fa-icon [icon]="['fas', 'file-pdf']" [fixedWidth]="true"></fa-icon>
|
||||
</span>
|
||||
</a>
|
||||
} @else {
|
||||
{{ item.id_code }}
|
||||
}
|
||||
</td>
|
||||
}
|
||||
<td class="proof text-right" [class]="widget ? 'widget' : ''">
|
||||
<a [href]="item.sanitized_simpleproof_url" target="_blank" class="badge badge-primary badge-verify" style="font-size: 1.035em;">
|
||||
<span class="icon">
|
||||
|
@@ -13,6 +13,9 @@ export interface SimpleProofCubo {
|
||||
parsed?: { type: string; year: number; studentNumber: number };
|
||||
}
|
||||
|
||||
export type SortField = 'student_name' | 'id_code' | null;
|
||||
export type SortDirection = 'asc' | 'desc';
|
||||
|
||||
@Component({
|
||||
selector: 'app-simpleproof-cubo-widget',
|
||||
templateUrl: './simpleproof-cubo-widget.component.html',
|
||||
@@ -35,6 +38,8 @@ export class SimpleProofCuboWidgetComponent implements OnChanges {
|
||||
lastPage = 1;
|
||||
itemsPerPage = 15;
|
||||
paginationMaxSize = window.innerWidth <= 767.98 ? 3 : 5;
|
||||
sortField: SortField = null;
|
||||
sortDirection: SortDirection = 'asc';
|
||||
|
||||
constructor(
|
||||
private servicesApiService: ServicesApiServices,
|
||||
@@ -122,6 +127,11 @@ export class SimpleProofCuboWidgetComponent implements OnChanges {
|
||||
} else {
|
||||
this.filteredVerified = this.verified;
|
||||
}
|
||||
|
||||
if (this.sortField) {
|
||||
this.applySort();
|
||||
}
|
||||
|
||||
this.page = 1;
|
||||
this.updatePage();
|
||||
}
|
||||
@@ -134,4 +144,56 @@ export class SimpleProofCuboWidgetComponent implements OnChanges {
|
||||
this.page = page;
|
||||
this.updatePage();
|
||||
}
|
||||
|
||||
sortBy(field: SortField, direction?: SortDirection): boolean {
|
||||
if (field && direction) {
|
||||
this.sortField = field;
|
||||
this.sortDirection = direction;
|
||||
} else {
|
||||
if (this.sortField === field) {
|
||||
if (this.sortDirection === 'asc') {
|
||||
this.sortDirection = 'desc';
|
||||
} else {
|
||||
this.sortField = null;
|
||||
}
|
||||
} else {
|
||||
this.sortField = field;
|
||||
this.sortDirection = 'asc';
|
||||
}
|
||||
}
|
||||
|
||||
this.applySort();
|
||||
this.page = 1;
|
||||
this.updatePage();
|
||||
return false;
|
||||
}
|
||||
|
||||
applySort(): void {
|
||||
// default to ascending sort by id
|
||||
const sortByField = this.sortField || 'id_code';
|
||||
const sortDirection = this.sortField ? this.sortDirection : 'asc';
|
||||
|
||||
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' });
|
||||
|
||||
this.filteredVerified.sort((a, b) => {
|
||||
let comparison = 0;
|
||||
if (sortByField === 'student_name') {
|
||||
comparison = collator.compare(a.student_name, b.student_name);
|
||||
} else if (sortByField === 'id_code') {
|
||||
// For ID sorting, try to use the parsed Cubo key logic first
|
||||
if (a.parsed && b.parsed) {
|
||||
if (a.parsed.year !== b.parsed.year) {
|
||||
comparison = a.parsed.year - b.parsed.year;
|
||||
} else if (a.parsed.type !== b.parsed.type) {
|
||||
comparison = a.parsed.type.localeCompare(b.parsed.type);
|
||||
} else {
|
||||
comparison = a.parsed.studentNumber - b.parsed.studentNumber;
|
||||
}
|
||||
} else {
|
||||
comparison = collator.compare(a.id_code, b.id_code);
|
||||
}
|
||||
}
|
||||
return sortDirection === 'asc' ? comparison : -comparison;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -60,6 +60,14 @@ tr, td, th {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.student {
|
||||
width: 50%;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hash {
|
||||
width: 20%;
|
||||
max-width: 700px;
|
||||
@@ -70,9 +78,7 @@ tr, td, th {
|
||||
td.hash {
|
||||
font-family: monospace;
|
||||
}
|
||||
.widget .hash {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.hash {
|
||||
display: none;
|
||||
@@ -93,10 +99,52 @@ td.verified {
|
||||
}
|
||||
|
||||
.proof {
|
||||
width: 25%;
|
||||
width: 120px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.widget {
|
||||
&.id, &.hash {
|
||||
display: none;
|
||||
}
|
||||
&.student {
|
||||
width: 100%;
|
||||
}
|
||||
&.proof {
|
||||
width: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.sortable {
|
||||
user-select: none;
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.sort-icons {
|
||||
display: inline-flex;
|
||||
line-height: 1;
|
||||
flex-direction: column;
|
||||
width: 1em;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
|
||||
fa-icon {
|
||||
transition: color 0.2s ease;
|
||||
margin: -0.25em 0;
|
||||
pointer-events: all;
|
||||
color: var(--secondary);
|
||||
&.active {
|
||||
color: var(--info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.badge-verify {
|
||||
font-size: 1.05em;
|
||||
font-weight: normal;
|
||||
|
Reference in New Issue
Block a user