Improve reimbursement lists #222
@@ -1,3 +1,5 @@
|
||||
{{#unless this.isConfirmed}}
|
||||
{{#if this.isConfirmed}}
|
||||
Confirmed at block <strong>{{@confirmedAtBlock}}</strong> (~ {{this.confirmedInHumanTime}} ago)
|
||||
{{else}}
|
||||
Confirming in <strong>{{this.confirmedInBlocks}}</strong> blocks (~ {{this.confirmedInHumanTime}})
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import Component from '@glimmer/component';
|
||||
|
|
||||
import { action } from '@ember/object';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { inject as service } from '@ember/service';
|
||||
import config from 'kredits-web/config/environment';
|
||||
import fmtDateLocalized from 'kredits-web/helpers/fmt-date-localized';
|
||||
|
||||
export default class ReimbursementItemComponent extends Component {
|
||||
@service kredits;
|
||||
@tracked showExpenseDetails = false;
|
||||
|
||||
constructor(owner, args) {
|
||||
super(owner, args);
|
||||
if (this.isUnconfirmed && !this.isVetoed) {
|
||||
this.showExpenseDetails = true;
|
||||
}
|
||||
}
|
||||
|
||||
get ipfsGatewayUrl () {
|
||||
return config.ipfs.gatewayUrl;
|
||||
}
|
||||
|
||||
get isConfirmed () {
|
||||
return (this.args.reimbursement.confirmedAt - this.kredits.currentBlock) <= 0;
|
||||
}
|
||||
|
||||
get isUnconfirmed () {
|
||||
return !this.isConfirmed;
|
||||
}
|
||||
|
||||
get isVetoed () {
|
||||
return this.args.reimbursement.vetoed;
|
||||
}
|
||||
|
||||
get showVetoButton () {
|
||||
return this.isUnconfirmed && this.kredits.currentUserIsCore;
|
||||
}
|
||||
|
||||
get showConfirmedIn () {
|
||||
return !this.isVetoed &&
|
||||
(this.showExpenseDetails || this.isUnconfirmed);
|
||||
}
|
||||
|
||||
get expenses () {
|
||||
return this.args.reimbursement.expenses;
|
||||
}
|
||||
|
||||
get expensesDateRange () {
|
||||
const dates = this.expenses.map(e => e.date).uniq().sort();
|
||||
let out = fmtDateLocalized.compute(dates.firstObject)
|
||||
if (dates.length > 1) {
|
||||
out += ' - ' + fmtDateLocalized.compute(dates.lastObject)
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleExpenseDetails () {
|
||||
this.showExpenseDetails = !this.showExpenseDetails;
|
||||
}
|
||||
|
||||
@action
|
||||
veto (id) {
|
||||
this.kredits.vetoReimbursement(id).then(transaction => {
|
||||
console.debug('[controllers:budget] Veto submitted to chain: '+transaction.hash);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<li data-reimbursement-id={{@reimbursement.id}}
|
||||
|
I'm guessing this is the offender why you had to disable the linter rule Adding a I'm guessing this is the offender why you had to disable the linter rule `no-invalid-interactive`.
Adding a `role="button"` to the list element should fix that.
```suggestion
<li data-reimbursement-id={{@reimbursement.id}}
class="{{item-status @reimbursement}}"
role="button"
{{on "click" this.toggleExpenseDetails}}>
```
|
||||
class="{{item-status @reimbursement}}"
|
||||
role="button" {{on "click" this.toggleExpenseDetails}}>
|
||||
<p class="meta">
|
||||
<span class="recipient">
|
||||
<UserAvatar @contributor={{@reimbursement.contributor}} />
|
||||
</span>
|
||||
<span class="title">
|
||||
Expenses covered by {{@reimbursement.contributor.name}}
|
||||
</span>
|
||||
</p>
|
||||
<p class="token-amount">
|
||||
<span class="amount">
|
||||
{{sats-to-btc @reimbursement.amount}}</span> <span class="symbol">BTC</span>
|
||||
</p>
|
||||
|
||||
{{#if this.showExpenseDetails}}
|
||||
<ExpenseList @expenses={{@reimbursement.expenses}} />
|
||||
{{/if}}
|
||||
|
||||
<div class="meta">
|
||||
<p>
|
||||
{{#if this.showConfirmedIn}}
|
||||
<ConfirmedIn @confirmedAtBlock={{@reimbursement.confirmedAt}} />
|
||||
{{else}}
|
||||
{{#unless this.isVetoed}}{{this.expensesDateRange}}{{/unless}}
|
||||
{{/if}}
|
||||
</p>
|
||||
<p class="actions">
|
||||
<a href="{{this.ipfsGatewayUrl}}/{{@reimbursement.ipfsHash}}"
|
||||
class="button small" target="_blank" rel="noopener noreferrer">
|
||||
Inspect IPFS data
|
||||
</a>
|
||||
{{#if this.showVetoButton}}
|
||||
<button {{on "click" (fn this.veto @reimbursement.id)}}
|
||||
disabled={{@reimbursement.vetoed}}
|
||||
class="button small danger" type="button">veto</button>
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
@@ -1,23 +1,10 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { sort } from '@ember/object/computed';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import config from 'kredits-web/config/environment';
|
||||
|
||||
export default class ReimbursementListComponent extends Component {
|
||||
@service kredits;
|
||||
|
||||
itemSorting = Object.freeze(['pendingStatus:asc', 'id:desc']);
|
||||
@sort('args.items', 'itemSorting') itemsSorted;
|
||||
|
||||
get ipfsGatewayUrl () {
|
||||
return config.ipfs.gatewayUrl;
|
||||
}
|
||||
|
||||
@action
|
||||
veto (id) {
|
||||
this.kredits.vetoReimbursement(id).then(transaction => {
|
||||
console.debug('[controllers:budget] Veto submitted to chain: '+transaction.hash);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,38 +1,5 @@
|
||||
<ul class="item-list spaced reimbursement-list {{if @loading 'loading'}}">
|
||||
{{#each this.itemsSorted as |reimbursement|}}
|
||||
<li data-reimbursement-id={{reimbursement.id}}
|
||||
class="{{item-status reimbursement}}">
|
||||
<p class="meta">
|
||||
<span class="recipient">
|
||||
<UserAvatar @contributor={{reimbursement.contributor}} />
|
||||
</span>
|
||||
<span class="title">
|
||||
Expenses covered by {{reimbursement.contributor.name}}
|
||||
</span>
|
||||
</p>
|
||||
<p class="token-amount">
|
||||
<span class="amount">
|
||||
{{sats-to-btc reimbursement.amount}}</span> <span class="symbol">BTC</span>
|
||||
</p>
|
||||
|
||||
<ExpenseList @expenses={{reimbursement.expenses}} />
|
||||
|
||||
<div class="meta">
|
||||
<p class="confirmation-eta">
|
||||
<ConfirmedIn @confirmedAtBlock={{reimbursement.confirmedAt}} />
|
||||
</p>
|
||||
<p class="actions">
|
||||
<a href="{{this.ipfsGatewayUrl}}/{{reimbursement.ipfsHash}}"
|
||||
class="button small" target="_blank" rel="noopener noreferrer">
|
||||
Inspect IPFS data
|
||||
</a>
|
||||
{{#if this.kredits.currentUserIsCore}}
|
||||
<button {{on "click" (fn this.veto reimbursement.id)}}
|
||||
disabled={{reimbursement.vetoed}}
|
||||
class="button small danger" type="button">veto</button>
|
||||
{{/if}}
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
<ul class="item-list collapsible spaced reimbursement-list {{if @loading 'loading'}}">
|
||||
{{#each this.itemsSorted as |item|}}
|
||||
<ReimbursementItem @reimbursement={{item}} />
|
||||
{{/each}}
|
||||
</ul>
|
||||
@@ -1,7 +1,7 @@
|
||||
import { helper } from '@ember/component/helper';
|
||||
import getLocale from 'kredits-web/utils/get-locale';
|
||||
|
||||
export default helper(function(dateStr) {
|
||||
export default helper(function fmtDateLocalized(dateStr) {
|
||||
const date = new Date(dateStr);
|
||||
const locale = getLocale();
|
||||
return new Intl.DateTimeFormat(locale).format(date);
|
||||
|
||||
@@ -16,6 +16,16 @@ ul.item-list {
|
||||
}
|
||||
}
|
||||
|
||||
&.collapsible {
|
||||
> li {
|
||||
border-left: 1px solid transparent;
|
||||
|
||||
&:hover {
|
||||
border-left: 1px solid $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.loading {
|
||||
@include loading-border-top;
|
||||
|
||||
|
||||
@@ -43,9 +43,6 @@ ul.reimbursement-list {
|
||||
flex: 1;
|
||||
padding: 1.6rem 0 1rem 0;
|
||||
|
||||
&.confirmation-eta {
|
||||
}
|
||||
|
||||
&.actions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user
✂️