Skip to content

Commit f9f77fa

Browse files
committed
read and write access division with the space selector
1 parent b366f55 commit f9f77fa

File tree

3 files changed

+206
-16
lines changed

3 files changed

+206
-16
lines changed

src/components/interactive/OrbitCallDashboard.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ export default function OrbitCallDashboard() {
246246
<SpaceSelector
247247
onSpaceChange={setSelectedSpaceId}
248248
selectedSpaceId={selectedSpaceId}
249+
requireWriteAccess={true}
249250
/>
250251
</div>
251252
</div>

src/components/interactive/SpaceSelector.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,18 @@
22

33
import { useState, useEffect } from "react";
44
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
5-
import { getUserSpaces, type Space } from "@/lib/utils";
5+
import { getUserSpaces, getUserSpacesWithWriteAccess, type Space } from "@/lib/utils";
66
import { EXTERNAL } from "@/constant";
77

88
interface SpaceSelectorProps {
99
onSpaceChange: (spaceId: string | null) => void;
1010
selectedSpaceId?: string | null;
1111
className?: string;
1212
showAllOption?: boolean;
13+
requireWriteAccess?: boolean; // New prop to determine if only write access spaces should be shown
1314
}
1415

15-
export default function SpaceSelector({ onSpaceChange, selectedSpaceId, className = "", showAllOption = false }: SpaceSelectorProps) {
16+
export default function SpaceSelector({ onSpaceChange, selectedSpaceId, className = "", showAllOption = false, requireWriteAccess = false }: SpaceSelectorProps) {
1617
const [spaces, setSpaces] = useState<Space[]>([]);
1718
const [isLoading, setIsLoading] = useState(true);
1819
const [error, setError] = useState<string | null>(null);
@@ -23,7 +24,9 @@ export default function SpaceSelector({ onSpaceChange, selectedSpaceId, classNam
2324
setError(null);
2425

2526
try {
26-
const result = await getUserSpaces(EXTERNAL.directus_url);
27+
const result = requireWriteAccess
28+
? await getUserSpacesWithWriteAccess(EXTERNAL.directus_url)
29+
: await getUserSpaces(EXTERNAL.directus_url);
2730

2831
if (result.success && result.spaces) {
2932
setSpaces(result.spaces);
@@ -43,7 +46,7 @@ export default function SpaceSelector({ onSpaceChange, selectedSpaceId, classNam
4346
}
4447

4548
fetchSpaces();
46-
}, [selectedSpaceId, onSpaceChange]);
49+
}, [selectedSpaceId, onSpaceChange, requireWriteAccess]);
4750

4851
if (isLoading) {
4952
return (
@@ -76,7 +79,7 @@ export default function SpaceSelector({ onSpaceChange, selectedSpaceId, classNam
7679
value={selectedSpaceId || undefined}
7780
onValueChange={onSpaceChange}
7881
>
79-
<SelectTrigger className="w-48 bg-white/20 border-white/40 text-white backdrop-blur-sm [&_svg]:text-white/70 focus-visible:ring-white/50">
82+
<SelectTrigger className="w-64 bg-white/20 border-white/40 text-white backdrop-blur-sm [&_svg]:text-white/70 focus-visible:ring-white/50">
8083
<SelectValue placeholder="Select a space" />
8184
</SelectTrigger>
8285
<SelectContent className="bg-white border border-gray-200 shadow-lg">
@@ -94,7 +97,7 @@ export default function SpaceSelector({ onSpaceChange, selectedSpaceId, classNam
9497
value={space.id.toString()}
9598
className="text-gray-900 hover:bg-gray-100 focus:bg-gray-100"
9699
>
97-
{space.name}
100+
{space.name} ({space.job_description_count || 0} jobs, {space.candidate_profile_count || 0} candidates)
98101
</SelectItem>
99102
))}
100103
</SelectContent>

src/lib/utils.ts

Lines changed: 196 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -646,11 +646,12 @@ export async function getUserSpaces(directusUrl: string): Promise<{ success: boo
646646
return false;
647647
}).map((item: any) => item.space).filter(Boolean) || [];
648648

649-
// Fetch candidate counts for each space separately
649+
// Fetch candidate and job description counts for each space separately
650650
const spacesWithCounts = await Promise.all(
651651
spacesWithReadAccess.map(async (space: any) => {
652652
try {
653-
const countResponse = await fetch(
653+
// Fetch candidate profile count
654+
const candidateResponse = await fetch(
654655
`${directusUrl}/items/candidate_profile?filter[space][_eq]=${space.id}&aggregate[count]=id`,
655656
{
656657
credentials: 'include',
@@ -660,31 +661,67 @@ export async function getUserSpaces(directusUrl: string): Promise<{ success: boo
660661
}
661662
);
662663

663-
if (countResponse.ok) {
664-
const countResult = await countResponse.json();
664+
if (candidateResponse.ok) {
665+
const candidateResult = await candidateResponse.json();
665666

666667
// Extract count from aggregate response - try different possible structures
667668
let count = 0;
668-
if (countResult.data?.[0]?.count !== undefined) {
669-
const countValue = countResult.data[0].count;
669+
if (candidateResult.data?.[0]?.count !== undefined) {
670+
const countValue = candidateResult.data[0].count;
670671
// Handle case where count is an object with id property
671672
if (typeof countValue === 'object' && countValue.id !== undefined) {
672673
count = parseInt(countValue.id) || 0;
673674
} else {
674675
count = parseInt(countValue) || 0;
675676
}
676-
} else if (countResult.data?.length !== undefined) {
677-
count = countResult.data.length;
678-
} else if (typeof countResult.data === 'number') {
679-
count = countResult.data;
677+
} else if (candidateResult.data?.length !== undefined) {
678+
count = candidateResult.data.length;
679+
} else if (typeof candidateResult.data === 'number') {
680+
count = candidateResult.data;
680681
}
681682

682683
space.candidate_profile_count = count;
683684
} else {
684685
space.candidate_profile_count = 0;
685686
}
687+
688+
// Fetch job description count
689+
const jdResponse = await fetch(
690+
`${directusUrl}/items/job_description?filter[space][_eq]=${space.id}&aggregate[count]=id`,
691+
{
692+
credentials: 'include',
693+
headers: {
694+
'Accept': 'application/json'
695+
}
696+
}
697+
);
698+
699+
if (jdResponse.ok) {
700+
const jdResult = await jdResponse.json();
701+
702+
// Extract count from aggregate response - try different possible structures
703+
let count = 0;
704+
if (jdResult.data?.[0]?.count !== undefined) {
705+
const countValue = jdResult.data[0].count;
706+
// Handle case where count is an object with id property
707+
if (typeof countValue === 'object' && countValue.id !== undefined) {
708+
count = parseInt(countValue.id) || 0;
709+
} else {
710+
count = parseInt(countValue) || 0;
711+
}
712+
} else if (jdResult.data?.length !== undefined) {
713+
count = jdResult.data.length;
714+
} else if (typeof jdResult.data === 'number') {
715+
count = jdResult.data;
716+
}
717+
718+
space.job_description_count = count;
719+
} else {
720+
space.job_description_count = 0;
721+
}
686722
} catch (error) {
687723
space.candidate_profile_count = 0;
724+
space.job_description_count = 0;
688725
}
689726
return space;
690727
})
@@ -703,6 +740,155 @@ export async function getUserSpaces(directusUrl: string): Promise<{ success: boo
703740
}
704741
}
705742

743+
// Fetch user spaces with write access only (for orbit call creation)
744+
export async function getUserSpacesWithWriteAccess(directusUrl: string): Promise<{ success: boolean; spaces?: Space[]; error?: string }> {
745+
try {
746+
const user = await getUserProfile(directusUrl);
747+
if (!user) {
748+
return {
749+
success: false,
750+
error: "User not authenticated"
751+
};
752+
}
753+
754+
const response = await fetch(
755+
`${directusUrl}/items/space_user?filter[user][_eq]=${encodeURIComponent(user.id)}&fields=id,space.id,space.name,space.description,permission`,
756+
{
757+
credentials: 'include',
758+
headers: {
759+
'Accept': 'application/json'
760+
}
761+
}
762+
);
763+
764+
if (!response.ok) {
765+
const errorText = await response.text().catch(() => 'Unknown error');
766+
return {
767+
success: false,
768+
error: `HTTP ${response.status}: ${errorText}`
769+
};
770+
}
771+
772+
const result = await response.json();
773+
774+
// Filter spaces where user has 'write' access or higher permissions (for orbit call creation)
775+
const spacesWithWriteAccess = result.data?.filter((item: any) => {
776+
const permission = item.permission;
777+
778+
// If no permission is set, deny access
779+
if (!permission) {
780+
return false;
781+
}
782+
783+
// Handle array of permissions
784+
if (Array.isArray(permission)) {
785+
return permission.some(perm =>
786+
typeof perm === 'string' && ['write', 'admin'].includes(perm.toLowerCase())
787+
);
788+
}
789+
790+
// Handle single string permission
791+
if (typeof permission === 'string') {
792+
return ['write', 'admin'].includes(permission.toLowerCase());
793+
}
794+
795+
return false;
796+
}).map((item: any) => item.space).filter(Boolean) || [];
797+
798+
// Fetch candidate and job description counts for each space separately
799+
const spacesWithCounts = await Promise.all(
800+
spacesWithWriteAccess.map(async (space: any) => {
801+
try {
802+
// Fetch candidate profile count
803+
const candidateResponse = await fetch(
804+
`${directusUrl}/items/candidate_profile?filter[space][_eq]=${space.id}&aggregate[count]=id`,
805+
{
806+
credentials: 'include',
807+
headers: {
808+
'Accept': 'application/json'
809+
}
810+
}
811+
);
812+
813+
if (candidateResponse.ok) {
814+
const candidateResult = await candidateResponse.json();
815+
816+
// Extract count from aggregate response - try different possible structures
817+
let count = 0;
818+
if (candidateResult.data?.[0]?.count !== undefined) {
819+
const countValue = candidateResult.data[0].count;
820+
// Handle case where count is an object with id property
821+
if (typeof countValue === 'object' && countValue.id !== undefined) {
822+
count = parseInt(countValue.id) || 0;
823+
} else {
824+
count = parseInt(countValue) || 0;
825+
}
826+
} else if (candidateResult.data?.length !== undefined) {
827+
count = candidateResult.data.length;
828+
} else if (typeof candidateResult.data === 'number') {
829+
count = candidateResult.data;
830+
}
831+
832+
space.candidate_profile_count = count;
833+
} else {
834+
space.candidate_profile_count = 0;
835+
}
836+
837+
// Fetch job description count
838+
const jdResponse = await fetch(
839+
`${directusUrl}/items/job_description?filter[space][_eq]=${space.id}&aggregate[count]=id`,
840+
{
841+
credentials: 'include',
842+
headers: {
843+
'Accept': 'application/json'
844+
}
845+
}
846+
);
847+
848+
if (jdResponse.ok) {
849+
const jdResult = await jdResponse.json();
850+
851+
// Extract count from aggregate response - try different possible structures
852+
let count = 0;
853+
if (jdResult.data?.[0]?.count !== undefined) {
854+
const countValue = jdResult.data[0].count;
855+
// Handle case where count is an object with id property
856+
if (typeof countValue === 'object' && countValue.id !== undefined) {
857+
count = parseInt(countValue.id) || 0;
858+
} else {
859+
count = parseInt(countValue) || 0;
860+
}
861+
} else if (jdResult.data?.length !== undefined) {
862+
count = jdResult.data.length;
863+
} else if (typeof jdResult.data === 'number') {
864+
count = jdResult.data;
865+
}
866+
867+
space.job_description_count = count;
868+
} else {
869+
space.job_description_count = 0;
870+
}
871+
} catch (error) {
872+
space.candidate_profile_count = 0;
873+
space.job_description_count = 0;
874+
}
875+
return space;
876+
})
877+
);
878+
879+
return {
880+
success: true,
881+
spaces: spacesWithCounts
882+
};
883+
} catch (error) {
884+
console.error('Error fetching user spaces with write access:', error);
885+
return {
886+
success: false,
887+
error: error instanceof Error ? error.message : 'Unknown error occurred'
888+
};
889+
}
890+
}
891+
706892
// Create a new space in Directus
707893
export async function createSpace(
708894
name: string,

0 commit comments

Comments
 (0)