A receipt or payment only reduces the party balance, while an income or expense keeps both amounts. Finance screens stack into cards on a phone so the forms and lists stay readable. Co-authored-by: Cursor <cursoragent@cursor.com>
153 lines
5.7 KiB
Vue
153 lines
5.7 KiB
Vue
<script setup lang="ts">
|
||
import { onMounted, ref } from 'vue';
|
||
import {
|
||
Alert as AAlert,
|
||
Card as ACard,
|
||
Col as ACol,
|
||
Divider as ADivider,
|
||
Empty as AEmpty,
|
||
Flex as AFlex,
|
||
List as AList,
|
||
Row as ARow,
|
||
Statistic as AStatistic,
|
||
Typography,
|
||
} from 'ant-design-vue';
|
||
import { api } from '@/lib/api';
|
||
import type { TreasuryDashboardDto } from '@mento/shared';
|
||
import { toman } from './treasury-format';
|
||
import { useTreasuryMobile } from './useTreasuryMobile';
|
||
|
||
const { Text: ATypographyText } = Typography;
|
||
const isMobile = useTreasuryMobile();
|
||
const AListItem = AList.Item;
|
||
const AListItemMeta = AList.Item.Meta;
|
||
|
||
const data = ref<TreasuryDashboardDto | null>(null);
|
||
const error = ref('');
|
||
|
||
const cards = [
|
||
{ key: 'bookedIncome', label: 'درآمد ثبتشده', hint: 'تعهدی' },
|
||
{ key: 'actualReceipts', label: 'دریافت واقعی', hint: 'ورود به حساب' },
|
||
{ key: 'bookedExpense', label: 'هزینه ثبتشده', hint: 'تعهدی' },
|
||
{ key: 'actualPayments', label: 'پرداخت واقعی', hint: 'خروج از حساب' },
|
||
{ key: 'operatingProfit', label: 'سود یا زیان عملیاتی', hint: 'درآمد و فروش فاکتور − هزینه' },
|
||
{ key: 'receivables', label: 'مطالبات وصولنشده', hint: 'اسناد باز و مانده فاکتور' },
|
||
{ key: 'payables', label: 'تعهدات پرداخت', hint: 'هزینه باز، حقوق و برگشتی' },
|
||
{ key: 'totalBalance', label: 'موجودی حسابها', hint: 'صندوق و بانک' },
|
||
] as const;
|
||
|
||
onMounted(async () => {
|
||
try {
|
||
const res = await api.get<TreasuryDashboardDto>('/treasury/dashboard');
|
||
data.value = res.data;
|
||
} catch {
|
||
error.value = 'بارگذاری داشبورد مالی ناموفق بود.';
|
||
}
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<a-flex vertical :gap="16">
|
||
<a-alert v-if="error" type="error" show-icon :message="error" />
|
||
|
||
<template v-if="data">
|
||
<a-row :gutter="[16, 16]">
|
||
<a-col
|
||
v-for="card in cards"
|
||
:key="card.key"
|
||
:xs="24"
|
||
:sm="12"
|
||
:md="8"
|
||
:xl="6"
|
||
>
|
||
<a-card :bordered="false">
|
||
<a-statistic :title="card.label" :value="toman(data[card.key])" />
|
||
<a-typography-text type="secondary">{{ card.hint }}</a-typography-text>
|
||
</a-card>
|
||
</a-col>
|
||
</a-row>
|
||
|
||
<a-card :bordered="false">
|
||
<a-row :gutter="[16, 16]">
|
||
<a-col :xs="24" :lg="12">
|
||
<a-divider orientation="right">اقساط نزدیک یا معوق</a-divider>
|
||
<a-empty
|
||
v-if="!data.upcomingInstallments.length"
|
||
description="قسط بازی نیست."
|
||
/>
|
||
<a-list
|
||
v-else
|
||
:item-layout="isMobile ? 'vertical' : 'horizontal'"
|
||
:data-source="data.upcomingInstallments"
|
||
>
|
||
<template #renderItem="{ item }">
|
||
<a-list-item>
|
||
<a-list-item-meta>
|
||
<template #title>قسط {{ item.position }}</template>
|
||
<template #description>
|
||
{{ item.status === 'overdue' ? 'معوق' : 'باز' }} · مانده {{ toman(item.remaining) }}
|
||
</template>
|
||
</a-list-item-meta>
|
||
<template #actions>
|
||
<a-typography-text strong>{{ toman(item.amount) }}</a-typography-text>
|
||
</template>
|
||
</a-list-item>
|
||
</template>
|
||
</a-list>
|
||
</a-col>
|
||
|
||
<a-col :xs="24" :lg="12">
|
||
<a-divider orientation="right">پرداختهای آینده</a-divider>
|
||
<a-empty
|
||
v-if="!data.upcomingPayables.length"
|
||
description="تعهد پرداخت بازی نیست."
|
||
/>
|
||
<a-list
|
||
v-else
|
||
:item-layout="isMobile ? 'vertical' : 'horizontal'"
|
||
:data-source="data.upcomingPayables"
|
||
>
|
||
<template #renderItem="{ item }">
|
||
<a-list-item>
|
||
<a-list-item-meta>
|
||
<template #title>{{ item.title }}</template>
|
||
<template #description>مانده {{ toman(item.remaining) }}</template>
|
||
</a-list-item-meta>
|
||
<template #actions>
|
||
<a-typography-text strong>{{ toman(item.amount) }}</a-typography-text>
|
||
</template>
|
||
</a-list-item>
|
||
</template>
|
||
</a-list>
|
||
</a-col>
|
||
</a-row>
|
||
|
||
<a-divider orientation="right">آخرین اسناد</a-divider>
|
||
<a-empty
|
||
v-if="!data.recent.length"
|
||
description="هنوز سندی ثبت نشده است."
|
||
/>
|
||
<a-list
|
||
v-else
|
||
:item-layout="isMobile ? 'vertical' : 'horizontal'"
|
||
:data-source="data.recent"
|
||
>
|
||
<template #renderItem="{ item }">
|
||
<a-list-item>
|
||
<a-list-item-meta>
|
||
<template #title>{{ item.title }}</template>
|
||
<template #description>
|
||
{{ item.kind === 'income' ? 'درآمد' : 'هزینه' }} · {{ item.status }}
|
||
</template>
|
||
</a-list-item-meta>
|
||
<template #actions>
|
||
<a-typography-text strong>{{ toman(item.amount) }}</a-typography-text>
|
||
</template>
|
||
</a-list-item>
|
||
</template>
|
||
</a-list>
|
||
</a-card>
|
||
</template>
|
||
</a-flex>
|
||
</template>
|