feat(activities): remove an assignee from the share list

A team, role, or person can be taken off the activity from the progress chips, and ungrouped people use the same chip as grouped members.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-10 18:43:02 +03:30
co-authored by Cursor
parent 4c8604d346
commit 6df198321a
2 changed files with 104 additions and 37 deletions
+12 -1
View File
@@ -168,13 +168,24 @@ function commit(ids: string[]) {
function onUpdate(value: unknown) { function onUpdate(value: unknown) {
const tokens = Array.isArray(value) ? value.map(String) : []; const tokens = Array.isArray(value) ? value.map(String) : [];
const tokenSet = new Set(tokens);
const removedMemberIds = new Set<string>();
for (const token of selectValue.value) {
if (tokenSet.has(token)) continue;
const dropped = catalogByToken.value.get(token);
if (!dropped) continue;
for (const id of dropped.userIds) removedMemberIds.add(id);
}
const ids = new Set<string>(); const ids = new Set<string>();
const pins = new Set<string>(); const pins = new Set<string>();
for (const token of tokens) { for (const token of tokens) {
const group = catalogByToken.value.get(token); const group = catalogByToken.value.get(token);
if (group) { if (group) {
pins.add(token); pins.add(token);
for (const id of group.userIds) ids.add(id); for (const id of group.userIds) {
if (removedMemberIds.has(id)) continue;
ids.add(id);
}
continue; continue;
} }
if (!token.startsWith('role:') && !token.startsWith('team:')) ids.add(token); if (!token.startsWith('role:') && !token.startsWith('team:')) ids.add(token);
+77 -21
View File
@@ -323,6 +323,18 @@ function shareExpanded(group: ShareGroup) {
function toggleShare(group: ShareGroup) { function toggleShare(group: ShareGroup) {
shareOpen.value = { ...shareOpen.value, [group.key]: !shareExpanded(group) }; shareOpen.value = { ...shareOpen.value, [group.key]: !shareExpanded(group) };
} }
function removeAssignees(ids: string[]) {
if (!canReassign.value || saving.value) return;
const drop = new Set(ids);
const next = assignUserIds.value.filter((id) => !drop.has(id));
if (!next.length) {
error.value = 'حداقل یک مسئول لازم است.';
return;
}
assignUserIds.value = next;
void reassign();
}
const myShareDone = computed(() => { const myShareDone = computed(() => {
const mine = auth.user?.id; const mine = auth.user?.id;
return Boolean(mine && completedIds.value.has(mine)); return Boolean(mine && completedIds.value.has(mine));
@@ -808,6 +820,7 @@ onMounted(() => {
{{ toFaDigits(shareDoneTotal) }} از {{ toFaDigits(assigneePeople.length) }} انجام شده {{ toFaDigits(shareDoneTotal) }} از {{ toFaDigits(assigneePeople.length) }} انجام شده
</p> </p>
<div v-for="group in shareGroups" :key="group.key" class="share-group"> <div v-for="group in shareGroups" :key="group.key" class="share-group">
<div class="share-group-bar">
<button type="button" class="share-group-head" @click="toggleShare(group)"> <button type="button" class="share-group-head" @click="toggleShare(group)">
<UiIcon <UiIcon
name="chevron" name="chevron"
@@ -823,6 +836,17 @@ onMounted(() => {
{{ toFaDigits(shareDoneCount(group.members)) }} از {{ toFaDigits(group.members.length) }} {{ toFaDigits(shareDoneCount(group.members)) }} از {{ toFaDigits(group.members.length) }}
</em> </em>
</button> </button>
<button
v-if="canReassign"
type="button"
class="share-remove"
:disabled="saving"
:aria-label="`برداشتن ${group.name}`"
@click="removeAssignees(group.members.map((person) => person.id))"
>
×
</button>
</div>
<ul v-if="shareExpanded(group)" class="share-members"> <ul v-if="shareExpanded(group)" class="share-members">
<li <li
v-for="person in group.members" v-for="person in group.members"
@@ -831,11 +855,21 @@ onMounted(() => {
> >
<span>{{ person.fullName }}</span> <span>{{ person.fullName }}</span>
<em>{{ completedIds.has(person.id) ? 'انجام شد' : 'مانده' }}</em> <em>{{ completedIds.has(person.id) ? 'انجام شد' : 'مانده' }}</em>
<button
v-if="canReassign"
type="button"
class="share-remove"
:disabled="saving"
:aria-label="`برداشتن ${person.fullName}`"
@click="removeAssignees([person.id])"
>
×
</button>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<ul v-else-if="needsEveryone && !isDone" class="share-list"> <ul v-else-if="needsEveryone && !isDone" class="share-members loose">
<li <li
v-for="person in assigneePeople" v-for="person in assigneePeople"
:key="person.id" :key="person.id"
@@ -843,6 +877,16 @@ onMounted(() => {
> >
<span>{{ person.fullName }}</span> <span>{{ person.fullName }}</span>
<em>{{ completedIds.has(person.id) ? 'انجام شد' : 'مانده' }}</em> <em>{{ completedIds.has(person.id) ? 'انجام شد' : 'مانده' }}</em>
<button
v-if="canReassign"
type="button"
class="share-remove"
:disabled="saving"
:aria-label="`برداشتن ${person.fullName}`"
@click="removeAssignees([person.id])"
>
×
</button>
</li> </li>
</ul> </ul>
<button <button
@@ -1452,31 +1496,19 @@ dd {
color: #0f766e; color: #0f766e;
} }
.share-list { .share-group-bar {
list-style: none;
margin: 0.55rem 0 0;
padding: 0;
width: 100%;
display: flex; display: flex;
flex-direction: column; align-items: center;
gap: 0.25rem;
} }
.share-list li { .share-group-bar .share-group-head {
display: flex; width: auto;
justify-content: space-between; flex: 1;
gap: 0.75rem; min-width: 0;
font-size: 0.86rem;
color: #334155;
} }
.share-list em { .share-group-bar > .share-remove {
font-style: normal; margin-inline-end: 0.45rem;
color: #94a3b8;
}
.share-list li.done em {
color: #0f766e;
} }
.share-board { .share-board {
@@ -1561,6 +1593,13 @@ dd {
gap: 0.35rem; gap: 0.35rem;
} }
.share-members.loose {
width: 100%;
margin-top: 0.55rem;
padding: 0;
max-height: none;
}
.share-members li { .share-members li {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
@@ -1600,6 +1639,23 @@ dd {
color: #0f766e; color: #0f766e;
} }
.share-remove {
flex: 0 0 auto;
border: 0;
background: transparent;
color: #94a3b8;
font: inherit;
font-size: 0.95rem;
line-height: 1;
padding: 0 0.1rem;
cursor: pointer;
}
.share-remove:disabled {
cursor: default;
opacity: 0.45;
}
.share-force { .share-force {
margin-top: 0.45rem; margin-top: 0.45rem;
border: 0; border: 0;