Add activity detail, Jalali pickers, and customer/user profiles.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-02 21:29:00 +03:30
co-authored by Cursor
parent 574feedfa5
commit f884c60cab
39 changed files with 8255 additions and 441 deletions
File diff suppressed because it is too large Load Diff
+616
View File
@@ -0,0 +1,616 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import { RouterLink, useRoute, useRouter } from 'vue-router';
import { api } from '@/lib/api';
import {
calendarDaysBetween,
formatClockFa,
formatJalaliDay,
} from '@/lib/jalali';
import UiIcon from '@/components/UiIcon.vue';
import UserAvatar from '@/components/UserAvatar.vue';
import ActivityCommentSheet from '@/components/activities/ActivityCommentSheet.vue';
import ActivityPostponeSheet from '@/components/activities/ActivityPostponeSheet.vue';
import type { ActivityDto, ActivityPriority, ActivityType } from '@mento/shared';
const route = useRoute();
const router = useRouter();
const id = computed(() => (typeof route.params.id === 'string' ? route.params.id : ''));
const loading = ref(true);
const saving = ref(false);
const error = ref('');
const activity = ref<ActivityDto | null>(null);
const moreOpen = ref(false);
const commentOpen = ref(false);
const postponeOpen = ref(false);
const noteOpen = ref(true);
const typeLabel: Record<ActivityType, string> = {
call: 'تماس',
meeting: 'جلسه',
task: 'وظیفه',
note: 'پیام',
};
const typeIcon: Record<ActivityType, 'phone' | 'users' | 'check' | 'chat'> = {
call: 'phone',
meeting: 'users',
task: 'check',
note: 'chat',
};
const priorityLabel: Record<ActivityPriority, string> = {
very_low: 'خیلی کم',
low: 'کم',
medium: 'متوسط',
high: 'زیاد',
very_high: 'خیلی زیاد',
};
const subtitle = computed(() => {
const a = activity.value;
if (!a) return '';
return a.deal?.title || a.company?.name || a.person?.fullName || '';
});
const dueLabel = computed(() => {
const due = activity.value?.dueAt;
if (!due) return 'بدون موعد';
const d = new Date(due);
const days = calendarDaysBetween(d, new Date());
const clock = formatClockFa(d);
if (days === 0) return `امروز - ${clock}`;
if (days === 1) return `دیروز - ${clock}`;
if (days === -1) return `فردا - ${clock}`;
return `${formatJalaliDay(d)} - ${clock}`;
});
const createdLabel = computed(() => {
const raw = activity.value?.createdAt;
if (!raw) return '—';
const d = new Date(raw);
return `${formatJalaliDay(d)} - ${formatClockFa(d)}`;
});
const isDone = computed(() => activity.value?.status === 'done');
const statusText = computed(() => {
if (!activity.value) return '—';
if (activity.value.status === 'done') return 'انجام شد';
if (activity.value.status === 'canceled') return 'لغو شده';
return 'انجام نشده';
});
async function load() {
if (!id.value) return;
loading.value = true;
error.value = '';
moreOpen.value = false;
try {
const { data } = await api.get<ActivityDto>(`/activities/${id.value}`);
activity.value = data;
} catch {
error.value = 'بارگذاری جزئیات فعالیت ناموفق بود.';
activity.value = null;
} finally {
loading.value = false;
}
}
async function complete() {
if (!id.value || isDone.value) return;
saving.value = true;
error.value = '';
try {
await api.patch(`/activities/${id.value}/complete`);
await router.push({ name: 'activities' });
} catch {
error.value = 'ثبت انجام‌شدن ناموفق بود.';
} finally {
saving.value = false;
}
}
async function postponeTo(iso: string) {
if (!id.value || !activity.value || isDone.value) return;
saving.value = true;
error.value = '';
try {
await api.patch(`/activities/${id.value}`, { dueAt: iso });
postponeOpen.value = false;
await load();
} catch {
error.value = 'تعویق فعالیت ناموفق بود.';
} finally {
saving.value = false;
}
}
async function submitComment(text: string) {
const body = text.trim();
if (!id.value || !activity.value || !body) return;
saving.value = true;
error.value = '';
try {
const prev = activity.value.description?.trim();
const next = prev ? `${prev}\n${body}` : body;
await api.patch(`/activities/${id.value}`, { description: next });
commentOpen.value = false;
await load();
} catch {
error.value = 'ثبت نظر ناموفق بود.';
} finally {
saving.value = false;
}
}
async function remove() {
moreOpen.value = false;
if (!id.value || !confirm('این فعالیت حذف شود؟')) return;
try {
await api.delete(`/activities/${id.value}`);
await router.push({ name: 'activities' });
} catch {
error.value = 'حذف ناموفق بود.';
}
}
watch(id, () => {
commentOpen.value = false;
postponeOpen.value = false;
void load();
});
onMounted(load);
</script>
<template>
<section class="page" @click="moreOpen = false">
<header class="nav">
<RouterLink class="icon-btn back" :to="{ name: 'activities' }" aria-label="بازگشت">
<UiIcon name="chevron" :size="18" />
</RouterLink>
<span class="brand">mento</span>
<div class="more" @click.stop>
<button class="icon-btn" type="button" aria-label="منوی بیشتر" @click="moreOpen = !moreOpen">
<UiIcon name="dots-v" :size="18" />
</button>
<div v-if="moreOpen && activity" class="menu">
<RouterLink :to="{ name: 'activity-edit', params: { id } }">ویرایش</RouterLink>
<button type="button" class="danger" @click="remove">حذف</button>
</div>
</div>
</header>
<h1>جزئیات فعالیت</h1>
<p v-if="loading" class="muted">در حال بارگذاری…</p>
<p v-else-if="error && !activity" class="error">{{ error }}</p>
<template v-else-if="activity">
<article class="card">
<header class="hero">
<div>
<h2>
{{ activity.title }}
<UiIcon name="star" :size="16" class="star" />
</h2>
<p v-if="subtitle" class="sub">
<UiIcon name="building" :size="14" />
{{ subtitle }}
</p>
</div>
<span class="type-ico" :class="activity.type">
<UiIcon :name="typeIcon[activity.type]" :size="22" />
</span>
</header>
<dl>
<div>
<dt>
<UiIcon :name="typeIcon[activity.type]" :size="15" />
نوع فعالیت
</dt>
<dd>{{ typeLabel[activity.type] }}</dd>
</div>
<div>
<dt>
<UiIcon name="calendar" :size="15" />
تاریخ و زمان
</dt>
<dd>{{ dueLabel }}</dd>
</div>
<div>
<dt>
<UiIcon name="user" :size="15" />
مسئول
</dt>
<dd class="person">
<UserAvatar v-if="activity.assignee" :name="activity.assignee.fullName" :size="28" />
{{ activity.assignee?.fullName || '—' }}
</dd>
</div>
<div>
<dt>
<UiIcon name="flag" :size="15" class="flag" :class="activity.priority || 'medium'" />
اولویت
</dt>
<dd class="priority" :class="activity.priority || 'medium'">
{{ priorityLabel[activity.priority || 'medium'] }}
</dd>
</div>
<div>
<dt>وضعیت</dt>
<dd>
<span class="status" :class="{ done: isDone }">{{ statusText }}</span>
</dd>
</div>
<div>
<dt>
<UiIcon name="user" :size="15" />
ایجاد شده توسط
</dt>
<dd class="person">
<UserAvatar v-if="activity.createdBy" :name="activity.createdBy.fullName" :size="28" />
{{ activity.createdBy?.fullName || '—' }}
</dd>
</div>
<div>
<dt>
<UiIcon name="calendar" :size="15" />
تاریخ ایجاد
</dt>
<dd>{{ createdLabel }}</dd>
</div>
</dl>
</article>
<article class="card note">
<button class="note-head" type="button" @click="noteOpen = !noteOpen">
<strong>
<UiIcon name="document" :size="16" />
یادداشت
</strong>
<UiIcon name="chevron" :size="16" :class="{ open: noteOpen }" />
</button>
<p v-show="noteOpen" :class="{ empty: !activity.description?.trim() }">
{{ activity.description?.trim() || 'یادداشتی ثبت نشده.' }}
</p>
</article>
<p v-if="error" class="error">{{ error }}</p>
<ActivityCommentSheet
v-if="commentOpen"
:activity="activity"
:saving="saving"
@close="commentOpen = false"
@submit="submitComment"
/>
<ActivityPostponeSheet
v-if="postponeOpen"
:due-at="activity.dueAt"
:saving="saving"
@close="postponeOpen = false"
@submit="postponeTo"
/>
</template>
<footer v-if="activity && !commentOpen && !postponeOpen" class="dock">
<button class="act primary" type="button" :disabled="saving || isDone" @click="complete">
<UiIcon name="check" :size="16" />
انجام شد
</button>
<button class="act ghost" type="button" :disabled="saving || isDone" @click="postponeOpen = true">
<UiIcon name="refresh" :size="16" />
تعویق
</button>
<button class="act ghost" type="button" :disabled="saving" @click="commentOpen = true">
<UiIcon name="chat" :size="16" />
ثبت نظر
</button>
</footer>
</section>
</template>
<style scoped>
.page {
max-width: 640px;
width: 100%;
min-width: 0;
margin: 0 auto;
display: grid;
gap: 0.9rem;
padding-bottom: 5.8rem;
}
.nav {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
z-index: 4;
}
.brand {
font-weight: 800;
color: #0f766e;
letter-spacing: -0.04em;
}
.icon-btn {
width: 38px;
height: 38px;
border: 0;
background: #fff;
border-radius: 12px;
display: grid;
place-items: center;
color: #334155;
box-shadow: 0 4px 12px rgba(15, 23, 42, 0.05);
}
.back :deep(.ico) {
transform: scaleX(-1);
}
.more {
position: relative;
}
.menu {
position: absolute;
top: calc(100% + 6px);
inset-inline-end: 0;
min-width: 10rem;
background: #fff;
border-radius: 12px;
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.14);
padding: 0.35rem;
display: grid;
z-index: 6;
}
.menu a,
.menu button {
display: block;
width: 100%;
text-align: start;
border: 0;
background: none;
padding: 0.62rem 0.75rem;
border-radius: 8px;
font: inherit;
cursor: pointer;
color: #0f172a;
}
.menu .danger {
color: #b91c1c;
}
h1 {
margin: 0.1rem 0 0;
font-size: 1.35rem;
}
.muted,
.error {
margin: 0;
}
.muted {
color: #64748b;
}
.error {
color: #b91c1c;
}
.card {
background: #fff;
border-radius: 20px;
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.05);
padding: 1rem 1.05rem;
}
.hero {
display: flex;
justify-content: space-between;
gap: 0.8rem;
align-items: flex-start;
padding-bottom: 0.85rem;
}
.hero h2 {
margin: 0;
font-size: 1.05rem;
display: flex;
align-items: flex-start;
gap: 0.35rem;
}
.star {
color: #cbd5e1;
margin-top: 0.15rem;
}
.sub {
margin: 0.4rem 0 0;
color: #64748b;
font-size: 0.84rem;
display: flex;
align-items: center;
gap: 0.3rem;
}
.type-ico {
width: 48px;
height: 48px;
border-radius: 999px;
display: grid;
place-items: center;
flex-shrink: 0;
}
.type-ico.call {
background: #ecfdf5;
color: #0f766e;
}
.type-ico.meeting {
background: #f5f3ff;
color: #6d28d9;
}
.type-ico.task {
background: #fff7ed;
color: #c2410c;
}
.type-ico.note {
background: #eff6ff;
color: #2563eb;
}
dl {
margin: 0;
}
dl > div {
display: flex;
justify-content: space-between;
gap: 0.8rem;
align-items: center;
padding: 0.78rem 0;
border-top: 1px solid #f1f5f9;
}
dt,
dd {
margin: 0;
}
dt {
display: flex;
align-items: center;
gap: 0.4rem;
color: #64748b;
font-size: 0.86rem;
}
dd {
font-weight: 700;
text-align: start;
}
.person {
display: flex;
align-items: center;
gap: 0.45rem;
}
.flag.low,
.priority.low {
color: #64748b;
}
.flag.medium,
.priority.medium {
color: #ea580c;
}
.flag.high,
.priority.high {
color: #dc2626;
}
.status {
display: inline-flex;
border-radius: 999px;
padding: 0.22rem 0.65rem;
background: #ecfdf5;
color: #0f766e;
font-size: 0.8rem;
}
.status.done {
background: #dcfce7;
color: #166534;
}
.note-head {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
border: 0;
background: none;
padding: 0;
font: inherit;
color: #0f766e;
cursor: pointer;
}
.note-head :deep(.ico:last-child) {
transform: rotate(90deg);
color: #94a3b8;
}
.note-head :deep(.ico.open) {
transform: rotate(-90deg);
}
.note p {
margin: 0.75rem 0 0;
color: #334155;
line-height: 1.9;
font-size: 0.9rem;
}
.note p.empty {
color: #94a3b8;
}
.dock {
position: fixed;
inset-inline: 0.85rem;
bottom: calc(0.55rem + env(safe-area-inset-bottom));
display: grid;
grid-template-columns: 1.15fr 1fr 1fr;
gap: 0.5rem;
z-index: 8;
}
.act {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.3rem;
min-height: 46px;
border-radius: 14px;
border: 1px solid #99f6e4;
background: #fff;
color: #0f766e;
font: inherit;
font-weight: 700;
cursor: pointer;
}
.act.primary {
background: #14b8a6;
border-color: #14b8a6;
color: #fff;
}
.act:disabled {
opacity: 0.45;
cursor: not-allowed;
}
@media (min-width: 900px) {
.dock {
position: sticky;
inset-inline: auto;
bottom: 0;
}
}
</style>
File diff suppressed because it is too large Load Diff
+10 -1
View File
@@ -4,6 +4,7 @@ import { RouterLink, useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { api } from '@/lib/api';
import CustomFieldsSection from '@/components/CustomFieldsSection.vue';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
import EntityTimeline from '@/components/EntityTimeline.vue';
import { formatMoney } from '@/lib/money';
import type {
@@ -167,6 +168,14 @@ async function load() {
} else {
const firstOpen = stages.value.find((s) => !s.isWon && !s.isLost);
stageId.value = firstOpen?.id ?? stages.value[0]?.id ?? '';
if (typeof route.query.personId === 'string' && route.query.personId) {
personId.value = route.query.personId;
const person = people.value.find((p) => p.id === personId.value);
if (person?.company?.id) companyId.value = person.company.id;
}
if (typeof route.query.companyId === 'string' && route.query.companyId) {
companyId.value = route.query.companyId;
}
}
} catch {
error.value = 'بارگذاری فرم معامله ناموفق بود.';
@@ -310,7 +319,7 @@ onMounted(load);
</div>
<div class="field">
<label for="expectedCloseAt">تاریخ بسته‌شدن مورد انتظار</label>
<input id="expectedCloseAt" v-model="expectedCloseAt" type="date" />
<JalaliDateInput id="expectedCloseAt" v-model="expectedCloseAt" />
</div>
<div class="field wide">
<label for="tags">برچسب‌ها (با کاما جدا کنید)</label>
+7 -5
View File
@@ -4,6 +4,8 @@ import { RouterLink, useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { api } from '@/lib/api';
import { formatMoney as formatMoneyLib } from '@/lib/money';
import { formatLocalDateInput } from '@/lib/jalali';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
import type {
CompanyDto,
CreateInvoiceRequest,
@@ -66,7 +68,7 @@ const payAmount = ref('');
const payMethod = ref<PaymentMethod>('transfer');
const payNotes = ref('');
const payInstallmentId = ref('');
const payDate = ref(new Date().toISOString().slice(0, 10));
const payDate = ref(formatLocalDateInput(new Date()));
const statusLabel: Record<InvoiceStatus, string> = {
draft: 'پیش‌نویس',
@@ -137,7 +139,7 @@ function splitInstallments(count: number) {
d.setMonth(d.getMonth() + i + 1);
return {
key: `split-${i}-${Date.now()}`,
dueDate: d.toISOString().slice(0, 10),
dueDate: formatLocalDateInput(d),
amount: String(base + (i === count - 1 ? remainder : 0)),
};
});
@@ -414,7 +416,7 @@ onMounted(load);
</div>
<div class="field">
<label>سررسید</label>
<input v-model="dueDate" type="date" />
<JalaliDateInput v-model="dueDate" />
</div>
<div class="field">
<label>ارز</label>
@@ -512,7 +514,7 @@ onMounted(load);
:key="item.key"
class="installment-row"
>
<input v-model="item.dueDate" type="date" />
<JalaliDateInput v-model="item.dueDate" />
<input
v-model="item.amount"
type="number"
@@ -561,7 +563,7 @@ onMounted(load);
{{ label }}
</option>
</select>
<input v-model="payDate" type="date" />
<JalaliDateInput v-model="payDate" />
<select v-model="payInstallmentId">
<option value="">بدون اتصال به قسط</option>
<option
+11 -2
View File
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { api } from '@/lib/api';
import NoteAiSuggestionPanel from '@/components/NoteAiSuggestionPanel.vue';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
import type {
DealDto,
NoteAttachmentDto,
@@ -15,6 +16,7 @@ import type {
} from '@mento/shared';
const router = useRouter();
const route = useRoute();
const loading = ref(false);
const saving = ref(false);
const error = ref('');
@@ -72,6 +74,13 @@ async function load() {
notes.value = notesRes.data.items;
people.value = peopleRes.data.items;
deals.value = boardRes.data.stages.flatMap((s) => s.deals);
if (typeof route.query.personId === 'string' && route.query.personId) {
draftPersonId.value = route.query.personId;
showExtras.value = true;
}
if (typeof route.query.q === 'string' && route.query.q && !q.value) {
q.value = route.query.q;
}
} catch (e) {
if (axios.isAxiosError(e) && !e.response) {
error.value = 'اتصال به API برقرار نشد.';
@@ -372,7 +381,7 @@ onMounted(load);
</div>
<div class="field">
<label>یادآوری</label>
<input v-model="draftReminder" type="datetime-local" />
<JalaliDateInput v-model="draftReminder" mode="datetime" />
</div>
</div>
</form>
+5 -2
View File
@@ -165,7 +165,7 @@ async function submit() {
await api.patch(`/people/${id.value}`, payload());
await router.push({ name: 'contacts', query: { tab: undefined } });
await router.push({ name: 'person-detail', params: { id: id.value } });
}
@@ -241,7 +241,10 @@ onMounted(load);
</div>
<RouterLink class="btn btn-ghost" :to="{ name: 'contacts' }">بازگشت</RouterLink>
<RouterLink
class="btn btn-ghost"
:to="isNew ? { name: 'contacts' } : { name: 'person-detail', params: { id } }"
>بازگشت</RouterLink>
</header>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -50,6 +50,8 @@ async function save() {
title: title.value.trim(),
notes: notes.value.trim() || undefined,
boardId,
personId:
typeof route.query.personId === 'string' ? route.query.personId : undefined,
});
await router.replace({ name: 'project-card-detail', params: { id: data.id } });
} else {
+11 -3
View File
@@ -4,6 +4,7 @@ import { RouterLink, useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import { api } from '@/lib/api';
import { formatMoney as formatMoneyLib } from '@/lib/money';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
import type {
CompanyDto,
CreateQuoteRequest,
@@ -148,8 +149,15 @@ async function load() {
} else if (fromDealId) {
dealId.value = fromDealId;
title.value = 'پیش‌فاکتور';
} else if (!lines.value.length) {
lines.value = [newLine()];
} else {
if (typeof route.query.personId === 'string' && route.query.personId) {
personId.value = route.query.personId;
const person = people.value.find((p) => p.id === personId.value);
if (person?.company?.id) companyId.value = person.company.id;
}
if (!lines.value.length) {
lines.value = [newLine()];
}
}
} catch {
error.value = 'بارگذاری فرم پیش‌فاکتور ناموفق بود.';
@@ -257,7 +265,7 @@ onMounted(load);
</div>
<div class="field">
<label>اعتبار تا</label>
<input v-model="validUntil" type="date" />
<JalaliDateInput v-model="validUntil" />
</div>
<div class="field">
<label>ارز</label>
+8 -10
View File
@@ -3,6 +3,8 @@ import { onMounted, ref } from 'vue';
import axios from 'axios';
import { api } from '@/lib/api';
import { formatMoney } from '@/lib/money';
import { formatLocalDateInput } from '@/lib/jalali';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
import type { OrganizationDto, ReportsDashboardDto } from '@mento/shared';
const loading = ref(false);
@@ -23,8 +25,8 @@ const typeLabel: Record<string, string> = {
function defaultRange() {
const now = new Date();
const start = new Date(now.getFullYear(), now.getMonth(), 1);
from.value = start.toISOString().slice(0, 10);
to.value = now.toISOString().slice(0, 10);
from.value = formatLocalDateInput(start);
to.value = formatLocalDateInput(now);
}
function money(n: number) {
@@ -76,11 +78,11 @@ onMounted(() => {
<form class="toolbar" @submit.prevent="load">
<label>
از
<input v-model="from" type="date" />
<JalaliDateInput v-model="from" />
</label>
<label>
تا
<input v-model="to" type="date" />
<JalaliDateInput v-model="to" />
</label>
<button class="btn" type="submit" :disabled="loading">اعمال</button>
<a
@@ -242,12 +244,8 @@ h1 {
color: var(--muted);
}
.toolbar input {
border: 1px solid var(--line);
border-radius: 12px;
padding: 0.55rem 0.7rem;
font: inherit;
background: #fff;
.toolbar :deep(.trigger) {
min-width: 11.5rem;
}
.portal-link {
+8 -8
View File
@@ -6,6 +6,8 @@ import { api } from '@/lib/api';
import { useAuthStore } from '@/stores/auth';
import { canReassignRecords, canViewAllRecords } from '@/lib/roles';
import { formatMoney as formatMoneyLib } from '@/lib/money';
import { formatLocalDateInput } from '@/lib/jalali';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
import type {
CreateSalesGoalRequest,
OrgUserDto,
@@ -32,13 +34,11 @@ const showAllHint = computed(() => canViewAllRecords(auth.user?.role));
function monthBounds() {
const now = new Date();
const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
const end = new Date(
Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0),
);
const start = new Date(now.getFullYear(), now.getMonth(), 1);
const end = new Date(now.getFullYear(), now.getMonth() + 1, 0);
return {
start: start.toISOString().slice(0, 10),
end: end.toISOString().slice(0, 10),
start: formatLocalDateInput(start),
end: formatLocalDateInput(end),
};
}
@@ -222,11 +222,11 @@ onMounted(() => {
</div>
<div class="field">
<label>از تاریخ</label>
<input v-model="periodStart" type="date" required />
<JalaliDateInput v-model="periodStart" required />
</div>
<div class="field">
<label>تا تاریخ</label>
<input v-model="periodEnd" type="date" required />
<JalaliDateInput v-model="periodEnd" required />
</div>
<div class="field wide">
<label>یادداشت</label>
+5
View File
@@ -150,6 +150,11 @@ async function load() {
assigneeId.value = auth.user?.id ?? '';
csatScore.value = null;
csatComment.value = '';
if (typeof route.query.personId === 'string' && route.query.personId) {
personId.value = route.query.personId;
const person = people.value.find((p) => p.id === personId.value);
if (person?.company?.id) companyId.value = person.company.id;
}
void loadFaqHints();
}
} catch {