web/satellite: fix billing history table styling

This change fixes an issue where the billing history will not display properly

see: https://github.com/storj/storj/issues/5097

Change-Id: Iac9a1f3038d4fdebf38448cccd1084ddc830402a
This commit is contained in:
Wilfred Asomani 2022-09-17 13:13:34 +00:00 committed by Storj Robot
parent 0e99f7a8cf
commit d6c7aa9290
5 changed files with 176 additions and 384 deletions

View File

@ -2,13 +2,23 @@
// See LICENSE for copying information. // See LICENSE for copying information.
<template> <template>
<div> <div class="billing-history">
<BillingHistoryHeader /> <h1 class="billing-history__title">
<VList Billing History
:data-set="historyItems" </h1>
:item-component="billingHistoryStructure"
/> <v-table class="billing-history__table">
<router-view /> <template #head>
<BillingHistoryHeader />
</template>
<template #body>
<BillingHistoryItem
v-for="item in historyItems"
:key="item.id"
:item="item"
/>
</template>
</v-table>
</div> </div>
</template> </template>
@ -17,14 +27,15 @@ import { Component, Vue } from 'vue-property-decorator';
import { PaymentsHistoryItem, PaymentsHistoryItemType } from '@/types/payments'; import { PaymentsHistoryItem, PaymentsHistoryItemType } from '@/types/payments';
import VList from '@/components/common/VList.vue';
import BillingHistoryHeader from '@/components/account/billing/billingTabs/BillingHistoryHeader.vue'; import BillingHistoryHeader from '@/components/account/billing/billingTabs/BillingHistoryHeader.vue';
import BillingHistoryShape from '@/components/account/billing/billingTabs/BillingHistoryShape.vue'; import BillingHistoryItem from '@/components/account/billing/billingTabs/BillingHistoryItem.vue';
import VTable from '@/components/common/VTable.vue';
// @vue/component // @vue/component
@Component({ @Component({
components: { components: {
VList, BillingHistoryItem,
VTable,
BillingHistoryHeader, BillingHistoryHeader,
}, },
}) })
@ -36,22 +47,20 @@ export default class BillingArea extends Vue {
return item.type === PaymentsHistoryItemType.Invoice || item.type === PaymentsHistoryItemType.Charge; return item.type === PaymentsHistoryItemType.Invoice || item.type === PaymentsHistoryItemType.Charge;
}); });
} }
public get billingHistoryStructure() {
return BillingHistoryShape;
}
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.billing_history2 { .billing-history {
position: relative; margin-top: 2rem;
&__content { &__title {
background-color: #fff; font-family: sans-serif;
display: flex; font-size: 1.5rem;
flex-direction: column; }
justify-content: flex-start;
&__table {
margin-top: 1.5rem;
} }
} }
</style> </style>

View File

@ -2,150 +2,24 @@
// See LICENSE for copying information. // See LICENSE for copying information.
<template> <template>
<div class="sort-header-container"> <fragment>
<div class="sort-header-container__date-item"> <th class="align-left">DATE</th>
<p class="sort-header-container__date-item__title">DATE</p> <th class="align-left">STATUS</th>
</div> <th class="align-left">AMOUNT</th>
<div class="sort-header-container__status-item"> <th class="align-left">DOWNLOAD</th>
<p class="sort-header-container__status-item__title creation-date">STATUS</p> </fragment>
</div>
<div class="sort-header-container__amount-item">
<p class="sort-header-container__amount-item__title creation-date">AMOUNT</p>
</div>
<div class="sort-header-container__download-item">
<p class="sort-header-container__download-item__title creation-date">DOWNLOAD</p>
</div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'; import { Component, Vue } from 'vue-property-decorator';
import { Fragment } from 'vue-fragment';
import { AccessGrantsOrderBy, OnHeaderClickCallback } from '@/types/accessGrants';
import { SortDirection } from '@/types/common';
// @vue/component // @vue/component
@Component({ @Component({
components: { components: {
Fragment,
}, },
}) })
export default class BillingHistoryHeader extends Vue { export default class BillingHistoryHeader extends Vue {
@Prop({ default: () => () => new Promise(() => false) })
private readonly onHeaderClickCallback: OnHeaderClickCallback;
public AccessGrantsOrderBy = AccessGrantsOrderBy;
public sortBy: AccessGrantsOrderBy = AccessGrantsOrderBy.NAME;
public sortDirection: SortDirection = SortDirection.ASCENDING;
/**
* Used for arrow styling.
*/
public get getSortDirection(): SortDirection {
return this.sortDirection === SortDirection.DESCENDING ? SortDirection.ASCENDING : SortDirection.DESCENDING;
}
/**
* Sets sorting kind if different from current.
* If same, changes sort direction.
* @param sortBy
*/
public get areAccessGrantsSortedByName(): boolean {
return this.sortBy === AccessGrantsOrderBy.NAME;
}
} }
</script> </script>
<style scoped lang="scss">
.sort-header-container {
display: flex;
height: 40px;
background-color: #fff;
margin-top: 31px;
padding: 16px 16px 0;
border-radius: 8px 8px 0 0;
&__name-item,
&__status-item {
padding-bottom: 13px;
font-family: 'font_medium', sans-serif;
font-style: normal;
font-weight: 500;
font-size: 12px;
line-height: 16px;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #6b7280;
width: 20%;
display: flex;
align-items: center;
margin: 0;
cursor: pointer;
}
&__amount-item {
padding-bottom: 13px;
font-family: 'font_medium', sans-serif;
font-style: normal;
font-weight: 500;
font-size: 12px;
line-height: 16px;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #6b7280;
width: 20%;
display: flex;
align-items: center;
margin: 0;
cursor: pointer;
}
&__download-item {
padding-bottom: 13px;
font-family: 'font_medium', sans-serif;
font-style: normal;
font-weight: 500;
font-size: 12px;
line-height: 16px;
margin-left: 0 0 0 53px;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #6b7280;
width: 24%;
display: flex;
align-items: center;
cursor: pointer;
&__title {
margin-left: 46px;
}
}
&__date-item {
padding-bottom: 13px;
font-family: 'font_medium', sans-serif;
font-style: normal;
font-weight: 500;
font-size: 12px;
line-height: 16px;
letter-spacing: 0.05em;
text-transform: uppercase;
color: #6b7280;
width: 30%;
display: flex;
align-items: center;
margin: 0;
cursor: pointer;
}
@media only screen and (max-width: 768px) {
.sort-header-container__status-item__title,
.sort-header-container__status-item {
display: none;
}
}
}
</style>

