Add calendar integrations and collaboration updates
Some checks failed
Backend CI/CD / build_and_deploy (push) Has been cancelled
Frontend CI/CD / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-05-05 15:25:53 -04:00
parent c49f03ec06
commit b66c10b681
82 changed files with 8420 additions and 2048 deletions

View File

@@ -0,0 +1,182 @@
import { computed, ref } from 'vue';
import { defineStore } from 'pinia';
import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useWorkspaceStore } from '@/features/workspaces/stores/workspaceStore.js';
import { useClient } from '@/plugins/api.js';
export const useCalendarIntegrationsStore = defineStore('calendar-integrations', () => {
const authStore = useAuthStore();
const workspaceStore = useWorkspaceStore();
const client = useClient();
const sources = ref([]);
const events = ref([]);
const catalogEntries = ref([]);
const hiddenSourceIds = ref(new Set());
const isLoadingSources = ref(false);
const isLoadingEvents = ref(false);
const isLoadingCatalog = ref(false);
const isCreatingSource = ref(false);
const error = ref(null);
const visibleSourceIds = computed(() =>
new Set(sources.value
.filter(source => source.isEnabled && !hiddenSourceIds.value.has(source.id))
.map(source => source.id))
);
const visibleEvents = computed(() =>
events.value.filter(event => visibleSourceIds.value.has(event.calendarSourceId))
);
function sourceById(sourceId) {
return sources.value.find(source => source.id === sourceId) ?? null;
}
async function fetchSources(workspaceId = workspaceStore.activeWorkspaceId) {
if (!authStore.isAuthenticated) {
sources.value = [];
error.value = null;
return;
}
isLoadingSources.value = true;
error.value = null;
try {
const response = await client.get('/api/calendar-integrations/sources', {
params: {
workspaceId: workspaceId ?? undefined,
},
});
sources.value = response.data ?? [];
} catch (fetchError) {
console.error('Failed to fetch calendar sources:', fetchError);
sources.value = [];
error.value = 'Failed to load calendar sources.';
} finally {
isLoadingSources.value = false;
}
}
async function fetchEvents({ workspaceId = workspaceStore.activeWorkspaceId, startDate, endDate } = {}) {
if (!authStore.isAuthenticated) {
events.value = [];
error.value = null;
return;
}
isLoadingEvents.value = true;
try {
const response = await client.get('/api/calendar-integrations/events', {
params: {
workspaceId: workspaceId ?? undefined,
startDate,
endDate,
},
});
events.value = response.data ?? [];
} catch (fetchError) {
console.error('Failed to fetch calendar events:', fetchError);
events.value = [];
error.value = 'Failed to load calendar events.';
} finally {
isLoadingEvents.value = false;
}
}
async function searchCatalog(filters = {}) {
if (!authStore.isAuthenticated) {
catalogEntries.value = [];
return;
}
isLoadingCatalog.value = true;
try {
const response = await client.get('/api/calendar-integrations/catalog', {
params: filters,
});
catalogEntries.value = response.data ?? [];
} catch (fetchError) {
console.error('Failed to search calendar catalog:', fetchError);
catalogEntries.value = [];
error.value = 'Failed to load calendar catalog.';
} finally {
isLoadingCatalog.value = false;
}
}
async function createSource(payload) {
isCreatingSource.value = true;
error.value = null;
try {
const response = await client.post('/api/calendar-integrations/sources', payload);
if (response.data) {
sources.value = [...sources.value, response.data]
.sort((left, right) => left.displayTitle.localeCompare(right.displayTitle));
}
return response.data;
} catch (createError) {
console.error('Failed to create calendar source:', createError);
error.value = 'Failed to add calendar source.';
throw createError;
} finally {
isCreatingSource.value = false;
}
}
async function refreshSource(sourceId) {
if (!sourceId) {
return null;
}
try {
const response = await client.post(`/api/calendar-integrations/sources/${sourceId}/refresh`);
const refreshedSource = response.data;
if (refreshedSource) {
sources.value = sources.value.map(source =>
source.id === refreshedSource.id ? refreshedSource : source
);
}
return refreshedSource;
} catch (refreshError) {
console.error('Failed to refresh calendar source:', refreshError);
error.value = 'Failed to refresh calendar source.';
throw refreshError;
}
}
function toggleSourceVisibility(sourceId) {
const nextHiddenIds = new Set(hiddenSourceIds.value);
if (nextHiddenIds.has(sourceId)) {
nextHiddenIds.delete(sourceId);
} else {
nextHiddenIds.add(sourceId);
}
hiddenSourceIds.value = nextHiddenIds;
}
return {
sources,
events,
catalogEntries,
hiddenSourceIds,
visibleSourceIds,
visibleEvents,
isLoadingSources,
isLoadingEvents,
isLoadingCatalog,
isCreatingSource,
error,
sourceById,
fetchSources,
fetchEvents,
searchCatalog,
createSource,
refreshSource,
toggleSourceVisibility,
};
});

