Files
gemory/src/pages/ledger/ViewLedgerStatistics.vue
T

156 lines
3.8 KiB
Vue

<script setup lang="ts">
import {computed, ref, watch} from "vue";
import {
BarController,
BarElement,
CategoryScale,
Chart as ChartJS,
type ChartData,
type ChartDataset,
type ChartOptions,
Filler,
Legend,
LinearScale,
LineController,
LineElement,
PointElement,
Tooltip,
} from "chart.js";
import {Chart} from "vue-chartjs";
import {useLedgerParam} from "@/ledger";
import {transactionApi} from "@/mammon";
import {useProcessedResource} from "@/activity";
import {DailyProfitResponse} from "@/generated/mammon";
import {IskLabel} from "@/market";
import {formatIsk} from "@/formaters";
import {DualRangeSlider} from "@/components";
ChartJS.register(
CategoryScale, LinearScale,
BarController, BarElement,
LineController, LineElement, PointElement,
Tooltip, Legend, Filler,
);
const {ledgerId} = useLedgerParam();
const profits = useProcessedResource<DailyProfitResponse[]>(async () => {
if (!ledgerId.value) {
return [];
}
const {data} = await transactionApi.profitPerDayInLedger(ledgerId.value);
return data;
}, []);
const from = ref(0);
const to = ref(0);
watch(() => profits.value.length, length => {
from.value = 0;
to.value = Math.max(length - 1, 0);
}, {immediate: true});
const maxIndex = computed(() => Math.max(profits.value.length - 1, 0));
const range = computed(() => profits.value.slice(from.value, to.value + 1));
const total = computed(() => range.value.reduce((sum, p) => sum + p.profit, 0));
const cumulativeProfit = computed(() => {
let running = 0;
return range.value.map(p => (running += p.profit));
});
const chartData = computed<ChartData<'bar' | 'line'>>(() => {
const cumulative: ChartDataset<'line'> = {
type: 'line',
label: 'Cumulative profit',
data: cumulativeProfit.value,
borderColor: '#38bdf8',
backgroundColor: 'rgba(56, 189, 248, 0.15)',
pointBackgroundColor: '#38bdf8',
fill: true,
tension: 0.2,
yAxisID: 'y',
order: 0,
};
const iskIn: ChartDataset<'bar'> = {
type: 'bar',
label: 'ISK in',
data: range.value.map(p => p.iskIn),
backgroundColor: 'rgba(16, 185, 129, 0.7)',
yAxisID: 'y',
order: 1,
};
const iskOut: ChartDataset<'bar'> = {
type: 'bar',
label: 'ISK out',
data: range.value.map(p => -p.iskOut),
backgroundColor: 'rgba(180, 83, 9, 0.7)',
yAxisID: 'y',
order: 1,
};
return {
labels: range.value.map(p => p.date),
datasets: [cumulative, iskIn, iskOut],
};
});
const chartOptions = computed<ChartOptions<'bar' | 'line'>>(() => ({
responsive: true,
maintainAspectRatio: false,
interaction: {mode: 'index', intersect: false},
plugins: {
legend: {display: true},
tooltip: {
callbacks: {
label: ctx => `${ctx.dataset.label}: ${formatIsk(Math.abs(ctx.parsed.y ?? 0))}`,
},
},
},
scales: {
x: {
stacked: true,
},
y: {
position: 'left',
stacked: true,
ticks: {
callback: value => formatIsk(Math.abs(Number(value))),
},
}
},
}));
</script>
<template>
<div class="mt-4">
<div class="flex justify-end mb-4">
<span class="mr-2">Total profit:</span>
<IskLabel :amount="total" />
</div>
<p v-if="profits.length === 0" class="text-center text-gray-400 mt-8">
No trade profit recorded for this ledger
</p>
<template v-else>
<div class="h-96">
<Chart type="bar" :data="chartData" :options="chartOptions" />
</div>
<div class="flex items-center gap-3 mt-4">
<span class="text-sm w-24 text-gray-400">{{ profits[from]?.date }}</span>
<DualRangeSlider class="grow" :min="0" :max="maxIndex" v-model:from="from" v-model:to="to" />
<span class="text-sm w-24 text-right text-gray-400">{{ profits[to]?.date }}</span>
</div>
</template>
</div>
</template>