View File

@ -0,0 +1,133 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<tr @click="downloadInvoice">
<th class="align-left data mobile">
<div class="few-items">
<p class="array-val date">
<span><Calendar /></span>
<span>{{ item.formattedStart }}</span>
</p>
<p class="array-val status">
<span v-if="item.status === 'paid'"> <CheckIcon class="checkmark" /> </span>
<span>{{ item.formattedStatus }}</span>
</p>
<p class="array-val">
{{ item.amount | centsToDollars }}
</p>
</div>
</th>
<fragment>
<th class="align-left data tablet-laptop">
<p class="date">
<span><Calendar /></span>
<span>{{ item.formattedStart }}</span>
</p>
</th>
<th class="align-left data tablet-laptop">
<p class="status">
<span v-if="item.status === 'paid'"> <CheckIcon class="checkmark" /> </span>
<span>{{ item.formattedStatus }}</span>
</p>
</th>
<th class="align-left data tablet-laptop">
<p>
{{ item.amount | centsToDollars }}
</p>
</th>
<th class="align-left data tablet-laptop">
<a :href="item.link" download>Invoice PDF</a>
</th>
</fragment>
</tr>
</template>
<script lang="ts">
import { Component, Prop } from 'vue-property-decorator';
import { Fragment } from 'vue-fragment';
import { PaymentsHistoryItem } from '@/types/payments';
import Resizable from '@/components/common/Resizable.vue';
import CheckIcon from '@/../static/images/billing/check-green-circle.svg';
import Calendar from '@/../static/images/billing/calendar.svg';
// @vue/component
@Component({
components: {
Calendar,
CheckIcon,
Fragment,
},
})
export default class BillingHistoryItem extends Resizable {
@Prop({ default: new PaymentsHistoryItem('', '', 0, 0, '', '', new Date(), new Date(), 0, 0) })
private readonly item: PaymentsHistoryItem;
public downloadInvoice() {
if (this.isMobile || this.isTablet)
window.open(this.item.link, '_blank', 'noreferrer');
}
}
</script>
<style scoped lang="scss">
a {
color: #0149ff;
text-decoration: underline;
}
.date {
display: flex;
gap: 0.7rem;
align-items: center;
}
.status {
display: flex;
gap: 0.7rem;
align-items: center;
}
.few-items {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.array-val {
font-family: 'font_regular', sans-serif;
font-size: 0.75rem;
line-height: 1.25rem;
&:first-of-type {
font-family: 'font_bold', sans-serif;
font-size: 0.875rem;
margin-bottom: 3px;
}
}
@media only screen and (max-width: 425px) {
.mobile {
display: table-cell;
}
.tablet-laptop {
display: none;
}
}
@media only screen and (min-width: 426px) {
.tablet-laptop {
display: table-cell;
}
.mobile {
display: none;
}
}
</style>

View File

@ -1,228 +0,0 @@
// Copyright (C) 2022 Storj Labs, Inc.
// See LICENSE for copying information.
<template>
<div class="billing-history">
<div class="billing-history__common-info">
<span class="name-container" :title="historyItem.start">
<Calendar />
<p class="name_date">{{ historyItem.start }}</p>
</span>
</div>
<div class="billing-history__common-info">
<span class="name-container" :title="historyItem.status">
<CheckIcon class="checkmark" />
<p class="name_status">{{ historyItem.status }}</p>
</span>
</div>
<div class="billing-history__common-info">
<div class="name-container" :title="historyItem.amount">
<p class="name_amount">{{ historyItem.amount }}</p>
</div>
</div>
<div v-if="historyItem.link" class="billing-history__common-info">
<a href="historyItem.link" download>
<div class="name-container" :title="historyItem.link">
<p class="name_download">Invoice PDF</p>
</div>
</a>
</div>
</div>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
import { PaymentsHistoryItem } from '@/types/payments';
import { AccessGrant } from '@/types/accessGrants';
import CheckIcon from '@/../static/images/billing/check-green-circle.svg';
import Calendar from '@/../static/images/billing/calendar.svg';
// @vue/component
@Component({
components: {
Calendar,
CheckIcon,
},
})
export default class BillingHistoryShape extends Vue {
@Prop({ default: new PaymentsHistoryItem('', '', 0, 0, '', '', new Date(), new Date(), 0, 0) })
@Prop({ default: new AccessGrant('', '', new Date(), '') })
private readonly historyItem: PaymentsHistoryItem;
private popupVisible = false;
public togglePopupVisibility(): void {
this.popupVisible = !this.popupVisible;
}
}
</script>
<style scoped lang="scss">
@mixin popup-menu-button {
padding: 0 15px;
height: 50%;
line-height: 55px;
text-align: left;
font-family: 'font_regular', sans-serif;
color: #1b2533;
transition: 100ms;
}
.checkmark {
margin-top: 3px;
margin-left: 37px;
}
.billing-history {
display: flex;
align-items: center;
justify-content: flex-start;
height: 64px;
background-color: #fff;
border: 1px solid #e5e7eb;
border-bottom: 0;
width: 78%;
&__common-info {
margin-left: 10px;
display: flex;
align-items: center;
justify-content: flex-start;
width: 60%;
}
}
.checkbox-container {
margin-left: 28px;
min-width: 21px;
min-height: 21px;
border-radius: 4px;
border: 1px solid #1b2533;
&__image {
display: none;
}
}
.name-container {
// max-width: calc(100% - 131px);
display: flex;
margin-right: 15px;
}
.name_date {
margin-top: 6px;
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 21px;
color: #354049;
margin-left: 15px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.name_status {
font-family: 'font_bold', sans-serif;
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
color: #111827;
margin-left: 6px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.name_amount {
font-family: 'font_bold', sans-serif;
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 20px;
color: #111827;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-left: -8px;
}
.name_downloaod {
font-family: 'font_bold', sans-serif;
font-size: 16px;
line-height: 21px;
color: #354049;
margin-left: 5px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.date {
font-family: 'font_regular', sans-serif;
font-size: 16px;
line-height: 21px;
color: #354049;
margin: 0;
}
.ellipses {
margin: 0 auto 20px;
font-size: 30px;
font-weight: 1000;
color: #7c8794;
cursor: pointer;
}
.popup-menu {
width: 160px;
height: 100px;
position: absolute;
right: 70px;
bottom: -90px;
z-index: 1;
background: #fff;
border-radius: 10px;
box-shadow: 0 20px 34px rgb(10 27 44 / 28%);
&__popup-details {
@include popup-menu-button;
border-radius: 10px 10px 0 0;
&:hover {
background-color: #354049;
cursor: pointer;
color: #fff;
}
}
&__popup-divider {
height: 1px;
background-color: #e5e7eb;
}
&__popup-delete {
@include popup-menu-button;
border-radius: 0 0 10px 10px;
&:hover {
background-color: #b53737;
cursor: pointer;
color: #fff;
}
}
}
.date-item-container {
width: 50%;
}
.menu-item-container {
width: 10%;
position: relative;
}
</style>

View File

@ -161,6 +161,10 @@ export class PaymentsHistoryItem {
return this.status.charAt(0).toUpperCase() + this.status.substring(1); return this.status.charAt(0).toUpperCase() + this.status.substring(1);
} }
public get formattedStart(): string {
return this.start.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
}
public get hasExpiration(): boolean { public get hasExpiration(): boolean {
// Go's zero date is passed in if the coupon does not expire // Go's zero date is passed in if the coupon does not expire
// Go's zero date is 0001-01-01 00:00:00 +0000 UTC // Go's zero date is 0001-01-01 00:00:00 +0000 UTC