156 lines
3.8 KiB
Vue
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>
|
|
|