@@ -100,6 +100,119 @@ export class AdminOrganizationsService {
100100 } ;
101101 }
102102
103+ async getOrgActivity ( options : {
104+ inactiveDays : number ;
105+ hasAccess ?: boolean ;
106+ onboarded ?: boolean ;
107+ page : number ;
108+ limit : number ;
109+ } ) {
110+ const { inactiveDays, hasAccess, onboarded, page, limit } = options ;
111+ const skip = ( page - 1 ) * limit ;
112+ const cutoff = new Date ( ) ;
113+ cutoff . setDate ( cutoff . getDate ( ) - inactiveDays ) ;
114+
115+ const where : Record < string , unknown > = { } ;
116+ if ( hasAccess !== undefined ) where . hasAccess = hasAccess ;
117+ if ( onboarded !== undefined ) where . onboardingCompleted = onboarded ;
118+
119+ const [ organizations , total ] = await Promise . all ( [
120+ db . organization . findMany ( {
121+ where,
122+ select : {
123+ id : true ,
124+ name : true ,
125+ createdAt : true ,
126+ hasAccess : true ,
127+ onboardingCompleted : true ,
128+ _count : { select : { members : true , tasks : true , policy : true , auditLog : true } } ,
129+ members : {
130+ where : { deactivated : false } ,
131+ select : {
132+ role : true ,
133+ user : {
134+ select : {
135+ id : true ,
136+ name : true ,
137+ email : true ,
138+ sessions : {
139+ orderBy : { updatedAt : 'desc' as const } ,
140+ take : 1 ,
141+ select : { updatedAt : true } ,
142+ } ,
143+ } ,
144+ } ,
145+ } ,
146+ } ,
147+ auditLog : {
148+ orderBy : { timestamp : 'desc' as const } ,
149+ take : 1 ,
150+ select : { timestamp : true } ,
151+ } ,
152+ } ,
153+ orderBy : { createdAt : 'desc' } ,
154+ skip,
155+ take : limit ,
156+ } ) ,
157+ db . organization . count ( { where } ) ,
158+ ] ) ;
159+
160+ // Post-process to find last activity per org
161+ const data = organizations . map ( ( org ) => {
162+ let lastSession : Date | null = null ;
163+ let owner : { id : string ; name : string ; email : string } | null = null ;
164+
165+ for ( const member of org . members ) {
166+ const sess = member . user ?. sessions ?. [ 0 ] ?. updatedAt ;
167+ if ( sess && ( ! lastSession || sess > lastSession ) ) {
168+ lastSession = sess ;
169+ }
170+ if ( member . role ?. includes ( 'owner' ) && ! owner ) {
171+ owner = { id : member . user . id , name : member . user . name , email : member . user . email } ;
172+ }
173+ }
174+
175+ const lastAuditLog = org . auditLog ?. [ 0 ] ?. timestamp ?? null ;
176+ const lastActivity = [ lastSession , lastAuditLog ]
177+ . filter ( Boolean )
178+ . sort ( ( a , b ) => ( b as Date ) . getTime ( ) - ( a as Date ) . getTime ( ) ) [ 0 ] as Date | undefined ;
179+
180+ const isActive = lastActivity ? lastActivity >= cutoff : false ;
181+
182+ return {
183+ id : org . id ,
184+ name : org . name ,
185+ createdAt : org . createdAt ,
186+ hasAccess : org . hasAccess ,
187+ onboardingCompleted : org . onboardingCompleted ,
188+ memberCount : org . _count . members ,
189+ taskCount : org . _count . tasks ,
190+ policyCount : org . _count . policy ,
191+ auditLogCount : org . _count . auditLog ,
192+ owner,
193+ lastSession : lastSession ?. toISOString ( ) ?? null ,
194+ lastAuditLog : lastAuditLog ? ( lastAuditLog as Date ) . toISOString ( ) : null ,
195+ lastActivity : lastActivity ?. toISOString ( ) ?? null ,
196+ isActive,
197+ } ;
198+ } ) ;
199+
200+ const activeCount = data . filter ( ( d ) => d . isActive ) . length ;
201+ const inactiveCount = data . filter ( ( d ) => ! d . isActive ) . length ;
202+
203+ return {
204+ data,
205+ total,
206+ page,
207+ limit,
208+ summary : {
209+ inactiveDays,
210+ activeInPage : activeCount ,
211+ inactiveInPage : inactiveCount ,
212+ } ,
213+ } ;
214+ }
215+
103216 async getOrganization ( id : string ) {
104217 const org = await db . organization . findUnique ( {
105218 where : { id } ,
0 commit comments