feat(routines): let each person create a routine for themselves

Open the new-routine action to every member, while keeping assignment to other people and teams limited to managers.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-04 19:34:36 +03:30
co-authored by Cursor
parent 5b18749005
commit 78a7d6a68a
6 changed files with 97 additions and 23 deletions
@@ -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 { useRoute, useRouter } from 'vue-router';
import type { ActivityPriority, RoutineDto, RoutineEntityLink, SaveRoutineRequest } from '@mento/shared';
import { ROUTINE_ENTITY_LINK_LABELS, ROUTINE_WEEKDAY_LABELS } from '@mento/shared';
@@ -18,10 +18,15 @@ import {
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 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);
@@ -60,6 +65,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 targetOptions = [
{ value: 'user' as const, label: 'یک کاربر' },
@@ -111,10 +118,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, 'روتین پیدا نشد.');
@@ -123,6 +134,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 ?? []);
@@ -169,6 +197,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 'تیم را انتخاب کنید.';
@@ -220,7 +253,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 {
@@ -234,10 +267,11 @@ async function save() {
<UiPageHeader
:title="isEdit ? 'ویرایش روتین' : 'روتین جدید'"
lead="کاری که باید به‌صورت منظم انجام شود؛ هر روزِ تکرار، یک کار برای مسئول ساخته می‌شود."
back-to="/routines/manage"
:back-to="backTo"
/>
<div v-if="loading" class="state">در حال بارگذاری…</div>
<p v-else-if="foreign" class="error">{{ error }}</p>
<form v-else class="grid" @submit.prevent="save">
<div class="col">
@@ -268,7 +302,8 @@ async function save() {
<UiCard>
<h3 class="sec-title">مسئول</h3>
<div class="fields">
<p v-if="!isManager" class="self-note">این روتین فقط برای خودتان ساخته می‌شود.</p>
<div v-else class="fields">
<UiSegmented v-model="form.target" :options="targetOptions" />
<UiField v-if="form.target === 'user'" label="کاربر" required>
<UiSelect v-model="form.assigneeId">
@@ -508,6 +543,13 @@ async function save() {
gap: var(--sp-2);
}
.self-note {
margin: 0;
font-size: var(--fs-sm);
color: var(--muted);
line-height: 1.7;
}
.error {
margin: 0;
}