View File

@@ -13,6 +13,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
const comments = ref([]);
const approvals = ref([]);
const notifications = ref([]);
const activity = ref([]);
const isLoading = ref(false);
const error = ref(null);
const actions = reactive({
@@ -35,6 +36,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
comments.value = [];
approvals.value = [];
notifications.value = [];
activity.value = [];
error.value = null;
}
@@ -49,19 +51,14 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
assetsResponse,
commentsResponse,
approvalsResponse,
notificationsResponse,
activityResponse,
] = await Promise.all([
client.get(`/api/content-items/${contentItemId}`),
client.get(`/api/content-items/${contentItemId}/revisions`),
client.get('/api/assets', { params: { contentItemId } }),
client.get('/api/comments', { params: { contentItemId } }),
client.get('/api/approvals', { params: { contentItemId } }),
client.get('/api/notifications', {
params: {
workspaceId: workspaceStore.activeWorkspaceId ?? undefined,
contentItemId,
},
}),
client.get(`/api/content-items/${contentItemId}/activity`),
]);
item.value = itemResponse.data;
@@ -69,7 +66,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
assets.value = assetsResponse.data ?? [];
comments.value = commentsResponse.data ?? [];
approvals.value = approvalsResponse.data ?? [];
notifications.value = notificationsResponse.data ?? [];
activity.value = activityResponse.data ?? [];
} catch (fetchError) {
console.error('Failed to load content item detail:', fetchError);
reset();
@@ -105,7 +102,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
});
if (response.data) {
assets.value = [...assets.value, response.data];
await fetchNotifications(contentItemId);
await fetchActivity(contentItemId);
}
return response.data;
} finally {
@@ -120,7 +117,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
const response = await client.post(`/api/assets/${assetId}/revisions`, payload);
if (response.data) {
await fetchAssets(contentItemId);
await fetchNotifications(contentItemId);
await fetchActivity(contentItemId);
}
return response.data;
} finally {
@@ -139,22 +136,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
});
if (response.data) {
comments.value = [...comments.value, response.data];
await fetchNotifications(contentItemId);
}
return response.data;
} finally {
actions.comment = false;
}
}
async function resolveComment(contentItemId, commentId) {
actions.comment = true;
try {
const response = await client.post(`/api/comments/${commentId}/resolve`);
if (response.data) {
comments.value = comments.value.map(comment => comment.id === commentId ? response.data : comment);
await fetchNotifications(contentItemId);
await fetchActivity(contentItemId);
}
return response.data;
} finally {
@@ -170,7 +152,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
if (response.data) {
approvals.value = approvals.value.map(approval => approval.id === approvalId ? response.data : approval);
await fetchContentItem(contentItemId);
await fetchNotifications(contentItemId);
await fetchActivity(contentItemId);
}
return response.data;
} finally {
@@ -184,7 +166,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
try {
const response = await client.post(`/api/content-items/${contentItemId}/status`, { status });
item.value = response.data;
await fetchNotifications(contentItemId);
await fetchActivity(contentItemId);
return response.data;
} finally {
actions.status = false;
@@ -214,6 +196,12 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
return notifications.value;
}
async function fetchActivity(contentItemId) {
const response = await client.get(`/api/content-items/${contentItemId}/activity`);
activity.value = response.data ?? [];
return activity.value;
}
return {
item,
revisions,
@@ -221,6 +209,7 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
comments,
approvals,
notifications,
activity,
isLoading,
error,
actions,
@@ -230,8 +219,8 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
addGoogleDriveAsset,
addAssetRevision,
addComment,
resolveComment,
submitDecision,
updateStatus,
fetchActivity,
};
});