feat(activities): let a group show and limit its members
Empty membership stays open to the whole workspace; picking people restricts who can see that activity group. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -170,7 +170,8 @@ const board = computed<string>(() => {
|
||||
});
|
||||
const currentBoard = computed(() => boards.value.find((b) => b.id === board.value) ?? null);
|
||||
const boardMenuOpen = ref(false);
|
||||
const boardModal = ref<{ mode: 'create' | 'edit'; name: string } | null>(null);
|
||||
const boardModal = ref<{ mode: 'create' | 'edit'; name: string; memberIds: string[] } | null>(null);
|
||||
const boardMemberQuery = ref('');
|
||||
const boardSaving = ref(false);
|
||||
const boardDeleteOpen = ref(false);
|
||||
|
||||
@@ -410,13 +411,76 @@ function setBoard(next: string) {
|
||||
});
|
||||
}
|
||||
|
||||
const activeBoardUsers = computed(() => users.value.filter((user) => user.isActive !== false));
|
||||
|
||||
const boardMembersOpenToAll = computed(() => {
|
||||
const ids = activeBoardUsers.value.map((user) => user.id);
|
||||
const selected = boardModal.value?.memberIds ?? [];
|
||||
return ids.length > 0 && ids.every((id) => selected.includes(id));
|
||||
});
|
||||
|
||||
const filteredBoardUsers = computed(() => {
|
||||
const query = boardMemberQuery.value.trim().replace(/ي/g, 'ی').replace(/ك/g, 'ک');
|
||||
const selected = new Set(boardModal.value?.memberIds ?? []);
|
||||
const list = activeBoardUsers.value.filter((user) => {
|
||||
if (!query) return true;
|
||||
return user.fullName.replace(/ي/g, 'ی').replace(/ك/g, 'ک').includes(query);
|
||||
});
|
||||
return list.slice().sort((a, b) => Number(selected.has(b.id)) - Number(selected.has(a.id)));
|
||||
});
|
||||
|
||||
function showExistingBoardMembers() {
|
||||
const modal = boardModal.value;
|
||||
if (!modal || modal.mode !== 'edit') return;
|
||||
const saved = currentBoard.value?.memberIds ?? [];
|
||||
modal.memberIds = saved.length
|
||||
? [...saved]
|
||||
: activeBoardUsers.value.map((user) => user.id);
|
||||
}
|
||||
|
||||
async function ensureBoardUsers() {
|
||||
if (!users.value.length) {
|
||||
try {
|
||||
const { data: rows } = await api.get<OrgUserDto[]>('/users');
|
||||
users.value = rows.filter((user) => user.isActive !== false);
|
||||
} catch {
|
||||
users.value = [];
|
||||
}
|
||||
}
|
||||
showExistingBoardMembers();
|
||||
}
|
||||
|
||||
function memberIdsToSave(selected: string[]) {
|
||||
const all = activeBoardUsers.value.map((user) => user.id);
|
||||
if (all.length && selected.length === all.length && all.every((id) => selected.includes(id))) {
|
||||
return [];
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
function openCreateBoard() {
|
||||
boardModal.value = { mode: 'create', name: '' };
|
||||
boardMemberQuery.value = '';
|
||||
boardModal.value = { mode: 'create', name: '', memberIds: [] };
|
||||
void ensureBoardUsers();
|
||||
}
|
||||
|
||||
function openEditBoard() {
|
||||
if (!currentBoard.value) return;
|
||||
boardModal.value = { mode: 'edit', name: currentBoard.value.name };
|
||||
boardMemberQuery.value = '';
|
||||
boardModal.value = {
|
||||
mode: 'edit',
|
||||
name: currentBoard.value.name,
|
||||
memberIds: [...(currentBoard.value.memberIds ?? [])],
|
||||
};
|
||||
void ensureBoardUsers();
|
||||
}
|
||||
|
||||
function toggleBoardMember(userId: string) {
|
||||
const modal = boardModal.value;
|
||||
if (!modal) return;
|
||||
modal.memberIds = modal.memberIds.includes(userId)
|
||||
? modal.memberIds.filter((id) => id !== userId)
|
||||
: [...modal.memberIds, userId];
|
||||
}
|
||||
|
||||
async function saveBoard() {
|
||||
@@ -427,12 +491,18 @@ async function saveBoard() {
|
||||
error.value = '';
|
||||
try {
|
||||
if (modal.mode === 'create') {
|
||||
const { data: created } = await api.post<ActivityBoardDto>('/activities/boards', { name });
|
||||
const { data: created } = await api.post<ActivityBoardDto>('/activities/boards', {
|
||||
name,
|
||||
memberIds: memberIdsToSave(modal.memberIds),
|
||||
});
|
||||
await loadBoards();
|
||||
boardModal.value = null;
|
||||
setBoard(created.id);
|
||||
} else if (currentBoard.value) {
|
||||
await api.patch(`/activities/boards/${currentBoard.value.id}`, { name });
|
||||
await api.patch(`/activities/boards/${currentBoard.value.id}`, {
|
||||
name,
|
||||
memberIds: memberIdsToSave(modal.memberIds),
|
||||
});
|
||||
await loadBoards();
|
||||
boardModal.value = null;
|
||||
}
|
||||
@@ -1620,6 +1690,34 @@ onUnmounted(() => {
|
||||
<p v-if="boardModal.mode === 'create'" class="board-form-hint">
|
||||
گروه جدید با ستونهای پیشفرض ساخته میشود و بعد میتوانید ستونهایش را تغییر دهید.
|
||||
</p>
|
||||
<label class="board-form-label" for="activity-board-member-search">اعضا</label>
|
||||
<UiInput
|
||||
id="activity-board-member-search"
|
||||
v-model="boardMemberQuery"
|
||||
placeholder="جستجوی نام عضو…"
|
||||
/>
|
||||
<p class="board-form-hint">
|
||||
{{
|
||||
boardMembersOpenToAll
|
||||
? `اعضای فعلی همین ${toFaDigits(boardModal.memberIds.length)} نفر هستند و گروه برای همهٔ محیط باز است. با برداشتن تیک، فقط افراد انتخابشده آن را میبینند.`
|
||||
: boardModal.memberIds.length
|
||||
? `${toFaDigits(boardModal.memberIds.length)} نفر انتخاب شده. فقط همین افراد این گروه را میبینند.`
|
||||
: 'اگر کسی را انتخاب نکنید، این گروه برای همهٔ اعضای محیط دیده میشود.'
|
||||
}}
|
||||
</p>
|
||||
<ul class="board-members">
|
||||
<li v-if="!filteredBoardUsers.length" class="board-form-hint">عضوی پیدا نشد.</li>
|
||||
<li v-for="user in filteredBoardUsers" :key="user.id">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
:checked="boardModal.memberIds.includes(user.id)"
|
||||
@change="toggleBoardMember(user.id)"
|
||||
/>
|
||||
<span>{{ user.fullName }}</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<UiButton
|
||||
v-if="boardModal.mode === 'edit' && currentBoard && !currentBoard.isDefault"
|
||||
variant="ghost"
|
||||
@@ -1890,6 +1988,31 @@ h1 {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.board-members {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0.25rem;
|
||||
max-height: 14rem;
|
||||
overflow: auto;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: var(--radius);
|
||||
display: grid;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
.board-members label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.35rem 0.45rem;
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.board-members label:hover {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.board-delete {
|
||||
justify-self: start;
|
||||
color: var(--danger);
|
||||
|
||||
Reference in New Issue
Block a user