feat: add team collaboration sessions for admins, enhancing session management and visibility in the application
This commit is contained in:
@@ -28,6 +28,7 @@ import {
|
|||||||
|
|
||||||
const TYPE_TABS = [
|
const TYPE_TABS = [
|
||||||
{ value: 'all' as const, icon: '📋', label: 'Tous' },
|
{ value: 'all' as const, icon: '📋', label: 'Tous' },
|
||||||
|
{ value: 'team' as const, icon: '🏢', label: 'Équipe' },
|
||||||
...WORKSHOPS.map((w) => ({ value: w.id, icon: w.icon, label: w.labelShort })),
|
...WORKSHOPS.map((w) => ({ value: w.id, icon: w.icon, label: w.labelShort })),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -131,6 +132,7 @@ interface WorkshopTabsProps {
|
|||||||
yearReviewSessions: YearReviewSession[];
|
yearReviewSessions: YearReviewSession[];
|
||||||
weeklyCheckInSessions: WeeklyCheckInSession[];
|
weeklyCheckInSessions: WeeklyCheckInSession[];
|
||||||
weatherSessions: WeatherSession[];
|
weatherSessions: WeatherSession[];
|
||||||
|
teamCollabSessions?: (AnySession & { isTeamCollab?: true })[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to get resolved collaborator from any session
|
// Helper to get resolved collaborator from any session
|
||||||
@@ -197,6 +199,7 @@ export function WorkshopTabs({
|
|||||||
yearReviewSessions,
|
yearReviewSessions,
|
||||||
weeklyCheckInSessions,
|
weeklyCheckInSessions,
|
||||||
weatherSessions,
|
weatherSessions,
|
||||||
|
teamCollabSessions = [],
|
||||||
}: WorkshopTabsProps) {
|
}: WorkshopTabsProps) {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -219,7 +222,7 @@ export function WorkshopTabs({
|
|||||||
router.push(`/sessions${params.toString() ? `?${params.toString()}` : ''}`);
|
router.push(`/sessions${params.toString() ? `?${params.toString()}` : ''}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Combine and sort all sessions
|
// Combine and sort all sessions (exclude team collab from main list - they're shown separately)
|
||||||
const allSessions: AnySession[] = [
|
const allSessions: AnySession[] = [
|
||||||
...swotSessions,
|
...swotSessions,
|
||||||
...motivatorSessions,
|
...motivatorSessions,
|
||||||
@@ -232,7 +235,9 @@ export function WorkshopTabs({
|
|||||||
const filteredSessions =
|
const filteredSessions =
|
||||||
activeTab === 'all' || activeTab === 'byPerson'
|
activeTab === 'all' || activeTab === 'byPerson'
|
||||||
? allSessions
|
? allSessions
|
||||||
: activeTab === 'swot'
|
: activeTab === 'team'
|
||||||
|
? teamCollabSessions
|
||||||
|
: activeTab === 'swot'
|
||||||
? swotSessions
|
? swotSessions
|
||||||
: activeTab === 'motivators'
|
: activeTab === 'motivators'
|
||||||
? motivatorSessions
|
? motivatorSessions
|
||||||
@@ -242,9 +247,11 @@ export function WorkshopTabs({
|
|||||||
? weeklyCheckInSessions
|
? weeklyCheckInSessions
|
||||||
: weatherSessions;
|
: weatherSessions;
|
||||||
|
|
||||||
// Separate by ownership
|
// Separate by ownership (for non-team tab: owned, shared, teamCollab)
|
||||||
const ownedSessions = filteredSessions.filter((s) => s.isOwner);
|
const ownedSessions = filteredSessions.filter((s) => s.isOwner);
|
||||||
const sharedSessions = filteredSessions.filter((s) => !s.isOwner);
|
const sharedSessions = filteredSessions.filter((s) => !s.isOwner && !(s as AnySession & { isTeamCollab?: boolean }).isTeamCollab);
|
||||||
|
const teamCollabFiltered =
|
||||||
|
activeTab === 'all' ? teamCollabSessions : activeTab === 'team' ? teamCollabSessions : [];
|
||||||
|
|
||||||
// Group by person (all sessions - owned and shared)
|
// Group by person (all sessions - owned and shared)
|
||||||
const sessionsByPerson = groupByPerson(allSessions);
|
const sessionsByPerson = groupByPerson(allSessions);
|
||||||
@@ -270,6 +277,15 @@ export function WorkshopTabs({
|
|||||||
label="Par personne"
|
label="Par personne"
|
||||||
count={sessionsByPerson.size}
|
count={sessionsByPerson.size}
|
||||||
/>
|
/>
|
||||||
|
{teamCollabSessions.length > 0 && (
|
||||||
|
<TabButton
|
||||||
|
active={activeTab === 'team'}
|
||||||
|
onClick={() => setActiveTab('team')}
|
||||||
|
icon="🏢"
|
||||||
|
label="Équipe"
|
||||||
|
count={teamCollabSessions.length}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<TypeFilterDropdown
|
<TypeFilterDropdown
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
setActiveTab={setActiveTab}
|
setActiveTab={setActiveTab}
|
||||||
@@ -281,6 +297,7 @@ export function WorkshopTabs({
|
|||||||
'year-review': yearReviewSessions.length,
|
'year-review': yearReviewSessions.length,
|
||||||
'weekly-checkin': weeklyCheckInSessions.length,
|
'weekly-checkin': weeklyCheckInSessions.length,
|
||||||
weather: weatherSessions.length,
|
weather: weatherSessions.length,
|
||||||
|
team: teamCollabSessions.length,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -312,6 +329,28 @@ export function WorkshopTabs({
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
) : activeTab === 'team' ? (
|
||||||
|
teamCollabSessions.length === 0 ? (
|
||||||
|
<div className="text-center py-12 text-muted">
|
||||||
|
Aucun atelier de vos collaborateurs d'équipe (non partagés avec vous)
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-8">
|
||||||
|
<section>
|
||||||
|
<h2 className="text-lg font-semibold text-muted mb-4">
|
||||||
|
🏢 Ateliers de l'équipe – non partagés ({teamCollabSessions.length})
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-muted mb-4">
|
||||||
|
En tant qu'admin d'équipe, vous voyez les ateliers de vos collaborateurs qui ne vous sont pas encore partagés.
|
||||||
|
</p>
|
||||||
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{teamCollabSessions.map((s) => (
|
||||||
|
<SessionCard key={s.id} session={s} isTeamCollab />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
) : filteredSessions.length === 0 ? (
|
) : filteredSessions.length === 0 ? (
|
||||||
<div className="text-center py-12 text-muted">Aucun atelier de ce type pour le moment</div>
|
<div className="text-center py-12 text-muted">Aucun atelier de ce type pour le moment</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -343,6 +382,20 @@ export function WorkshopTabs({
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Team collab sessions (non-shared) - grayed out, admin view only */}
|
||||||
|
{activeTab === 'all' && teamCollabFiltered.length > 0 && (
|
||||||
|
<section>
|
||||||
|
<h2 className="text-lg font-semibold text-muted mb-4">
|
||||||
|
🏢 Équipe – non partagés ({teamCollabFiltered.length})
|
||||||
|
</h2>
|
||||||
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{teamCollabFiltered.map((s) => (
|
||||||
|
<SessionCard key={s.id} session={s} isTeamCollab />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -470,7 +523,7 @@ function TabButton({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SessionCard({ session }: { session: AnySession }) {
|
function SessionCard({ session, isTeamCollab = false }: { session: AnySession; isTeamCollab?: boolean }) {
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
const [showEditModal, setShowEditModal] = useState(false);
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
@@ -562,11 +615,8 @@ function SessionCard({ session }: { session: AnySession }) {
|
|||||||
|
|
||||||
const editParticipantLabel = workshop.participantLabel;
|
const editParticipantLabel = workshop.participantLabel;
|
||||||
|
|
||||||
return (
|
const cardContent = (
|
||||||
<>
|
<Card hover={!isTeamCollab} className={`h-full p-4 relative overflow-hidden ${isTeamCollab ? 'opacity-60' : ''}`}>
|
||||||
<div className="relative group">
|
|
||||||
<Link href={href}>
|
|
||||||
<Card hover className="h-full p-4 relative overflow-hidden">
|
|
||||||
{/* Accent bar */}
|
{/* Accent bar */}
|
||||||
<div
|
<div
|
||||||
className="absolute top-0 left-0 right-0 h-1"
|
className="absolute top-0 left-0 right-0 h-1"
|
||||||
@@ -677,7 +727,21 @@ function SessionCard({ session }: { session: AnySession }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="relative group">
|
||||||
|
{isTeamCollab ? (
|
||||||
|
<div
|
||||||
|
className="cursor-default"
|
||||||
|
title="Atelier non partagé avec vous – visible en tant qu'admin d'équipe"
|
||||||
|
>
|
||||||
|
{cardContent}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Link href={href}>{cardContent}</Link>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Action buttons - only for owner */}
|
{/* Action buttons - only for owner */}
|
||||||
{session.isOwner && (
|
{session.isOwner && (
|
||||||
|
|||||||
@@ -1,10 +1,25 @@
|
|||||||
import { Suspense } from 'react';
|
import { Suspense } from 'react';
|
||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
import { getSessionsByUserId } from '@/services/sessions';
|
import {
|
||||||
import { getMotivatorSessionsByUserId } from '@/services/moving-motivators';
|
getSessionsByUserId,
|
||||||
import { getYearReviewSessionsByUserId } from '@/services/year-review';
|
getTeamCollaboratorSessionsForAdmin as getTeamSwotSessions,
|
||||||
import { getWeeklyCheckInSessionsByUserId } from '@/services/weekly-checkin';
|
} from '@/services/sessions';
|
||||||
import { getWeatherSessionsByUserId } from '@/services/weather';
|
import {
|
||||||
|
getMotivatorSessionsByUserId,
|
||||||
|
getTeamCollaboratorSessionsForAdmin as getTeamMotivatorSessions,
|
||||||
|
} from '@/services/moving-motivators';
|
||||||
|
import {
|
||||||
|
getYearReviewSessionsByUserId,
|
||||||
|
getTeamCollaboratorSessionsForAdmin as getTeamYearReviewSessions,
|
||||||
|
} from '@/services/year-review';
|
||||||
|
import {
|
||||||
|
getWeeklyCheckInSessionsByUserId,
|
||||||
|
getTeamCollaboratorSessionsForAdmin as getTeamWeeklyCheckInSessions,
|
||||||
|
} from '@/services/weekly-checkin';
|
||||||
|
import {
|
||||||
|
getWeatherSessionsByUserId,
|
||||||
|
getTeamCollaboratorSessionsForAdmin as getTeamWeatherSessions,
|
||||||
|
} from '@/services/weather';
|
||||||
import { Card } from '@/components/ui';
|
import { Card } from '@/components/ui';
|
||||||
import { withWorkshopType } from '@/lib/workshops';
|
import { withWorkshopType } from '@/lib/workshops';
|
||||||
import { WorkshopTabs } from './WorkshopTabs';
|
import { WorkshopTabs } from './WorkshopTabs';
|
||||||
@@ -36,15 +51,30 @@ export default async function SessionsPage() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch SWOT, Moving Motivators, Year Review, Weekly Check-in, and Weather sessions
|
// Fetch sessions (owned + shared) and team collab sessions (for team admins, non-shared)
|
||||||
const [swotSessions, motivatorSessions, yearReviewSessions, weeklyCheckInSessions, weatherSessions] =
|
const [
|
||||||
await Promise.all([
|
swotSessions,
|
||||||
getSessionsByUserId(session.user.id),
|
motivatorSessions,
|
||||||
getMotivatorSessionsByUserId(session.user.id),
|
yearReviewSessions,
|
||||||
getYearReviewSessionsByUserId(session.user.id),
|
weeklyCheckInSessions,
|
||||||
getWeeklyCheckInSessionsByUserId(session.user.id),
|
weatherSessions,
|
||||||
getWeatherSessionsByUserId(session.user.id),
|
teamSwotSessions,
|
||||||
]);
|
teamMotivatorSessions,
|
||||||
|
teamYearReviewSessions,
|
||||||
|
teamWeeklyCheckInSessions,
|
||||||
|
teamWeatherSessions,
|
||||||
|
] = await Promise.all([
|
||||||
|
getSessionsByUserId(session.user.id),
|
||||||
|
getMotivatorSessionsByUserId(session.user.id),
|
||||||
|
getYearReviewSessionsByUserId(session.user.id),
|
||||||
|
getWeeklyCheckInSessionsByUserId(session.user.id),
|
||||||
|
getWeatherSessionsByUserId(session.user.id),
|
||||||
|
getTeamSwotSessions(session.user.id),
|
||||||
|
getTeamMotivatorSessions(session.user.id),
|
||||||
|
getTeamYearReviewSessions(session.user.id),
|
||||||
|
getTeamWeeklyCheckInSessions(session.user.id),
|
||||||
|
getTeamWeatherSessions(session.user.id),
|
||||||
|
]);
|
||||||
|
|
||||||
// Add workshopType to each session for unified display
|
// Add workshopType to each session for unified display
|
||||||
const allSwotSessions = withWorkshopType(swotSessions, 'swot');
|
const allSwotSessions = withWorkshopType(swotSessions, 'swot');
|
||||||
@@ -53,6 +83,12 @@ export default async function SessionsPage() {
|
|||||||
const allWeeklyCheckInSessions = withWorkshopType(weeklyCheckInSessions, 'weekly-checkin');
|
const allWeeklyCheckInSessions = withWorkshopType(weeklyCheckInSessions, 'weekly-checkin');
|
||||||
const allWeatherSessions = withWorkshopType(weatherSessions, 'weather');
|
const allWeatherSessions = withWorkshopType(weatherSessions, 'weather');
|
||||||
|
|
||||||
|
const teamSwotWithType = withWorkshopType(teamSwotSessions, 'swot');
|
||||||
|
const teamMotivatorWithType = withWorkshopType(teamMotivatorSessions, 'motivators');
|
||||||
|
const teamYearReviewWithType = withWorkshopType(teamYearReviewSessions, 'year-review');
|
||||||
|
const teamWeeklyCheckInWithType = withWorkshopType(teamWeeklyCheckInSessions, 'weekly-checkin');
|
||||||
|
const teamWeatherWithType = withWorkshopType(teamWeatherSessions, 'weather');
|
||||||
|
|
||||||
// Combine and sort by updatedAt
|
// Combine and sort by updatedAt
|
||||||
const allSessions = [
|
const allSessions = [
|
||||||
...allSwotSessions,
|
...allSwotSessions,
|
||||||
@@ -99,6 +135,13 @@ export default async function SessionsPage() {
|
|||||||
yearReviewSessions={allYearReviewSessions}
|
yearReviewSessions={allYearReviewSessions}
|
||||||
weeklyCheckInSessions={allWeeklyCheckInSessions}
|
weeklyCheckInSessions={allWeeklyCheckInSessions}
|
||||||
weatherSessions={allWeatherSessions}
|
weatherSessions={allWeatherSessions}
|
||||||
|
teamCollabSessions={[
|
||||||
|
...teamSwotWithType,
|
||||||
|
...teamMotivatorWithType,
|
||||||
|
...teamYearReviewWithType,
|
||||||
|
...teamWeeklyCheckInWithType,
|
||||||
|
...teamWeatherWithType,
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ export const WORKSHOP_TYPE_IDS = [
|
|||||||
|
|
||||||
export type WorkshopTypeId = (typeof WORKSHOP_TYPE_IDS)[number];
|
export type WorkshopTypeId = (typeof WORKSHOP_TYPE_IDS)[number];
|
||||||
|
|
||||||
export type WorkshopTabType = WorkshopTypeId | 'all' | 'byPerson';
|
export type WorkshopTabType = WorkshopTypeId | 'all' | 'byPerson' | 'team';
|
||||||
|
|
||||||
export const VALID_TAB_PARAMS: WorkshopTabType[] = [
|
export const VALID_TAB_PARAMS: WorkshopTabType[] = [
|
||||||
'all',
|
'all',
|
||||||
...WORKSHOP_TYPE_IDS,
|
...WORKSHOP_TYPE_IDS,
|
||||||
'byPerson',
|
'byPerson',
|
||||||
|
'team',
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface WorkshopConfig {
|
export interface WorkshopConfig {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { prisma } from '@/services/database';
|
import { prisma } from '@/services/database';
|
||||||
import { resolveCollaborator } from '@/services/auth';
|
import { resolveCollaborator } from '@/services/auth';
|
||||||
|
import { getTeamMemberIdsForAdminTeams } from '@/services/teams';
|
||||||
import type { ShareRole, MotivatorType } from '@prisma/client';
|
import type { ShareRole, MotivatorType } from '@prisma/client';
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -76,6 +77,43 @@ export async function getMotivatorSessionsByUserId(userId: string) {
|
|||||||
return sessionsWithResolved;
|
return sessionsWithResolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sessions owned by team members (where user is team admin) that are NOT shared with the user. */
|
||||||
|
export async function getTeamCollaboratorSessionsForAdmin(userId: string) {
|
||||||
|
const teamMemberIds = await getTeamMemberIdsForAdminTeams(userId);
|
||||||
|
if (teamMemberIds.length === 0) return [];
|
||||||
|
|
||||||
|
const sessions = await prisma.movingMotivatorsSession.findMany({
|
||||||
|
where: {
|
||||||
|
userId: { in: teamMemberIds },
|
||||||
|
shares: { none: { userId } },
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: { select: { id: true, name: true, email: true } },
|
||||||
|
shares: {
|
||||||
|
include: {
|
||||||
|
user: { select: { id: true, name: true, email: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_count: { select: { cards: true } },
|
||||||
|
},
|
||||||
|
orderBy: { updatedAt: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const withRole = sessions.map((s) => ({
|
||||||
|
...s,
|
||||||
|
isOwner: false as const,
|
||||||
|
role: 'VIEWER' as const,
|
||||||
|
isTeamCollab: true as const,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
withRole.map(async (s) => ({
|
||||||
|
...s,
|
||||||
|
resolvedParticipant: await resolveCollaborator(s.participant),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getMotivatorSessionById(sessionId: string, userId: string) {
|
export async function getMotivatorSessionById(sessionId: string, userId: string) {
|
||||||
// Check if user owns the session OR has it shared
|
// Check if user owns the session OR has it shared
|
||||||
const session = await prisma.movingMotivatorsSession.findFirst({
|
const session = await prisma.movingMotivatorsSession.findFirst({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { prisma } from '@/services/database';
|
import { prisma } from '@/services/database';
|
||||||
import { resolveCollaborator } from '@/services/auth';
|
import { resolveCollaborator } from '@/services/auth';
|
||||||
|
import { getTeamMemberIdsForAdminTeams } from '@/services/teams';
|
||||||
import type { SwotCategory, ShareRole } from '@prisma/client';
|
import type { SwotCategory, ShareRole } from '@prisma/client';
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -78,6 +79,48 @@ export async function getSessionsByUserId(userId: string) {
|
|||||||
return sessionsWithResolved;
|
return sessionsWithResolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sessions owned by team members (where user is team admin) that are NOT shared with the user. */
|
||||||
|
export async function getTeamCollaboratorSessionsForAdmin(userId: string) {
|
||||||
|
const teamMemberIds = await getTeamMemberIdsForAdminTeams(userId);
|
||||||
|
if (teamMemberIds.length === 0) return [];
|
||||||
|
|
||||||
|
const sessions = await prisma.session.findMany({
|
||||||
|
where: {
|
||||||
|
userId: { in: teamMemberIds },
|
||||||
|
shares: { none: { userId } },
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: { select: { id: true, name: true, email: true } },
|
||||||
|
shares: {
|
||||||
|
include: {
|
||||||
|
user: { select: { id: true, name: true, email: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
items: true,
|
||||||
|
actions: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: { updatedAt: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const withRole = sessions.map((s) => ({
|
||||||
|
...s,
|
||||||
|
isOwner: false as const,
|
||||||
|
role: 'VIEWER' as const,
|
||||||
|
isTeamCollab: true as const,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
withRole.map(async (s) => ({
|
||||||
|
...s,
|
||||||
|
resolvedCollaborator: await resolveCollaborator(s.collaborator),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getSessionById(sessionId: string, userId: string) {
|
export async function getSessionById(sessionId: string, userId: string) {
|
||||||
// Check if user owns the session OR has it shared
|
// Check if user owns the session OR has it shared
|
||||||
const session = await prisma.session.findFirst({
|
const session = await prisma.session.findFirst({
|
||||||
|
|||||||
@@ -244,6 +244,28 @@ export async function getTeamMember(teamId: string, userId: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns user IDs of all members in teams where the given user is ADMIN (excluding self). */
|
||||||
|
export async function getTeamMemberIdsForAdminTeams(userId: string): Promise<string[]> {
|
||||||
|
const adminTeams = await prisma.teamMember.findMany({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
role: 'ADMIN',
|
||||||
|
},
|
||||||
|
select: { teamId: true },
|
||||||
|
});
|
||||||
|
if (adminTeams.length === 0) return [];
|
||||||
|
|
||||||
|
const members = await prisma.teamMember.findMany({
|
||||||
|
where: {
|
||||||
|
teamId: { in: adminTeams.map((t) => t.teamId) },
|
||||||
|
userId: { not: userId },
|
||||||
|
},
|
||||||
|
select: { userId: true },
|
||||||
|
distinct: ['userId'],
|
||||||
|
});
|
||||||
|
return members.map((m) => m.userId);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getTeamMemberById(teamMemberId: string) {
|
export async function getTeamMemberById(teamMemberId: string) {
|
||||||
return prisma.teamMember.findUnique({
|
return prisma.teamMember.findUnique({
|
||||||
where: { id: teamMemberId },
|
where: { id: teamMemberId },
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { prisma } from '@/services/database';
|
import { prisma } from '@/services/database';
|
||||||
|
import { getTeamMemberIdsForAdminTeams } from '@/services/teams';
|
||||||
import type { ShareRole } from '@prisma/client';
|
import type { ShareRole } from '@prisma/client';
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -67,6 +68,36 @@ export async function getWeatherSessionsByUserId(userId: string) {
|
|||||||
return allSessions;
|
return allSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sessions owned by team members (where user is team admin) that are NOT shared with the user. */
|
||||||
|
export async function getTeamCollaboratorSessionsForAdmin(userId: string) {
|
||||||
|
const teamMemberIds = await getTeamMemberIdsForAdminTeams(userId);
|
||||||
|
if (teamMemberIds.length === 0) return [];
|
||||||
|
|
||||||
|
const sessions = await prisma.weatherSession.findMany({
|
||||||
|
where: {
|
||||||
|
userId: { in: teamMemberIds },
|
||||||
|
shares: { none: { userId } },
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: { select: { id: true, name: true, email: true } },
|
||||||
|
shares: {
|
||||||
|
include: {
|
||||||
|
user: { select: { id: true, name: true, email: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_count: { select: { entries: true } },
|
||||||
|
},
|
||||||
|
orderBy: { updatedAt: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
return sessions.map((s) => ({
|
||||||
|
...s,
|
||||||
|
isOwner: false as const,
|
||||||
|
role: 'VIEWER' as const,
|
||||||
|
isTeamCollab: true as const,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
export async function getWeatherSessionById(sessionId: string, userId: string) {
|
export async function getWeatherSessionById(sessionId: string, userId: string) {
|
||||||
// Check if user owns the session OR has it shared
|
// Check if user owns the session OR has it shared
|
||||||
const session = await prisma.weatherSession.findFirst({
|
const session = await prisma.weatherSession.findFirst({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { prisma } from '@/services/database';
|
import { prisma } from '@/services/database';
|
||||||
import { resolveCollaborator } from '@/services/auth';
|
import { resolveCollaborator } from '@/services/auth';
|
||||||
|
import { getTeamMemberIdsForAdminTeams } from '@/services/teams';
|
||||||
import type { ShareRole, WeeklyCheckInCategory, Emotion } from '@prisma/client';
|
import type { ShareRole, WeeklyCheckInCategory, Emotion } from '@prisma/client';
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -76,6 +77,43 @@ export async function getWeeklyCheckInSessionsByUserId(userId: string) {
|
|||||||
return sessionsWithResolved;
|
return sessionsWithResolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sessions owned by team members (where user is team admin) that are NOT shared with the user. */
|
||||||
|
export async function getTeamCollaboratorSessionsForAdmin(userId: string) {
|
||||||
|
const teamMemberIds = await getTeamMemberIdsForAdminTeams(userId);
|
||||||
|
if (teamMemberIds.length === 0) return [];
|
||||||
|
|
||||||
|
const sessions = await prisma.weeklyCheckInSession.findMany({
|
||||||
|
where: {
|
||||||
|
userId: { in: teamMemberIds },
|
||||||
|
shares: { none: { userId } },
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: { select: { id: true, name: true, email: true } },
|
||||||
|
shares: {
|
||||||
|
include: {
|
||||||
|
user: { select: { id: true, name: true, email: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_count: { select: { items: true } },
|
||||||
|
},
|
||||||
|
orderBy: { updatedAt: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const withRole = sessions.map((s) => ({
|
||||||
|
...s,
|
||||||
|
isOwner: false as const,
|
||||||
|
role: 'VIEWER' as const,
|
||||||
|
isTeamCollab: true as const,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
withRole.map(async (s) => ({
|
||||||
|
...s,
|
||||||
|
resolvedParticipant: await resolveCollaborator(s.participant),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getWeeklyCheckInSessionById(sessionId: string, userId: string) {
|
export async function getWeeklyCheckInSessionById(sessionId: string, userId: string) {
|
||||||
// Check if user owns the session OR has it shared
|
// Check if user owns the session OR has it shared
|
||||||
const session = await prisma.weeklyCheckInSession.findFirst({
|
const session = await prisma.weeklyCheckInSession.findFirst({
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { prisma } from '@/services/database';
|
import { prisma } from '@/services/database';
|
||||||
import { resolveCollaborator } from '@/services/auth';
|
import { resolveCollaborator } from '@/services/auth';
|
||||||
|
import { getTeamMemberIdsForAdminTeams } from '@/services/teams';
|
||||||
import type { ShareRole, YearReviewCategory } from '@prisma/client';
|
import type { ShareRole, YearReviewCategory } from '@prisma/client';
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -76,6 +77,43 @@ export async function getYearReviewSessionsByUserId(userId: string) {
|
|||||||
return sessionsWithResolved;
|
return sessionsWithResolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sessions owned by team members (where user is team admin) that are NOT shared with the user. */
|
||||||
|
export async function getTeamCollaboratorSessionsForAdmin(userId: string) {
|
||||||
|
const teamMemberIds = await getTeamMemberIdsForAdminTeams(userId);
|
||||||
|
if (teamMemberIds.length === 0) return [];
|
||||||
|
|
||||||
|
const sessions = await prisma.yearReviewSession.findMany({
|
||||||
|
where: {
|
||||||
|
userId: { in: teamMemberIds },
|
||||||
|
shares: { none: { userId } },
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: { select: { id: true, name: true, email: true } },
|
||||||
|
shares: {
|
||||||
|
include: {
|
||||||
|
user: { select: { id: true, name: true, email: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_count: { select: { items: true } },
|
||||||
|
},
|
||||||
|
orderBy: { updatedAt: 'desc' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const withRole = sessions.map((s) => ({
|
||||||
|
...s,
|
||||||
|
isOwner: false as const,
|
||||||
|
role: 'VIEWER' as const,
|
||||||
|
isTeamCollab: true as const,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
withRole.map(async (s) => ({
|
||||||
|
...s,
|
||||||
|
resolvedParticipant: await resolveCollaborator(s.participant),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getYearReviewSessionById(sessionId: string, userId: string) {
|
export async function getYearReviewSessionById(sessionId: string, userId: string) {
|
||||||
// Check if user owns the session OR has it shared
|
// Check if user owns the session OR has it shared
|
||||||
const session = await prisma.yearReviewSession.findFirst({
|
const session = await prisma.yearReviewSession.findFirst({
|
||||||
|
|||||||
Reference in New Issue
Block a user