Skip to content

Commit 6431885

Browse files
committed
fixed tasks status issues, added project roles addition, updation and deletion. Added contributors tab
1 parent 7dfdd09 commit 6431885

11 files changed

Lines changed: 1501 additions & 345 deletions

File tree

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
<template>
2+
<b-modal
3+
ref="modal"
4+
hide-footer
5+
title="Add Contributor"
6+
@hidden="resetForm"
7+
>
8+
<section class="project-detail-add-contributor">
9+
<p class="project-detail-add-contributor-copy">
10+
Select a workspace user and assign their project role.
11+
</p>
12+
13+
<div class="project-detail-add-contributor-field">
14+
<label :for="searchInputId" class="form-label">Search users</label>
15+
<div class="project-detail-add-contributor-search-shell">
16+
<input
17+
:id="searchInputId"
18+
:value="searchQuery"
19+
class="form-control project-detail-add-contributor-search-input"
20+
type="search"
21+
placeholder="Search by name or email"
22+
@input="onSearchInput"
23+
>
24+
<app-icon variant="search" size="20" no-margin />
25+
</div>
26+
</div>
27+
28+
<div class="project-detail-add-contributor-results" aria-live="polite">
29+
<div v-if="loading" class="project-detail-add-contributor-status">
30+
<app-spinner size="sm" />
31+
<span>Loading users...</span>
32+
</div>
33+
34+
<p v-else-if="filteredUsers.length === 0" class="project-detail-add-contributor-status">
35+
No users match your search.
36+
</p>
37+
38+
<div v-else class="project-detail-add-contributor-list" role="listbox" aria-label="Available users">
39+
<button
40+
v-for="user in filteredUsers"
41+
:key="user.authUid"
42+
class="project-detail-add-contributor-user"
43+
:class="{
44+
'project-detail-add-contributor-user-disabled': isExistingUser(user.authUid),
45+
'project-detail-add-contributor-user-selected': selectedUserId === user.authUid,
46+
}"
47+
type="button"
48+
:disabled="isExistingUser(user.authUid)"
49+
:aria-pressed="selectedUserId === user.authUid"
50+
@click="selectUser(user.authUid)"
51+
>
52+
<span class="project-detail-add-contributor-avatar" aria-hidden="true">
53+
{{ getInitial(user.displayName) }}
54+
</span>
55+
56+
<span class="project-detail-add-contributor-user-copy">
57+
<strong>{{ user.displayName }}</strong>
58+
<span>{{ user.email }}</span>
59+
</span>
60+
61+
<span
62+
v-if="isExistingUser(user.authUid)"
63+
class="project-detail-add-contributor-state"
64+
>
65+
Already added
66+
</span>
67+
</button>
68+
</div>
69+
</div>
70+
71+
<div class="project-detail-add-contributor-field">
72+
<label :for="roleSelectId" class="form-label">Role</label>
73+
<app-select
74+
:id="roleSelectId"
75+
v-model="selectedRole"
76+
:options="roleOptions"
77+
:aria-label="'Select contributor role'"
78+
/>
79+
</div>
80+
81+
<div class="project-detail-add-contributor-actions">
82+
<button
83+
class="btn btn-outline-secondary"
84+
type="button"
85+
:disabled="saving"
86+
@click="hide"
87+
>
88+
Cancel
89+
</button>
90+
91+
<button
92+
class="btn btn-primary"
93+
type="button"
94+
:disabled="!canSubmit || saving"
95+
@click="submit"
96+
>
97+
<app-spinner v-if="saving" size="sm" />
98+
<template v-else>
99+
Add Contributor
100+
</template>
101+
</button>
102+
</div>
103+
</section>
104+
</b-modal>
105+
</template>
106+
107+
<script setup lang="ts">
108+
import { BModal } from 'bootstrap-vue-next/components/BModal';
109+
import type { ComponentExposed } from 'vue-component-type-helpers';
110+
111+
import type { ProjectWizardWorkspaceUser } from '~/types/project-wizard';
112+
import type { WorkspaceProjectContributorRole } from '~/types/projects';
113+
114+
interface SelectOption {
115+
label: string;
116+
value: WorkspaceProjectContributorRole;
117+
}
118+
119+
interface Props {
120+
existingUserIds: string[];
121+
loading: boolean;
122+
saving: boolean;
123+
users: ProjectWizardWorkspaceUser[];
124+
}
125+
126+
const props = defineProps<Props>();
127+
const emit = defineEmits<{
128+
submit: [payload: { role: WorkspaceProjectContributorRole; userId: string }];
129+
'update:search': [value: string];
130+
}>();
131+
132+
const modal = useTemplateRef<ComponentExposed<typeof BModal>>('modal');
133+
const searchInputId = 'project-detail-add-contributor-search';
134+
const roleSelectId = 'project-detail-add-contributor-role';
135+
const searchQuery = ref('');
136+
const selectedRole = ref<WorkspaceProjectContributorRole>('contributor');
137+
const selectedUserId = ref('');
138+
139+
const roleOptions: SelectOption[] = [
140+
{ label: 'Lead', value: 'lead' },
141+
{ label: 'Validator', value: 'validator' },
142+
{ label: 'Contributor', value: 'contributor' },
143+
];
144+
145+
const filteredUsers = computed(() => props.users.slice(0, 10));
146+
147+
const canSubmit = computed(() =>
148+
selectedUserId.value.trim().length > 0,
149+
);
150+
151+
defineExpose({ hide, show });
152+
153+
function show() {
154+
modal.value?.show();
155+
}
156+
157+
function hide() {
158+
modal.value?.hide();
159+
}
160+
161+
function resetForm() {
162+
searchQuery.value = '';
163+
selectedRole.value = 'contributor';
164+
selectedUserId.value = '';
165+
emit('update:search', '');
166+
}
167+
168+
function submit() {
169+
if (!canSubmit.value) {
170+
return;
171+
}
172+
173+
emit('submit', {
174+
role: selectedRole.value,
175+
userId: selectedUserId.value,
176+
});
177+
hide();
178+
}
179+
180+
function getInitial(name: string) {
181+
return name.trim().charAt(0).toUpperCase() || '?';
182+
}
183+
184+
function onSearchInput(event: Event) {
185+
const value = (event.target as HTMLInputElement).value;
186+
searchQuery.value = value;
187+
emit('update:search', value);
188+
}
189+
190+
function isExistingUser(userId: string) {
191+
return props.existingUserIds.includes(userId);
192+
}
193+
194+
function selectUser(userId: string) {
195+
if (isExistingUser(userId)) {
196+
return;
197+
}
198+
199+
selectedUserId.value = userId;
200+
}
201+
</script>
202+
203+
<style lang="scss" scoped>
204+
@import "~/assets/scss/theme.scss";
205+
206+
.project-detail-add-contributor {
207+
display: grid;
208+
gap: 1rem;
209+
}
210+
211+
.project-detail-add-contributor-copy {
212+
margin: 0;
213+
color: rgba($secondary, 0.96);
214+
font-size: 0.96rem;
215+
line-height: 1.5;
216+
}
217+
218+
.project-detail-add-contributor-field {
219+
display: grid;
220+
gap: 0.45rem;
221+
}
222+
223+
.project-detail-add-contributor-search-shell {
224+
position: relative;
225+
}
226+
227+
.project-detail-add-contributor-search-shell :deep(.material-icons) {
228+
position: absolute;
229+
top: 50%;
230+
right: 0.85rem;
231+
color: rgba($secondary, 0.78);
232+
transform: translateY(-50%);
233+
}
234+
235+
.project-detail-add-contributor-search-input {
236+
padding-right: 2.5rem;
237+
}
238+
239+
.project-detail-add-contributor-results {
240+
min-height: 8rem;
241+
border: 1px solid rgba($text-navy, 0.12);
242+
border-radius: 0.75rem;
243+
background: #ffffff;
244+
overflow: hidden;
245+
}
246+
247+
.project-detail-add-contributor-list {
248+
max-height: 20rem;
249+
overflow-y: auto;
250+
}
251+
252+
.project-detail-add-contributor-user {
253+
width: 100%;
254+
display: grid;
255+
grid-template-columns: auto minmax(0, 1fr) auto;
256+
gap: 0.8rem;
257+
align-items: center;
258+
padding: 0.9rem 1rem;
259+
text-align: left;
260+
background: #ffffff;
261+
border: 0;
262+
border-bottom: 1px solid rgba($text-navy, 0.08);
263+
}
264+
265+
.project-detail-add-contributor-user:last-child {
266+
border-bottom: 0;
267+
}
268+
269+
.project-detail-add-contributor-user:hover,
270+
.project-detail-add-contributor-user:focus-visible {
271+
background: rgba($primary, 0.04);
272+
}
273+
274+
.project-detail-add-contributor-user-disabled {
275+
cursor: not-allowed;
276+
opacity: 0.72;
277+
}
278+
279+
.project-detail-add-contributor-user-disabled:hover,
280+
.project-detail-add-contributor-user-disabled:focus-visible {
281+
background: #ffffff;
282+
}
283+
284+
.project-detail-add-contributor-user-selected {
285+
background: rgba($primary, 0.08);
286+
}
287+
288+
.project-detail-add-contributor-avatar {
289+
width: 2.25rem;
290+
height: 2.25rem;
291+
display: inline-flex;
292+
align-items: center;
293+
justify-content: center;
294+
color: #5d5abf;
295+
font-size: 0.95rem;
296+
font-weight: 700;
297+
background: linear-gradient(135deg, #efe7fb 0%, #edf1ff 100%);
298+
border: 1px solid rgba(#7150d0, 0.14);
299+
border-radius: 50%;
300+
}
301+
302+
.project-detail-add-contributor-user-copy {
303+
min-width: 0;
304+
display: grid;
305+
gap: 0.15rem;
306+
}
307+
308+
.project-detail-add-contributor-user-copy strong {
309+
color: $text-navy;
310+
font-size: 0.96rem;
311+
font-weight: 700;
312+
line-height: 1.25;
313+
}
314+
315+
.project-detail-add-contributor-user-copy span {
316+
color: rgba($secondary, 0.94);
317+
font-size: 0.88rem;
318+
line-height: 1.35;
319+
word-break: break-word;
320+
}
321+
322+
.project-detail-add-contributor-state {
323+
color: rgba($secondary, 0.88);
324+
font-size: 0.8rem;
325+
font-weight: 600;
326+
white-space: nowrap;
327+
}
328+
329+
.project-detail-add-contributor-status {
330+
display: flex;
331+
align-items: center;
332+
gap: 0.55rem;
333+
padding: 1rem;
334+
margin: 0;
335+
color: rgba($secondary, 0.96);
336+
font-size: 0.92rem;
337+
}
338+
339+
.project-detail-add-contributor-field :deep(.tdei-select-toggle) {
340+
min-height: 2.9rem;
341+
border-radius: 0.75rem;
342+
}
343+
344+
.project-detail-add-contributor-actions {
345+
display: flex;
346+
justify-content: flex-end;
347+
gap: 0.75rem;
348+
}
349+
350+
.project-detail-add-contributor-actions .btn {
351+
min-width: 9.5rem;
352+
min-height: 2.9rem;
353+
}
354+
355+
@include media-breakpoint-down(sm) {
356+
.project-detail-add-contributor-actions {
357+
grid-template-columns: 1fr;
358+
display: grid;
359+
}
360+
361+
.project-detail-add-contributor-actions .btn {
362+
width: 100%;
363+
}
364+
}
365+
</style>

0 commit comments

Comments
 (0)