feat(activities): let creators lock edit and delete to themselves

Add editPolicy (default creator-only), enforce it in the API, surface clear permission messages, and drop the redundant edit menu now that detail is editable.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-11 15:56:36 +03:30
co-authored by Cursor
parent ec9a7cf9c9
commit 1a05eee46d
10 changed files with 279 additions and 57 deletions
@@ -29,6 +29,7 @@ import {
startOfLocalDay,
} from '@/lib/jalali';
import { useAuthStore } from '@/stores/auth';
import { canMutateActivity } from '@/lib/activity-access';
import ActivityCommentSheet from '@/components/activities/ActivityCommentSheet.vue';
import JalaliDateInput from '@/components/JalaliDateInput.vue';
@@ -200,6 +201,7 @@ function clearNoteMedia() {
const isClosed = computed(
() => activity.value?.status === 'done' || activity.value?.status === 'canceled',
);
const canMutate = computed(() => canMutateActivity(activity.value, auth.user));
const needsEveryone = computed(
() => activity.value?.completionMode === 'all' && (activity.value.assignees?.length ?? 0) > 1,
);
@@ -407,6 +409,10 @@ async function runAction() {
error.value = '';
try {
if (isClosed.value) {
if (!canMutate.value) {
error.value = 'شما مجوز ویرایش این فعالیت را ندارید.';
return;
}
await api.patch(`/activities/${id}`, { status: 'open' });
await load(id);
emit('changed');
@@ -525,7 +531,13 @@ function openFull() {
<a-divider :style="{ margin: '16px 0 12px' }" />
<a-flex justify="space-between" align="center" :style="{ marginBottom: '8px' }">
<a-typography-text strong>یادداشت</a-typography-text>
<a-button type="primary" shape="circle" aria-label="یادداشت جدید" @click="commentOpen = true">
<a-button
type="primary"
shape="circle"
aria-label="یادداشت جدید"
:disabled="!canMutate"
@click="commentOpen = true"
>
<template #icon><PlusOutlined /></template>
</a-button>
</a-flex>
@@ -630,12 +642,16 @@ function openFull() {
<a-flex align="center" :gap="8">
<a-select
v-model:value="postponeKey"
:disabled="saving || loading || isClosed"
:disabled="saving || loading || isClosed || !canMutate"
:options="postponeChoices.map((item) => ({ value: item.key, label: item.label }))"
:dropdown-style="{ zIndex: 1200 }"
style="flex: 1"
/>
<a-button type="primary" :disabled="saving || loading || isClosed || !postponeReady" @click="applyPostpone">
<a-button
type="primary"
:disabled="saving || loading || isClosed || !canMutate || !postponeReady"
@click="applyPostpone"
>
تعویق
</a-button>
</a-flex>
@@ -644,12 +660,17 @@ function openFull() {
v-model="customDue"
mode="datetime"
placeholder="تاریخ و ساعت"
:disabled="!canMutate"
/>
</a-flex>
<a-divider :style="{ margin: '16px 0 12px' }" />
<a-flex justify="space-between" align="center" wrap="wrap" :gap="8">
<a-button type="primary" :disabled="saving || loading" @click="runAction">
<a-button
type="primary"
:disabled="saving || loading || (isClosed && !canMutate)"
@click="runAction"
>
{{ actionLabel }}
</a-button>
<a-button type="link" @click="openFull">مشاهدهٔ کامل</a-button>