merge: bring latest origin/main into dev (design local, logic main)
Keep the local Ant screens and icon pack. Take treasury parties, routine self-service, Tahesab auto-sync, public lead registration, and contact merge from main.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, inject, onMounted, reactive, ref } from 'vue';
|
||||
import { computed, inject, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { RouterLink, useRoute, useRouter } from 'vue-router';
|
||||
import {
|
||||
Alert as AAlert,
|
||||
@@ -30,13 +30,18 @@ import RecurrencePicker, { type RecurrenceValue } from '@/components/routines/Re
|
||||
import { api } from '@/lib/api';
|
||||
import { toFaDigits } from '@/lib/jalali';
|
||||
import { PRIORITY_LABELS, ROUTINES_CTX, apiError, formatDayNumeric, todayKey } from '@/lib/routines';
|
||||
import { useAuthStore } from '@/stores/auth';
|
||||
|
||||
const { Title: ATypographyTitle, Text: ATypographyText, Paragraph: ATypographyParagraph } =
|
||||
Typography;
|
||||
|
||||
const shell = inject(ROUTINES_CTX)!;
|
||||
const auth = useAuthStore();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const isManager = computed(() => !!shell.context.value?.isManager);
|
||||
const selfId = computed(() => auth.user?.id ?? '');
|
||||
const backTo = computed(() => (isManager.value ? '/routines/manage' : '/routines/list'));
|
||||
|
||||
const id = computed(() => (typeof route.params.id === 'string' ? route.params.id : null));
|
||||
const isEdit = computed(() => !!id.value);
|
||||
@@ -76,6 +81,8 @@ const recurrence = ref<RecurrenceValue>({
|
||||
const loading = ref(false);
|
||||
const saving = ref(false);
|
||||
const error = ref('');
|
||||
const loaded = ref<RoutineDto | null>(null);
|
||||
const foreign = ref(false);
|
||||
|
||||
const priorities = Object.entries(PRIORITY_LABELS) as Array<[ActivityPriority, string]>;
|
||||
const entityLinks = Object.entries(ROUTINE_ENTITY_LINK_LABELS) as Array<[RoutineEntityLink, string]>;
|
||||
@@ -140,10 +147,14 @@ function fill(r: RoutineDto) {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (!id.value) return;
|
||||
if (!id.value) {
|
||||
if (selfId.value) form.assigneeId = selfId.value;
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const { data } = await api.get<RoutineDto>(`/routines/${id.value}`);
|
||||
loaded.value = data;
|
||||
fill(data);
|
||||
} catch (e) {
|
||||
error.value = apiError(e, 'روتین پیدا نشد.');
|
||||
@@ -152,6 +163,23 @@ onMounted(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
[() => shell.context.value, loaded, selfId],
|
||||
() => {
|
||||
const ctx = shell.context.value;
|
||||
const data = loaded.value;
|
||||
if (!ctx || !data || ctx.isManager) {
|
||||
foreign.value = false;
|
||||
return;
|
||||
}
|
||||
foreign.value = data.createdById !== selfId.value;
|
||||
if (foreign.value) {
|
||||
error.value = 'این روتین را مدیر تعریف کرده و فقط خودش میتواند تغییرش دهد.';
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
const users = computed(() => shell.context.value?.users ?? []);
|
||||
const teams = computed(() => shell.context.value?.teams ?? []);
|
||||
const categories = computed(() => shell.context.value?.categories ?? []);
|
||||
@@ -198,6 +226,11 @@ const summary = computed(() => {
|
||||
});
|
||||
|
||||
function validate(): string | null {
|
||||
if (!isManager.value) {
|
||||
form.target = 'user';
|
||||
form.assigneeId = selfId.value;
|
||||
form.teamId = '';
|
||||
}
|
||||
if (form.title.trim().length < 2) return 'عنوان روتین را وارد کنید.';
|
||||
if (form.target === 'user' && !form.assigneeId) return 'مسئول روتین را انتخاب کنید.';
|
||||
if (form.target === 'team' && !form.teamId) return 'تیم را انتخاب کنید.';
|
||||
@@ -249,7 +282,7 @@ async function save() {
|
||||
if (isEdit.value) await api.patch(`/routines/${id.value}`, body);
|
||||
else await api.post('/routines', body);
|
||||
shell.notifyChanged();
|
||||
void router.push('/routines/manage');
|
||||
void router.push(backTo.value);
|
||||
} catch (e) {
|
||||
error.value = apiError(e, 'ذخیره روتین ممکن نشد.');
|
||||
} finally {
|
||||
@@ -263,7 +296,7 @@ async function save() {
|
||||
<a-flex vertical :gap="4">
|
||||
<a-breadcrumb>
|
||||
<a-breadcrumb-item>
|
||||
<RouterLink to="/routines/manage">مدیریت روتینها</RouterLink>
|
||||
<RouterLink :to="backTo">{{ isManager ? 'مدیریت روتینها' : 'روتینهای من' }}</RouterLink>
|
||||
</a-breadcrumb-item>
|
||||
<a-breadcrumb-item>{{ pageTitle }}</a-breadcrumb-item>
|
||||
</a-breadcrumb>
|
||||
@@ -275,7 +308,9 @@ async function save() {
|
||||
</a-typography-text>
|
||||
</a-flex>
|
||||
|
||||
<CrmSkeleton :loading="loading">
|
||||
<a-alert v-if="foreign" type="error" show-icon :message="error" />
|
||||
|
||||
<CrmSkeleton v-if="!foreign" :loading="loading">
|
||||
<CrmSkeletonCard :rows="8">
|
||||
<a-form layout="vertical" @finish="save">
|
||||
<a-row :gutter="[16, 16]">
|
||||
@@ -323,6 +358,10 @@ async function save() {
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" title="مسئول">
|
||||
<a-typography-text v-if="!isManager" type="secondary">
|
||||
این روتین فقط برای خودتان ساخته میشود.
|
||||
</a-typography-text>
|
||||
<template v-else>
|
||||
<a-form-item>
|
||||
<a-radio-group v-model:value="form.target" button-style="solid">
|
||||
<a-radio-button value="user">یک کاربر</a-radio-button>
|
||||
@@ -354,6 +393,7 @@ async function save() {
|
||||
style="width: 100%"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</a-card>
|
||||
|
||||
<a-card :bordered="false" title="زمانبندی و تکرار">
|
||||
@@ -484,7 +524,7 @@ async function save() {
|
||||
<a-button type="primary" html-type="submit" block :loading="saving">
|
||||
{{ isEdit ? 'ذخیره تغییرات' : 'ایجاد روتین' }}
|
||||
</a-button>
|
||||
<a-button block :disabled="saving" @click="router.push('/routines/manage')">
|
||||
<a-button block :disabled="saving" @click="router.push(backTo)">
|
||||
انصراف
|
||||
</a-button>
|
||||
</a-space>
|
||||
|
||||
Reference in New Issue
Block a user