Files
CRM_Montzar/apps/web/src/views/treasury/TreasuryHomeView.vue
T
MahdiandCursor d12d7b91cb feat(treasury): separate receipts from income and fit finance on a phone
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>
2026-11-10 12:28:27 +03:30

153 lines
5.7 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>