feat(finance): open payroll details from mobile cards
Show each payroll as a card on a phone and slide its details up from the bottom so a short sheet leaves the top of the screen clear. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
|||||||
Flex as AFlex,
|
Flex as AFlex,
|
||||||
Form as AForm,
|
Form as AForm,
|
||||||
FormItem as AFormItem,
|
FormItem as AFormItem,
|
||||||
|
Grid,
|
||||||
Modal as AModal,
|
Modal as AModal,
|
||||||
Row as ARow,
|
Row as ARow,
|
||||||
Select as ASelect,
|
Select as ASelect,
|
||||||
@@ -46,6 +47,8 @@ import { parseMoneyNumber } from '@/lib/money';
|
|||||||
const { Text: ATypographyText } = Typography;
|
const { Text: ATypographyText } = Typography;
|
||||||
const { useToken } = theme;
|
const { useToken } = theme;
|
||||||
const { token } = useToken();
|
const { token } = useToken();
|
||||||
|
const screens = Grid.useBreakpoint();
|
||||||
|
const isMobile = computed(() => !screens.value.md);
|
||||||
|
|
||||||
type PayrollPayment = {
|
type PayrollPayment = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -207,12 +210,6 @@ const paymentColumns: TableColumnsType<PayrollPayment> = [
|
|||||||
{ title: 'عملیات', key: 'action' },
|
{ title: 'عملیات', key: 'action' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const detailPaymentColumns: TableColumnsType<PayrollPayment> = [
|
|
||||||
{ title: 'تاریخ', key: 'occurredAt' },
|
|
||||||
{ title: 'حساب', dataIndex: 'accountName', key: 'accountName' },
|
|
||||||
{ title: 'مبلغ', key: 'amount' },
|
|
||||||
];
|
|
||||||
|
|
||||||
function moneyFilled(value: string) {
|
function moneyFilled(value: string) {
|
||||||
return (parseMoneyNumber(String(value ?? ''), 0) ?? 0) > 0;
|
return (parseMoneyNumber(String(value ?? ''), 0) ?? 0) > 0;
|
||||||
}
|
}
|
||||||
@@ -745,6 +742,95 @@ onMounted(() => {
|
|||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
</a-form>
|
</a-form>
|
||||||
|
<a-row>
|
||||||
|
<a-col :xs="24" :md="0">
|
||||||
|
<a-empty v-if="!filtered.length" description="رکوردی با این فیلتر نیست." />
|
||||||
|
<a-flex v-else vertical :gap="8">
|
||||||
|
<a-card
|
||||||
|
v-for="row in filtered"
|
||||||
|
:key="row.id"
|
||||||
|
hoverable
|
||||||
|
@click="detail = row"
|
||||||
|
>
|
||||||
|
<a-flex vertical :gap="8">
|
||||||
|
<a-flex justify="space-between" align="center" :gap="8">
|
||||||
|
<a-typography-text strong>{{ row.fullName }}</a-typography-text>
|
||||||
|
<a-typography-text type="secondary">{{ statusLabel[row.status] ?? row.status }}</a-typography-text>
|
||||||
|
</a-flex>
|
||||||
|
<a-typography-text>{{ formatPeriod(row.period) }}</a-typography-text>
|
||||||
|
<a-row :gutter="[8, 8]">
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-flex vertical>
|
||||||
|
<a-typography-text type="secondary">پایه</a-typography-text>
|
||||||
|
<span>{{ toman(row.baseAmount) }}</span>
|
||||||
|
</a-flex>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-flex vertical>
|
||||||
|
<a-typography-text type="secondary">مزایا</a-typography-text>
|
||||||
|
<span>{{ toman(row.benefits) }}</span>
|
||||||
|
</a-flex>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-flex vertical>
|
||||||
|
<a-typography-text type="secondary">پاداش</a-typography-text>
|
||||||
|
<span>{{ toman(row.bonus) }}</span>
|
||||||
|
</a-flex>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-flex vertical>
|
||||||
|
<a-typography-text type="secondary">کسورات</a-typography-text>
|
||||||
|
<span>{{ toman(row.deductions) }}</span>
|
||||||
|
</a-flex>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-flex vertical>
|
||||||
|
<a-typography-text type="secondary">قابل پرداخت</a-typography-text>
|
||||||
|
<span>{{ toman(row.payable) }}</span>
|
||||||
|
</a-flex>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="12">
|
||||||
|
<a-flex vertical>
|
||||||
|
<a-typography-text type="secondary">پرداختشده</a-typography-text>
|
||||||
|
<span>{{ toman(row.paidAmount) }}</span>
|
||||||
|
</a-flex>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="24">
|
||||||
|
<a-flex justify="space-between" align="center" :gap="8">
|
||||||
|
<a-flex vertical>
|
||||||
|
<a-typography-text type="secondary">مانده</a-typography-text>
|
||||||
|
<a-typography-text strong>{{ toman(row.remaining) }}</a-typography-text>
|
||||||
|
</a-flex>
|
||||||
|
<a-flex :gap="4" align="center" @click.stop>
|
||||||
|
<a-tooltip v-if="row.status !== 'void'" title="ویرایش">
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
aria-label="ویرایش"
|
||||||
|
:style="{ color: token.colorInfo }"
|
||||||
|
@click="startEdit(row)"
|
||||||
|
>
|
||||||
|
<template #icon><EditOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip v-if="row.status === 'open' || row.status === 'partial'" title="پرداخت">
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
aria-label="پرداخت"
|
||||||
|
:style="{ color: token.colorPrimary }"
|
||||||
|
@click="onPayPick(row.id)"
|
||||||
|
>
|
||||||
|
<template #icon><WalletOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</a-flex>
|
||||||
|
</a-flex>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-flex>
|
||||||
|
</a-card>
|
||||||
|
</a-flex>
|
||||||
|
</a-col>
|
||||||
|
<a-col :xs="0" :md="24">
|
||||||
<a-table
|
<a-table
|
||||||
:columns="reportColumns"
|
:columns="reportColumns"
|
||||||
:data-source="filtered"
|
:data-source="filtered"
|
||||||
@@ -801,19 +887,32 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</a-table>
|
</a-table>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
</a-card>
|
</a-card>
|
||||||
|
|
||||||
<a-modal
|
<a-modal
|
||||||
:open="detail != null"
|
:open="detail != null"
|
||||||
:title="detail ? `جزئیات حقوق · ${detail.fullName}` : ''"
|
|
||||||
:footer="null"
|
:footer="null"
|
||||||
:width="'min(640px, calc(100vw - 32px))'"
|
:width="isMobile ? '100%' : 'min(640px, calc(100vw - 32px))'"
|
||||||
|
:wrap-style="isMobile ? { overflow: 'hidden' } : undefined"
|
||||||
|
:style="isMobile ? { position: 'absolute', top: 'auto', bottom: 0, left: 0, right: 0, width: '100%', maxWidth: '100%', height: 'auto', maxHeight: '100%', margin: 0, paddingBottom: 0, overflow: 'hidden' } : undefined"
|
||||||
|
:body-style="isMobile ? { maxHeight: 'calc(100svh - 72px)', overflowX: 'hidden', overflowY: 'auto', scrollbarWidth: 'none' } : undefined"
|
||||||
@cancel="detail = null"
|
@cancel="detail = null"
|
||||||
>
|
>
|
||||||
<a-flex v-if="detail" vertical :gap="16">
|
<template #title>
|
||||||
<a-descriptions bordered :column="1">
|
<a-flex v-if="detail" justify="space-between" align="center" wrap="wrap" :gap="8" :style="{ paddingInlineEnd: '32px' }">
|
||||||
<a-descriptions-item label="پرسنل">{{ detail.fullName }}</a-descriptions-item>
|
<span>جزئیات حقوق · {{ detail.fullName }}</span>
|
||||||
<a-descriptions-item label="دوره">{{ formatPeriod(detail.period) }}</a-descriptions-item>
|
<span>{{ formatPeriod(detail.period) }}</span>
|
||||||
|
</a-flex>
|
||||||
|
</template>
|
||||||
|
<a-flex v-if="detail" vertical :gap="16" style="max-width: 100%; overflow-x: hidden">
|
||||||
|
<a-descriptions
|
||||||
|
bordered
|
||||||
|
:column="1"
|
||||||
|
:label-style="{ whiteSpace: 'normal' }"
|
||||||
|
:content-style="{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }"
|
||||||
|
>
|
||||||
<a-descriptions-item label="وضعیت">{{ statusLabel[detail.status] ?? detail.status }}</a-descriptions-item>
|
<a-descriptions-item label="وضعیت">{{ statusLabel[detail.status] ?? detail.status }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="پایه">{{ toman(detail.baseAmount) }}</a-descriptions-item>
|
<a-descriptions-item label="پایه">{{ toman(detail.baseAmount) }}</a-descriptions-item>
|
||||||
<a-descriptions-item label="مزایا">{{ toman(detail.benefits) }}</a-descriptions-item>
|
<a-descriptions-item label="مزایا">{{ toman(detail.benefits) }}</a-descriptions-item>
|
||||||
@@ -834,25 +933,22 @@ onMounted(() => {
|
|||||||
</a-descriptions>
|
</a-descriptions>
|
||||||
<a-typography-text strong>پرداختها</a-typography-text>
|
<a-typography-text strong>پرداختها</a-typography-text>
|
||||||
<a-empty v-if="!detail.payments.length" description="پرداختی ثبت نشده است." />
|
<a-empty v-if="!detail.payments.length" description="پرداختی ثبت نشده است." />
|
||||||
<a-table
|
<a-flex v-else vertical :gap="8">
|
||||||
v-else
|
<a-flex
|
||||||
:columns="detailPaymentColumns"
|
v-for="payment in detail.payments"
|
||||||
:data-source="detail.payments"
|
:key="payment.id"
|
||||||
:pagination="false"
|
justify="space-between"
|
||||||
row-key="id"
|
align="center"
|
||||||
>
|
wrap="wrap"
|
||||||
<template #bodyCell="{ column, record }">
|
:gap="8"
|
||||||
<template v-if="column.key === 'occurredAt'">
|
>
|
||||||
{{ formatJalaliNumeric(new Date(record.occurredAt)) }}
|
<a-flex vertical>
|
||||||
</template>
|
<span>{{ formatJalaliNumeric(new Date(payment.occurredAt)) }}</span>
|
||||||
<template v-else-if="column.key === 'amount'">
|
<a-typography-text type="secondary">{{ payment.accountName }}</a-typography-text>
|
||||||
{{ toman(record.amount) }}
|
</a-flex>
|
||||||
</template>
|
<span>{{ toman(payment.amount) }}</span>
|
||||||
<template v-else>
|
</a-flex>
|
||||||
{{ record.accountName }}
|
</a-flex>
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</a-table>
|
|
||||||
</a-flex>
|
</a-flex>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</a-flex>
|
</a-flex>
|
||||||
|
|||||||
Reference in New Issue
Block a user