Add multi-workspace selector scope

This commit is contained in:
2026-05-05 13:20:44 -04:00
parent 78a7517de7
commit 0d4188b64e
11 changed files with 270 additions and 37 deletions

View File

@@ -15,7 +15,7 @@ export const useCampaignsStore = defineStore('campaigns', () => {
const error = ref(null);
async function fetchCampaigns() {
if (!authStore.isAuthenticated || !workspaceStore.activeWorkspaceId) {
if (!authStore.isAuthenticated) {
campaigns.value = [];
error.value = null;
return;
@@ -27,11 +27,13 @@ export const useCampaignsStore = defineStore('campaigns', () => {
try {
const response = await client.get('/api/campaigns', {
params: {
workspaceId: workspaceStore.activeWorkspaceId,
workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
},
});
campaigns.value = response.data ?? [];
campaigns.value = (response.data ?? []).filter(campaign =>
workspaceStore.isWorkspaceVisible(campaign.workspaceId)
);
} catch (fetchError) {
console.error('Failed to fetch campaigns:', fetchError);
campaigns.value = [];
@@ -75,9 +77,9 @@ export const useCampaignsStore = defineStore('campaigns', () => {
}
watch(
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId],
async ([isAuthenticated, workspaceId]) => {
if (!isAuthenticated || !workspaceId) {
() => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
async ([isAuthenticated]) => {
if (!isAuthenticated) {
campaigns.value = [];
error.value = null;
return;

View File

@@ -14,6 +14,7 @@ export const useChannelsStore = defineStore('channels', () => {
const isCreating = ref(false);
const error = ref(null);
const loadedWorkspaceId = ref(null);
const allWorkspacesKey = '__all__';
const availableNetworks = [
'Instagram',
@@ -28,15 +29,16 @@ export const useChannelsStore = defineStore('channels', () => {
async function fetchChannels({ force = false } = {}) {
const currentWorkspaceId = workspaceStore.activeWorkspaceId;
const currentScopeKey = currentWorkspaceId ?? workspaceStore.workspaceScopeKey ?? allWorkspacesKey;
if (!authStore.isAuthenticated || !currentWorkspaceId) {
if (!authStore.isAuthenticated) {
channels.value = [];
error.value = null;
loadedWorkspaceId.value = null;
return;
}
if (!force && loadedWorkspaceId.value === currentWorkspaceId) {
if (!force && loadedWorkspaceId.value === currentScopeKey) {
return;
}
@@ -46,12 +48,14 @@ export const useChannelsStore = defineStore('channels', () => {
try {
const response = await client.get('/api/channels', {
params: {
workspaceId: currentWorkspaceId,
workspaceId: currentWorkspaceId ?? undefined,
},
});
channels.value = response.data ?? [];
loadedWorkspaceId.value = currentWorkspaceId;
channels.value = (response.data ?? []).filter(channel =>
workspaceStore.isWorkspaceVisible(channel.workspaceId)
);
loadedWorkspaceId.value = currentScopeKey;
} catch (fetchError) {
console.error('Failed to fetch channels:', fetchError);
channels.value = [];
@@ -101,9 +105,9 @@ export const useChannelsStore = defineStore('channels', () => {
}
watch(
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId],
async ([isAuthenticated, workspaceId]) => {
if (!isAuthenticated || !workspaceId) {
() => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
async ([isAuthenticated]) => {
if (!isAuthenticated) {
channels.value = [];
error.value = null;
loadedWorkspaceId.value = null;

View File

@@ -47,10 +47,12 @@
.filter(channel => channel.network)
.map(channel => {
const metrics = buildMetrics(channel.name);
const workspace = workspaceStore.workspaces.find(candidate => candidate.id === channel.workspaceId);
return {
...channel,
...metrics,
workspaceName: workspace?.name ?? t('nav.noWorkspace'),
};
})
);
@@ -215,7 +217,7 @@
>
<div class="channel-header">
<strong>{{ channel.name }}</strong>
<span>{{ workspaceStore.activeWorkspace?.name || t('nav.noWorkspace') }}</span>
<span>{{ channel.workspaceName }}</span>
</div>
<div class="channel-metrics">

View File

@@ -25,7 +25,7 @@ export const useClientsStore = defineStore('clients', () => {
});
async function fetchClients() {
if (!authStore.isAuthenticated || !workspaceStore.activeWorkspaceId) {
if (!authStore.isAuthenticated) {
clients.value = [];
error.value = null;
return;
@@ -37,11 +37,13 @@ export const useClientsStore = defineStore('clients', () => {
try {
const response = await client.get('/api/clients', {
params: {
workspaceId: workspaceStore.activeWorkspaceId,
workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
},
});
clients.value = response.data ?? [];
clients.value = (response.data ?? []).filter(candidate =>
workspaceStore.isWorkspaceVisible(candidate.workspaceId)
);
} catch (fetchError) {
console.error('Failed to fetch clients:', fetchError);
clients.value = [];
@@ -153,9 +155,9 @@ export const useClientsStore = defineStore('clients', () => {
}
watch(
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId],
async ([isAuthenticated, workspaceId]) => {
if (!isAuthenticated || !workspaceId) {
() => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
async ([isAuthenticated]) => {
if (!isAuthenticated) {
clients.value = [];
error.value = null;
return;

View File

@@ -24,6 +24,10 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
status: false,
});
function currentItemWorkspaceId() {
return item.value?.workspaceId ?? workspaceStore.activeWorkspaceId;
}
function reset() {
item.value = null;
revisions.value = [];
@@ -54,7 +58,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
client.get('/api/approvals', { params: { contentItemId } }),
client.get('/api/notifications', {
params: {
workspaceId: workspaceStore.activeWorkspaceId,
workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
contentItemId,
},
}),
@@ -97,7 +101,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
const response = await client.post('/api/assets/google-drive', {
...payload,
contentItemId,
workspaceId: workspaceStore.activeWorkspaceId,
workspaceId: currentItemWorkspaceId(),
});
if (response.data) {
assets.value = [...assets.value, response.data];
@@ -131,7 +135,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
const response = await client.post('/api/comments', {
...payload,
contentItemId,
workspaceId: workspaceStore.activeWorkspaceId,
workspaceId: currentItemWorkspaceId(),
});
if (response.data) {
comments.value = [...comments.value, response.data];
@@ -202,7 +206,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
async function fetchNotifications(contentItemId) {
const response = await client.get('/api/notifications', {
params: {
workspaceId: workspaceStore.activeWorkspaceId,
workspaceId: currentItemWorkspaceId() ?? undefined,
contentItemId,
},
});

View File

@@ -20,7 +20,7 @@ export const useContentItemsStore = defineStore('content-items', () => {
);
async function fetchContentItems(filters = {}) {
if (!authStore.isAuthenticated || !workspaceStore.activeWorkspaceId) {
if (!authStore.isAuthenticated) {
items.value = [];
error.value = null;
return;
@@ -32,13 +32,15 @@ export const useContentItemsStore = defineStore('content-items', () => {
try {
const response = await client.get('/api/content-items', {
params: {
workspaceId: workspaceStore.activeWorkspaceId,
workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
clientId: filters.clientId,
campaignId: filters.campaignId,
},
});
items.value = response.data ?? [];
items.value = (response.data ?? []).filter(item =>
workspaceStore.isWorkspaceVisible(item.workspaceId)
);
} catch (fetchError) {
console.error('Failed to fetch content items:', fetchError);
items.value = [];
@@ -86,9 +88,9 @@ export const useContentItemsStore = defineStore('content-items', () => {
}
watch(
() => [authStore.isAuthenticated, workspaceStore.activeWorkspaceId],
async ([isAuthenticated, workspaceId]) => {
if (!isAuthenticated || !workspaceId) {
() => [authStore.isAuthenticated, workspaceStore.workspaceScopeKey],
async ([isAuthenticated]) => {
if (!isAuthenticated) {
items.value = [];
error.value = null;
return;

View File

@@ -11,6 +11,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
const workspaces = ref([]);
const activeWorkspaceId = ref(null);
const visibleWorkspaceIds = ref([]);
const isLoading = ref(false);
const isCreating = ref(false);
const isUpdating = ref(false);
@@ -25,11 +26,43 @@ export const useWorkspaceStore = defineStore('workspace', () => {
const activeWorkspace = computed(() =>
workspaces.value.find(workspace => workspace.id === activeWorkspaceId.value) ?? null
);
const isAllWorkspacesSelected = computed(() =>
activeWorkspaceId.value === null && workspaces.value.length > 1
);
const visibleWorkspaceCount = computed(() =>
activeWorkspaceId.value ? 1 : visibleWorkspaceIds.value.length
);
const areAllWorkspacesVisible = computed(() =>
activeWorkspaceId.value === null &&
workspaces.value.length > 1 &&
visibleWorkspaceIds.value.length === workspaces.value.length
);
const visibleWorkspaceIdSet = computed(() => new Set(visibleWorkspaceIds.value));
const workspaceScopeKey = computed(() =>
activeWorkspaceId.value ?? visibleWorkspaceIds.value.slice().sort().join(',')
);
function allWorkspaceIds() {
return workspaces.value.map(workspace => workspace.id);
}
function normalizeVisibleWorkspaces() {
const workspaceIds = allWorkspaceIds();
const knownWorkspaceIds = new Set(workspaceIds);
const nextVisibleWorkspaceIds = visibleWorkspaceIds.value.filter(workspaceId =>
knownWorkspaceIds.has(workspaceId)
);
visibleWorkspaceIds.value = nextVisibleWorkspaceIds.length > 0
? nextVisibleWorkspaceIds
: workspaceIds;
}
async function fetchWorkspaces() {
if (!authStore.isAuthenticated) {
workspaces.value = [];
activeWorkspaceId.value = null;
visibleWorkspaceIds.value = [];
error.value = null;
return;
}
@@ -40,9 +73,10 @@ export const useWorkspaceStore = defineStore('workspace', () => {
try {
const response = await client.get('/api/workspaces');
workspaces.value = response.data ?? [];
normalizeVisibleWorkspaces();
if (!workspaces.value.some(workspace => workspace.id === activeWorkspaceId.value)) {
activeWorkspaceId.value = workspaces.value[0]?.id ?? null;
activeWorkspaceId.value = workspaces.value.length > 1 ? null : workspaces.value[0]?.id ?? null;
}
organizationStore.setSelectedOrganizationFromWorkspace(activeWorkspace.value);
@@ -75,6 +109,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
workspaces.value = [...workspaces.value, response.data]
.sort((left, right) => left.name.localeCompare(right.name));
activeWorkspaceId.value = response.data.id;
visibleWorkspaceIds.value = [response.data.id];
try {
await client.post('/api/clients', {
@@ -172,10 +207,62 @@ export const useWorkspaceStore = defineStore('workspace', () => {
if (workspaces.value.some(workspace => workspace.id === workspaceId)) {
activeWorkspaceId.value = workspaceId;
visibleWorkspaceIds.value = [workspaceId];
organizationStore.setSelectedOrganizationFromWorkspace(activeWorkspace.value);
}
}
function setAllWorkspaces() {
if (workspaces.value.length > 1) {
activeWorkspaceId.value = null;
visibleWorkspaceIds.value = allWorkspaceIds();
}
}
function isWorkspaceVisible(workspaceId) {
if (activeWorkspaceId.value) {
return workspaceId === activeWorkspaceId.value;
}
if (visibleWorkspaceIds.value.length === 0) {
return true;
}
return visibleWorkspaceIdSet.value.has(workspaceId);
}
function toggleWorkspaceVisibility(workspaceId) {
if (!workspaces.value.some(workspace => workspace.id === workspaceId)) {
return;
}
const wasFocusedOnSingleWorkspace = Boolean(activeWorkspaceId.value);
activeWorkspaceId.value = null;
const visibleIds = new Set(
wasFocusedOnSingleWorkspace || visibleWorkspaceIds.value.length === 0
? allWorkspaceIds()
: visibleWorkspaceIds.value
);
if (visibleIds.has(workspaceId)) {
visibleIds.delete(workspaceId);
} else {
visibleIds.add(workspaceId);
}
if (visibleIds.size === 0) {
visibleIds.add(workspaceId);
}
const nextVisibleWorkspaceIds = allWorkspaceIds().filter(id => visibleIds.has(id));
if (nextVisibleWorkspaceIds.length === 1) {
setActiveWorkspace(nextVisibleWorkspaceIds[0]);
return;
}
visibleWorkspaceIds.value = nextVisibleWorkspaceIds;
}
async function fetchInvites(workspaceId = activeWorkspaceId.value) {
if (!authStore.isAuthenticated || !workspaceId) {
invitesByWorkspace.value = {};
@@ -257,6 +344,7 @@ export const useWorkspaceStore = defineStore('workspace', () => {
if (!isAuthenticated) {
workspaces.value = [];
activeWorkspaceId.value = null;
visibleWorkspaceIds.value = [];
error.value = null;
return;
}
@@ -270,6 +358,11 @@ export const useWorkspaceStore = defineStore('workspace', () => {
workspaces,
activeWorkspaceId,
activeWorkspace,
visibleWorkspaceIds,
isAllWorkspacesSelected,
visibleWorkspaceCount,
areAllWorkspacesVisible,
workspaceScopeKey,
isLoading,
isCreating,
isUpdating,
@@ -288,5 +381,8 @@ export const useWorkspaceStore = defineStore('workspace', () => {
fetchMembers,
inviteMember,
setActiveWorkspace,
setAllWorkspaces,
isWorkspaceVisible,
toggleWorkspaceVisibility,
};
});