mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
Split invoice history table into two tables for paid and open (#11459)
This commit is contained in:
@@ -11,8 +11,12 @@
|
|||||||
></i>
|
></i>
|
||||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="invoices || transactions">
|
<ng-container *ngIf="openInvoices || paidInvoices || transactions">
|
||||||
<app-billing-history [invoices]="invoices" [transactions]="transactions"></app-billing-history>
|
<app-billing-history
|
||||||
|
[openInvoices]="openInvoices"
|
||||||
|
[paidInvoices]="paidInvoices"
|
||||||
|
[transactions]="transactions"
|
||||||
|
></app-billing-history>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
bitButton
|
bitButton
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
|
|||||||
export class BillingHistoryViewComponent implements OnInit {
|
export class BillingHistoryViewComponent implements OnInit {
|
||||||
loading = false;
|
loading = false;
|
||||||
firstLoaded = false;
|
firstLoaded = false;
|
||||||
invoices: BillingInvoiceResponse[] = [];
|
openInvoices: BillingInvoiceResponse[] = [];
|
||||||
|
paidInvoices: BillingInvoiceResponse[] = [];
|
||||||
transactions: BillingTransactionResponse[] = [];
|
transactions: BillingTransactionResponse[] = [];
|
||||||
hasAdditionalHistory: boolean = false;
|
hasAdditionalHistory: boolean = false;
|
||||||
|
|
||||||
@@ -41,8 +42,14 @@ export class BillingHistoryViewComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
const invoicesPromise = this.accountBillingApiService.getBillingInvoices(
|
const openInvoicesPromise = this.accountBillingApiService.getBillingInvoices(
|
||||||
this.invoices.length > 0 ? this.invoices[this.invoices.length - 1].id : null,
|
"open",
|
||||||
|
this.openInvoices.length > 0 ? this.openInvoices[this.openInvoices.length - 1].id : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const paidInvoicesPromise = this.accountBillingApiService.getBillingInvoices(
|
||||||
|
"paid",
|
||||||
|
this.paidInvoices.length > 0 ? this.paidInvoices[this.paidInvoices.length - 1].id : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
const transactionsPromise = this.accountBillingApiService.getBillingTransactions(
|
const transactionsPromise = this.accountBillingApiService.getBillingTransactions(
|
||||||
@@ -51,15 +58,20 @@ export class BillingHistoryViewComponent implements OnInit {
|
|||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
const accountInvoices = await invoicesPromise;
|
const openInvoices = await openInvoicesPromise;
|
||||||
const accountTransactions = await transactionsPromise;
|
const paidInvoices = await paidInvoicesPromise;
|
||||||
|
const transactions = await transactionsPromise;
|
||||||
|
|
||||||
const pageSize = 5;
|
const pageSize = 5;
|
||||||
|
|
||||||
this.invoices = [...this.invoices, ...accountInvoices];
|
this.openInvoices = [...this.openInvoices, ...openInvoices];
|
||||||
this.transactions = [...this.transactions, ...accountTransactions];
|
this.paidInvoices = [...this.paidInvoices, ...paidInvoices];
|
||||||
this.hasAdditionalHistory = !(
|
this.transactions = [...this.transactions, ...transactions];
|
||||||
accountInvoices.length < pageSize && accountTransactions.length < pageSize
|
|
||||||
);
|
this.hasAdditionalHistory =
|
||||||
|
openInvoices.length >= pageSize ||
|
||||||
|
paidInvoices.length >= pageSize ||
|
||||||
|
transactions.length >= pageSize;
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,12 @@
|
|||||||
></i>
|
></i>
|
||||||
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
<span class="tw-sr-only">{{ "loading" | i18n }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="invoices || transactions">
|
<ng-container *ngIf="openInvoices || paidInvoices || transactions">
|
||||||
<app-billing-history [invoices]="invoices" [transactions]="transactions"></app-billing-history>
|
<app-billing-history
|
||||||
|
[openInvoices]="openInvoices"
|
||||||
|
[paidInvoices]="paidInvoices"
|
||||||
|
[transactions]="transactions"
|
||||||
|
></app-billing-history>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
bitButton
|
bitButton
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import {
|
|||||||
export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
|
export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
|
||||||
loading = false;
|
loading = false;
|
||||||
firstLoaded = false;
|
firstLoaded = false;
|
||||||
invoices: BillingInvoiceResponse[] = [];
|
openInvoices: BillingInvoiceResponse[] = [];
|
||||||
|
paidInvoices: BillingInvoiceResponse[] = [];
|
||||||
transactions: BillingTransactionResponse[] = [];
|
transactions: BillingTransactionResponse[] = [];
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
hasAdditionalHistory: boolean = false;
|
hasAdditionalHistory: boolean = false;
|
||||||
@@ -51,9 +52,16 @@ export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
|
||||||
const invoicesPromise = this.organizationBillingApiService.getBillingInvoices(
|
const openInvoicesPromise = this.organizationBillingApiService.getBillingInvoices(
|
||||||
this.organizationId,
|
this.organizationId,
|
||||||
this.invoices.length > 0 ? this.invoices[this.invoices.length - 1].id : null,
|
"open",
|
||||||
|
this.openInvoices.length > 0 ? this.openInvoices[this.openInvoices.length - 1].id : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const paidInvoicesPromise = this.organizationBillingApiService.getBillingInvoices(
|
||||||
|
this.organizationId,
|
||||||
|
"paid",
|
||||||
|
this.paidInvoices.length > 0 ? this.paidInvoices[this.paidInvoices.length - 1].id : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
const transactionsPromise = this.organizationBillingApiService.getBillingTransactions(
|
const transactionsPromise = this.organizationBillingApiService.getBillingTransactions(
|
||||||
@@ -63,13 +71,21 @@ export class OrgBillingHistoryViewComponent implements OnInit, OnDestroy {
|
|||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
|
||||||
const invoices = await invoicesPromise;
|
const openInvoices = await openInvoicesPromise;
|
||||||
|
const paidInvoices = await paidInvoicesPromise;
|
||||||
const transactions = await transactionsPromise;
|
const transactions = await transactionsPromise;
|
||||||
|
|
||||||
const pageSize = 5;
|
const pageSize = 5;
|
||||||
|
|
||||||
this.invoices = [...this.invoices, ...invoices];
|
this.openInvoices = [...this.openInvoices, ...openInvoices];
|
||||||
|
this.paidInvoices = [...this.paidInvoices, ...paidInvoices];
|
||||||
this.transactions = [...this.transactions, ...transactions];
|
this.transactions = [...this.transactions, ...transactions];
|
||||||
this.hasAdditionalHistory = !(invoices.length < pageSize && transactions.length < pageSize);
|
|
||||||
|
this.hasAdditionalHistory =
|
||||||
|
openInvoices.length <= pageSize ||
|
||||||
|
paidInvoices.length <= pageSize ||
|
||||||
|
transactions.length <= pageSize;
|
||||||
|
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<bit-section>
|
<bit-section>
|
||||||
<h3 bitTypography="h3">{{ "invoices" | i18n }}</h3>
|
<h3 bitTypography="h3">{{ "unpaid" | i18n }} {{ "invoices" | i18n }}</h3>
|
||||||
<p bitTypography="body1" *ngIf="!invoices || !invoices.length">{{ "noInvoices" | i18n }}</p>
|
<p bitTypography="body1" *ngIf="!openInvoices || !openInvoices.length">
|
||||||
|
{{ "noUnpaidInvoices" | i18n }}
|
||||||
|
</p>
|
||||||
<bit-table>
|
<bit-table>
|
||||||
<ng-template body>
|
<ng-template body>
|
||||||
<tr bitRow *ngFor="let i of invoices">
|
<tr bitRow *ngFor="let i of openInvoices">
|
||||||
<td bitCell>{{ i.date | date: "mediumDate" }}</td>
|
<td bitCell>{{ i.date | date: "mediumDate" }}</td>
|
||||||
<td bitCell>
|
<td bitCell>
|
||||||
<a
|
<a
|
||||||
@@ -26,7 +28,51 @@
|
|||||||
>
|
>
|
||||||
</td>
|
</td>
|
||||||
<td bitCell>{{ i.amount | currency: "$" }}</td>
|
<td bitCell>{{ i.amount | currency: "$" }}</td>
|
||||||
|
<td bitCell class="tw-w-28">
|
||||||
|
<span *ngIf="i.paid">
|
||||||
|
<i class="bwi bwi-check tw-text-success" aria-hidden="true"></i>
|
||||||
|
{{ "paid" | i18n }}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="!i.paid">
|
||||||
|
<i class="bwi bwi-exclamation-circle tw-text-muted" aria-hidden="true"></i>
|
||||||
|
{{ "unpaid" | i18n }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</ng-template>
|
||||||
|
</bit-table>
|
||||||
|
</bit-section>
|
||||||
|
<bit-section>
|
||||||
|
<h3 bitTypography="h3">{{ "paid" | i18n }} {{ "invoices" | i18n }}</h3>
|
||||||
|
<p bitTypography="body1" *ngIf="!paidInvoices || !paidInvoices.length">
|
||||||
|
{{ "noPaidInvoices" | i18n }}
|
||||||
|
</p>
|
||||||
|
<bit-table>
|
||||||
|
<ng-template body>
|
||||||
|
<tr bitRow *ngFor="let i of paidInvoices">
|
||||||
|
<td bitCell>{{ i.date | date: "mediumDate" }}</td>
|
||||||
<td bitCell>
|
<td bitCell>
|
||||||
|
<a
|
||||||
|
href="{{ i.pdfUrl }}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
class="tw-mr-2"
|
||||||
|
appA11yTitle="{{ 'downloadInvoice' | i18n }}"
|
||||||
|
>
|
||||||
|
<i class="bwi bwi-file-pdf" aria-hidden="true"></i
|
||||||
|
></a>
|
||||||
|
<a
|
||||||
|
bitLink
|
||||||
|
href="{{ i.url }}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
title="{{ 'viewInvoice' | i18n }}"
|
||||||
|
>
|
||||||
|
{{ "invoiceNumber" | i18n: i.number }}</a
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
<td bitCell>{{ i.amount | currency: "$" }}</td>
|
||||||
|
<td bitCell class="tw-w-28">
|
||||||
<span *ngIf="i.paid">
|
<span *ngIf="i.paid">
|
||||||
<i class="bwi bwi-check tw-text-success" aria-hidden="true"></i>
|
<i class="bwi bwi-check tw-text-success" aria-hidden="true"></i>
|
||||||
{{ "paid" | i18n }}
|
{{ "paid" | i18n }}
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ import {
|
|||||||
})
|
})
|
||||||
export class BillingHistoryComponent {
|
export class BillingHistoryComponent {
|
||||||
@Input()
|
@Input()
|
||||||
invoices: BillingInvoiceResponse[];
|
openInvoices: BillingInvoiceResponse[];
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
paidInvoices: BillingInvoiceResponse[];
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
transactions: BillingTransactionResponse[];
|
transactions: BillingTransactionResponse[];
|
||||||
|
|||||||
@@ -2616,8 +2616,11 @@
|
|||||||
"invoices": {
|
"invoices": {
|
||||||
"message": "Invoices"
|
"message": "Invoices"
|
||||||
},
|
},
|
||||||
"noInvoices": {
|
"noUnpaidInvoices": {
|
||||||
"message": "No invoices."
|
"message": "No unpaid invoices."
|
||||||
|
},
|
||||||
|
"noPaidInvoices": {
|
||||||
|
"message": "No paid invoices."
|
||||||
},
|
},
|
||||||
"paid": {
|
"paid": {
|
||||||
"message": "Paid",
|
"message": "Paid",
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import {
|
|||||||
} from "@bitwarden/common/billing/models/response/billing.response";
|
} from "@bitwarden/common/billing/models/response/billing.response";
|
||||||
|
|
||||||
export class AccountBillingApiServiceAbstraction {
|
export class AccountBillingApiServiceAbstraction {
|
||||||
getBillingInvoices: (id: string, startAfter?: string) => Promise<BillingInvoiceResponse[]>;
|
getBillingInvoices: (status?: string, startAfter?: string) => Promise<BillingInvoiceResponse[]>;
|
||||||
getBillingTransactions: (
|
getBillingTransactions: (startAfter?: string) => Promise<BillingTransactionResponse[]>;
|
||||||
id: string,
|
|
||||||
startAfter?: string,
|
|
||||||
) => Promise<BillingTransactionResponse[]>;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import {
|
|||||||
} from "@bitwarden/common/billing/models/response/billing.response";
|
} from "@bitwarden/common/billing/models/response/billing.response";
|
||||||
|
|
||||||
export class OrganizationBillingApiServiceAbstraction {
|
export class OrganizationBillingApiServiceAbstraction {
|
||||||
getBillingInvoices: (id: string, startAfter?: string) => Promise<BillingInvoiceResponse[]>;
|
getBillingInvoices: (
|
||||||
|
id: string,
|
||||||
|
status?: string,
|
||||||
|
startAfter?: string,
|
||||||
|
) => Promise<BillingInvoiceResponse[]>;
|
||||||
|
|
||||||
getBillingTransactions: (
|
getBillingTransactions: (
|
||||||
id: string,
|
id: string,
|
||||||
startAfter?: string,
|
startAfter?: string,
|
||||||
|
|||||||
@@ -8,11 +8,25 @@ import {
|
|||||||
export class AccountBillingApiService implements AccountBillingApiServiceAbstraction {
|
export class AccountBillingApiService implements AccountBillingApiServiceAbstraction {
|
||||||
constructor(private apiService: ApiService) {}
|
constructor(private apiService: ApiService) {}
|
||||||
|
|
||||||
async getBillingInvoices(startAfter?: string): Promise<BillingInvoiceResponse[]> {
|
async getBillingInvoices(
|
||||||
const queryParams = startAfter ? `?startAfter=${startAfter}` : "";
|
status?: string,
|
||||||
|
startAfter?: string,
|
||||||
|
): Promise<BillingInvoiceResponse[]> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
params.append("status", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startAfter) {
|
||||||
|
params.append("startAfter", startAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = `?${params.toString()}`;
|
||||||
|
|
||||||
const r = await this.apiService.send(
|
const r = await this.apiService.send(
|
||||||
"GET",
|
"GET",
|
||||||
`/accounts/billing/invoices${queryParams}`,
|
`/accounts/billing/invoices${queryString}`,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -8,11 +8,26 @@ import {
|
|||||||
export class OrganizationBillingApiService implements OrganizationBillingApiServiceAbstraction {
|
export class OrganizationBillingApiService implements OrganizationBillingApiServiceAbstraction {
|
||||||
constructor(private apiService: ApiService) {}
|
constructor(private apiService: ApiService) {}
|
||||||
|
|
||||||
async getBillingInvoices(id: string, startAfter?: string): Promise<BillingInvoiceResponse[]> {
|
async getBillingInvoices(
|
||||||
const queryParams = startAfter ? `?startAfter=${startAfter}` : "";
|
id: string,
|
||||||
|
status?: string,
|
||||||
|
startAfter?: string,
|
||||||
|
): Promise<BillingInvoiceResponse[]> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
params.append("status", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startAfter) {
|
||||||
|
params.append("startAfter", startAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = `?${params.toString()}`;
|
||||||
|
|
||||||
const r = await this.apiService.send(
|
const r = await this.apiService.send(
|
||||||
"GET",
|
"GET",
|
||||||
`/organizations/${id}/billing/invoices${queryParams}`,
|
`/organizations/${id}/billing/invoices${queryString}`,
|
||||||
null,
|
null,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
|||||||
Reference in New Issue
Block a user