feat(activities): keep multi-assignee completion readable for large teams
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { Select as ASelect } from 'ant-design-vue';
|
import { Button as AButton, Select as ASelect } from 'ant-design-vue';
|
||||||
|
import { toFaDigits } from '@/lib/jalali';
|
||||||
|
|
||||||
type PickUser = { id: string; fullName: string };
|
type PickUser = { id: string; fullName: string };
|
||||||
|
|
||||||
@@ -13,8 +14,10 @@ const props = withDefaults(
|
|||||||
min?: number;
|
min?: number;
|
||||||
addLabel?: string;
|
addLabel?: string;
|
||||||
emptyLabel?: string;
|
emptyLabel?: string;
|
||||||
|
/** دکمهٔ انتخاب یکجای همهٔ اعضا و جمعکردن برچسبها وقتی تعداد زیاد است */
|
||||||
|
selectAll?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{ disabled: false, min: 0, addLabel: 'افزودن', emptyLabel: 'کسی انتخاب نشده' },
|
{ disabled: false, min: 0, addLabel: 'افزودن', emptyLabel: 'کسی انتخاب نشده', selectAll: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@@ -26,8 +29,25 @@ const options = computed(() =>
|
|||||||
props.users.map((u) => ({ value: u.id, label: u.fullName })),
|
props.users.map((u) => ({ value: u.id, label: u.fullName })),
|
||||||
);
|
);
|
||||||
|
|
||||||
function onUpdate(value: unknown) {
|
const allPicked = computed(
|
||||||
const ids = Array.isArray(value) ? value.map(String) : [];
|
() =>
|
||||||
|
props.users.length > 0 &&
|
||||||
|
props.users.every((user) => props.modelValue.includes(user.id)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const tagLimit = computed(() => {
|
||||||
|
if (!props.selectAll) return undefined;
|
||||||
|
if (allPicked.value && props.users.length > 2) return 0;
|
||||||
|
return 'responsive' as const;
|
||||||
|
});
|
||||||
|
|
||||||
|
function tagPlaceholder(omitted: unknown[]) {
|
||||||
|
if (allPicked.value) return `همهٔ اعضا (${toFaDigits(props.users.length)})`;
|
||||||
|
const extra = Array.isArray(omitted) ? omitted.length : 0;
|
||||||
|
return extra ? `${toFaDigits(extra)} نفر دیگر` : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function commit(ids: string[]) {
|
||||||
if (ids.length < props.min && props.modelValue.length >= props.min) {
|
if (ids.length < props.min && props.modelValue.length >= props.min) {
|
||||||
emit('update:modelValue', [...props.modelValue]);
|
emit('update:modelValue', [...props.modelValue]);
|
||||||
return;
|
return;
|
||||||
@@ -35,19 +55,54 @@ function onUpdate(value: unknown) {
|
|||||||
emit('update:modelValue', ids);
|
emit('update:modelValue', ids);
|
||||||
emit('change', ids);
|
emit('change', ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onUpdate(value: unknown) {
|
||||||
|
const ids = Array.isArray(value) ? value.map(String) : [];
|
||||||
|
commit(ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickAll() {
|
||||||
|
if (props.disabled || allPicked.value) return;
|
||||||
|
commit(props.users.map((user) => user.id));
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a-select
|
<div class="user-multi" :class="{ 'with-all': selectAll }">
|
||||||
mode="multiple"
|
<a-select
|
||||||
allow-clear
|
mode="multiple"
|
||||||
show-search
|
allow-clear
|
||||||
option-filter-prop="label"
|
show-search
|
||||||
:value="modelValue"
|
option-filter-prop="label"
|
||||||
:options="options"
|
:value="modelValue"
|
||||||
:disabled="disabled"
|
:options="options"
|
||||||
:placeholder="emptyLabel"
|
:disabled="disabled"
|
||||||
style="width: 100%"
|
:placeholder="emptyLabel"
|
||||||
@update:value="onUpdate"
|
:max-tag-count="tagLimit"
|
||||||
/>
|
:max-tag-placeholder="selectAll ? tagPlaceholder : undefined"
|
||||||
|
style="width: 100%"
|
||||||
|
@update:value="onUpdate"
|
||||||
|
/>
|
||||||
|
<a-button v-if="selectAll" :disabled="disabled || allPicked || !users.length" @click="pickAll">
|
||||||
|
همه
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.user-multi.with-all {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-multi.with-all :deep(.ant-select) {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-multi.with-all :deep(.ant-btn) {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type {
|
|||||||
ActivityDto,
|
ActivityDto,
|
||||||
ActivityPriority,
|
ActivityPriority,
|
||||||
OrgUserDto,
|
OrgUserDto,
|
||||||
|
TeamDto,
|
||||||
} from '@mento/shared';
|
} from '@mento/shared';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import { canAssignToOthers } from '@/lib/assign-access';
|
import { canAssignToOthers } from '@/lib/assign-access';
|
||||||
@@ -171,7 +172,9 @@ watch(
|
|||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
const users = ref<OrgUserDto[]>([]);
|
const users = ref<OrgUserDto[]>([]);
|
||||||
|
const teams = ref<TeamDto[]>([]);
|
||||||
const assignUserIds = ref<string[]>([]);
|
const assignUserIds = ref<string[]>([]);
|
||||||
|
const shareOpen = ref<Record<string, boolean>>({});
|
||||||
|
|
||||||
const assigneePeople = computed(() => {
|
const assigneePeople = computed(() => {
|
||||||
const a = activity.value;
|
const a = activity.value;
|
||||||
@@ -187,7 +190,110 @@ const needsEveryone = computed(
|
|||||||
() =>
|
() =>
|
||||||
activity.value?.completionMode === 'all' && assigneePeople.value.length > 1,
|
activity.value?.completionMode === 'all' && assigneePeople.value.length > 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
type SharePerson = { id: string; fullName: string };
|
||||||
|
type ShareGroup = {
|
||||||
|
key: string;
|
||||||
|
kind: 'team' | 'role' | 'rest';
|
||||||
|
name: string;
|
||||||
|
members: SharePerson[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const shareGroups = computed<ShareGroup[]>(() => {
|
||||||
|
const people = assigneePeople.value;
|
||||||
|
if (people.length < 2) return [];
|
||||||
|
const byId = new Map(people.map((person) => [person.id, person]));
|
||||||
|
const claimed = new Set<string>();
|
||||||
|
const groups: ShareGroup[] = [];
|
||||||
|
|
||||||
|
const teamCandidates = teams.value
|
||||||
|
.map((team) => {
|
||||||
|
const members = [
|
||||||
|
...new Map(
|
||||||
|
team.members
|
||||||
|
.map((member) => byId.get(member.userId))
|
||||||
|
.filter((person): person is SharePerson => Boolean(person))
|
||||||
|
.map((person) => [person.id, person]),
|
||||||
|
).values(),
|
||||||
|
].sort((a, b) => a.fullName.localeCompare(b.fullName, 'fa'));
|
||||||
|
return { id: team.id, name: team.name, members };
|
||||||
|
})
|
||||||
|
.filter((team) => team.members.length >= 2)
|
||||||
|
.sort(
|
||||||
|
(a, b) => b.members.length - a.members.length || a.name.localeCompare(b.name, 'fa'),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const team of teamCandidates) {
|
||||||
|
const free = team.members.filter((member) => !claimed.has(member.id));
|
||||||
|
if (free.length < 2) continue;
|
||||||
|
for (const member of free) claimed.add(member.id);
|
||||||
|
groups.push({ key: `team:${team.id}`, kind: 'team', name: team.name, members: free });
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleMap = new Map<string, { name: string; members: SharePerson[] }>();
|
||||||
|
for (const user of users.value) {
|
||||||
|
const person = byId.get(user.id);
|
||||||
|
if (!person || claimed.has(person.id) || !user.orgRoleId || !user.orgRoleName) continue;
|
||||||
|
const row = roleMap.get(user.orgRoleId) ?? { name: user.orgRoleName, members: [] };
|
||||||
|
row.members.push(person);
|
||||||
|
roleMap.set(user.orgRoleId, row);
|
||||||
|
}
|
||||||
|
const roles = [...roleMap.entries()]
|
||||||
|
.map(([id, role]) => ({
|
||||||
|
id,
|
||||||
|
name: role.name,
|
||||||
|
members: [...role.members].sort((a, b) => a.fullName.localeCompare(b.fullName, 'fa')),
|
||||||
|
}))
|
||||||
|
.filter((role) => role.members.length >= 2)
|
||||||
|
.sort((a, b) => b.members.length - a.members.length || a.name.localeCompare(b.name, 'fa'));
|
||||||
|
for (const role of roles) {
|
||||||
|
for (const member of role.members) claimed.add(member.id);
|
||||||
|
groups.push({ key: `role:${role.id}`, kind: 'role', name: role.name, members: role.members });
|
||||||
|
}
|
||||||
|
|
||||||
|
const rest = people
|
||||||
|
.filter((person) => !claimed.has(person.id))
|
||||||
|
.sort((a, b) => a.fullName.localeCompare(b.fullName, 'fa'));
|
||||||
|
if (rest.length) {
|
||||||
|
groups.push({
|
||||||
|
key: 'rest',
|
||||||
|
kind: 'rest',
|
||||||
|
name: groups.length ? 'سایر مسئولها' : 'مسئولها',
|
||||||
|
members: rest,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
});
|
||||||
|
|
||||||
const completedIds = computed(() => new Set(activity.value?.completedByIds ?? []));
|
const completedIds = computed(() => new Set(activity.value?.completedByIds ?? []));
|
||||||
|
|
||||||
|
const shareUsesGroups = computed(
|
||||||
|
() =>
|
||||||
|
shareGroups.value.some((group) => group.kind !== 'rest' || group.members.length > 6),
|
||||||
|
);
|
||||||
|
|
||||||
|
const shareDoneTotal = computed(
|
||||||
|
() => assigneePeople.value.filter((person) => completedIds.value.has(person.id)).length,
|
||||||
|
);
|
||||||
|
|
||||||
|
function shareDoneCount(members: SharePerson[]) {
|
||||||
|
return members.filter((person) => completedIds.value.has(person.id)).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shareKindLabel(group: ShareGroup) {
|
||||||
|
if (group.kind === 'team' && !group.name.startsWith('تیم')) return 'تیم';
|
||||||
|
if (group.kind === 'role' && !group.name.startsWith('نقش')) return 'نقش';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function shareExpanded(group: ShareGroup) {
|
||||||
|
if (group.key in shareOpen.value) return shareOpen.value[group.key];
|
||||||
|
return group.kind === 'rest' && group.members.length <= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleShare(group: ShareGroup) {
|
||||||
|
shareOpen.value = { ...shareOpen.value, [group.key]: !shareExpanded(group) };
|
||||||
|
}
|
||||||
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));
|
||||||
@@ -278,6 +384,7 @@ async function load() {
|
|||||||
try {
|
try {
|
||||||
const { data } = await api.get<ActivityDto>(`/activities/${id.value}`);
|
const { data } = await api.get<ActivityDto>(`/activities/${id.value}`);
|
||||||
activity.value = data;
|
activity.value = data;
|
||||||
|
shareOpen.value = {};
|
||||||
await loadActivityImages(data);
|
await loadActivityImages(data);
|
||||||
assignUserIds.value = data.assignees?.length
|
assignUserIds.value = data.assignees?.length
|
||||||
? data.assignees.map((u) => u.id)
|
? data.assignees.map((u) => u.id)
|
||||||
@@ -324,12 +431,16 @@ async function loadActivityImages(row: ActivityDto) {
|
|||||||
onUnmounted(clearImageUrls);
|
onUnmounted(clearImageUrls);
|
||||||
|
|
||||||
async function loadUsers() {
|
async function loadUsers() {
|
||||||
if (!canReassign.value) return;
|
|
||||||
try {
|
try {
|
||||||
const { data } = await api.get<OrgUserDto[]>('/users');
|
const [usersRes, teamsRes] = await Promise.all([
|
||||||
users.value = data;
|
api.get<OrgUserDto[]>('/users').catch(() => ({ data: [] as OrgUserDto[] })),
|
||||||
|
api.get<TeamDto[]>('/teams').catch(() => ({ data: [] as TeamDto[] })),
|
||||||
|
]);
|
||||||
|
users.value = usersRes.data;
|
||||||
|
teams.value = teamsRes.data;
|
||||||
} catch {
|
} catch {
|
||||||
users.value = [];
|
users.value = [];
|
||||||
|
teams.value = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -637,6 +748,7 @@ onMounted(() => {
|
|||||||
:users="users"
|
:users="users"
|
||||||
:disabled="saving"
|
:disabled="saving"
|
||||||
:min="1"
|
:min="1"
|
||||||
|
select-all
|
||||||
@change="reassign"
|
@change="reassign"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -660,7 +772,39 @@ onMounted(() => {
|
|||||||
همه باید انجام دهند
|
همه باید انجام دهند
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ul v-if="needsEveryone && !isDone" class="share-list">
|
<div v-if="needsEveryone && !isDone && shareUsesGroups" class="share-board">
|
||||||
|
<p class="share-progress">
|
||||||
|
{{ toFaDigits(shareDoneTotal) }} از {{ toFaDigits(assigneePeople.length) }} انجام شده
|
||||||
|
</p>
|
||||||
|
<div v-for="group in shareGroups" :key="group.key" class="share-group">
|
||||||
|
<button type="button" class="share-group-head" @click="toggleShare(group)">
|
||||||
|
<UiIcon
|
||||||
|
name="chevron"
|
||||||
|
:size="14"
|
||||||
|
class="share-caret"
|
||||||
|
:class="{ open: shareExpanded(group) }"
|
||||||
|
/>
|
||||||
|
<span class="share-group-name">
|
||||||
|
<small v-if="shareKindLabel(group)">{{ shareKindLabel(group) }}</small>
|
||||||
|
{{ group.name }}
|
||||||
|
</span>
|
||||||
|
<em :class="{ done: shareDoneCount(group.members) === group.members.length }">
|
||||||
|
{{ toFaDigits(shareDoneCount(group.members)) }} از {{ toFaDigits(group.members.length) }}
|
||||||
|
</em>
|
||||||
|
</button>
|
||||||
|
<ul v-if="shareExpanded(group)" class="share-members">
|
||||||
|
<li
|
||||||
|
v-for="person in group.members"
|
||||||
|
:key="person.id"
|
||||||
|
:class="{ done: completedIds.has(person.id) }"
|
||||||
|
>
|
||||||
|
<span>{{ person.fullName }}</span>
|
||||||
|
<em>{{ completedIds.has(person.id) ? 'انجام شد' : 'مانده' }}</em>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul v-else-if="needsEveryone && !isDone" class="share-list">
|
||||||
<li
|
<li
|
||||||
v-for="person in assigneePeople"
|
v-for="person in assigneePeople"
|
||||||
:key="person.id"
|
:key="person.id"
|
||||||
@@ -1304,6 +1448,115 @@ dd {
|
|||||||
color: #0f766e;
|
color: #0f766e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.share-board {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0.55rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-progress {
|
||||||
|
margin: 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-group {
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 0.7rem;
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-group-head {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0.4rem 0.65rem;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.84rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #334155;
|
||||||
|
text-align: start;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-group-name {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-group-name small {
|
||||||
|
margin-inline-end: 0.3rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-group-head em {
|
||||||
|
font-style: normal;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-group-head em.done {
|
||||||
|
color: #0f766e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-group-head :deep(.share-caret) {
|
||||||
|
color: #94a3b8;
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-group-head :deep(.share-caret.open) {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-members {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.15rem 0.65rem 0.5rem;
|
||||||
|
max-height: 14rem;
|
||||||
|
overflow: auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(13.5rem, 1fr));
|
||||||
|
gap: 0.2rem 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-members li {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-width: 0;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-members li span {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-members em {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
font-style: normal;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-members li.done em {
|
||||||
|
color: #0f766e;
|
||||||
|
}
|
||||||
|
|
||||||
.share-force {
|
.share-force {
|
||||||
margin-top: 0.45rem;
|
margin-top: 0.45rem;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|||||||
@@ -1285,6 +1285,9 @@ watch([id, isNew], () => void load(), { immediate: true });
|
|||||||
<input v-model="completionMode" type="radio" value="all" />
|
<input v-model="completionMode" type="radio" value="all" />
|
||||||
همهٔ مسئولها باید انجام دهند
|
همهٔ مسئولها باید انجام دهند
|
||||||
</label>
|
</label>
|
||||||
|
<p v-if="completionMode === 'all'" class="completion-hint">
|
||||||
|
هر کدام از همین افراد باید جداگانه سهم خودش را انجام دهد.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<small class="hint">
|
<small class="hint">
|
||||||
{{
|
{{
|
||||||
@@ -1819,14 +1822,16 @@ watch([id, isNew], () => void load(), { immediate: true });
|
|||||||
.completion-mode {
|
.completion-mode {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.65rem 1rem;
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
margin-top: 0.75rem;
|
margin-top: 0.75rem;
|
||||||
color: #334155;
|
|
||||||
font-size: 0.88rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.completion-mode span {
|
.completion-mode > span {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.75rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1834,9 +1839,46 @@ watch([id, isNew], () => void load(), { immediate: true });
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.2rem 0.65rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #fff;
|
||||||
|
color: #334155;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.4;
|
||||||
|
white-space: nowrap;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.completion-mode label:has(input:checked) {
|
||||||
|
border-color: #14b8a6;
|
||||||
|
background: #f0fdfa;
|
||||||
|
color: #0f766e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completion-mode input {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
min-height: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex: 0 0 14px;
|
||||||
|
accent-color: #0d9488;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.completion-hint {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0.15rem 0 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.assignee-chips {
|
.assignee-chips {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
Reference in New Issue
Block a user