Skip to content
Open
22 changes: 14 additions & 8 deletions src/app/committees/[shortName]/Nav.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
'use client'
import { SubPageNavBar, SubPageNavBarItem } from '@/components/NavBar/SubPageNavBar/SubPageNavBar'
import { faArrowLeft, faCog, faInfo, faUsers } from '@fortawesome/free-solid-svg-icons'
import { faArrowLeft, faCog, faInfo, faScroll, faUsers } from '@fortawesome/free-solid-svg-icons'
import { usePathname } from 'next/navigation'
import type { AuthResultTypeAny } from '@/auth/authorizer/AuthResult'

type PropTypes = {
shortName: string
shortName: string,
canReadCommitteeApplication: AuthResultTypeAny
}

export default function Nav({ shortName }: PropTypes) {
export default function Nav({ shortName, canReadCommitteeApplication }: PropTypes) {
const pathname = usePathname()

const settingsPath = `/committees/${shortName}/admin`
const adminPath = `/committees/${shortName}/admin`
const readPeriodesPath = `/committees/${shortName}/applicationPeriods`
const membersPath = `/committees/${shortName}/members`
const aboutPath = `/committees/${shortName}/about`

return (
<SubPageNavBar>
<SubPageNavBarItem icon={faCog} href={settingsPath}>Innstillinger</SubPageNavBarItem>
<SubPageNavBarItem icon={faUsers} href={membersPath}>Members</SubPageNavBarItem>
<SubPageNavBarItem icon={faInfo} href={aboutPath}>About</SubPageNavBarItem>
<SubPageNavBarItem icon={faCog} href={adminPath}>Innstillinger</SubPageNavBarItem>
{canReadCommitteeApplication.authorized &&
<SubPageNavBarItem icon={faScroll} href={readPeriodesPath}>Søknadsperioder</SubPageNavBarItem>
}
<SubPageNavBarItem icon={faUsers} href={membersPath}>Medlemmer</SubPageNavBarItem>
<SubPageNavBarItem icon={faInfo} href={aboutPath}>Om</SubPageNavBarItem>
<SubPageNavBarItem icon={faArrowLeft} href={
pathname === `/committees/${shortName}` ? '/committees' : `/committees/${shortName}`
}>
Back
Tilbake
</SubPageNavBarItem>
</SubPageNavBar>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@use '@/styles/ohma';
//TODO: fix styling to be aligned to the future styling pr


.applicationContainer {
margin-top: 1em;
display: flex;
flex-direction: column;
border: 2px solid hsla(0, 0%, 70%, 0.507);
@include ohma.round;

}

.headingContainer {
display: flex;
align-items: center;

* {
display: inline-block;
margin-inline: 0.1em;
}
}


.applicantName {
color: black;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import styles from './page.module.scss'
import { unwrapActionReturn } from '@/app/redirectToErrorPage'
import { readCommitteeApplicationsInPeriodAction } from '@/services/applications/committeeParticipation/actions'
import ProfilePicture from '@/components/User/ProfilePicture'
import { readSpecialImageAction } from '@/services/images/actions'
import Link from 'next/link'
import type { Image as ImageT } from '@/prisma-generated-pn-types'
export type PropTypes = {
params: Promise<{
shortName: string,
participationId: string,
}>
}


async function getFallbackImageIfNoImage(image: ImageT | null) {
return image ? image : await readSpecialImageAction.bind(
null, { params: { special: 'DEFAULT_PROFILE_IMAGE' } }
)().then(res => {
if (!res.success) throw new Error('Kunne ikke finne standard profilbilde')
return res.data
})
}


export default async function PeriodeCommitteePage({ params }: PropTypes) {
const participationId = parseInt((await params).participationId, 10)
const applications = unwrapActionReturn(
await readCommitteeApplicationsInPeriodAction({ params: { participationId } })
)
if (applications.length === 0) { return 'ingen søknader funnet' }
const sortedApplications = applications.sort((a, b) => a.priority - b.priority)
return (
<div className={styles.applicationsContainer}>
{sortedApplications.map(async (application, index) => (
<div className={styles.applicationContainer} key={index}>
<div className={styles.headingContainer}>
<h3>{application.priority}.</h3>
<ProfilePicture
width={50}
profileImage={(await getFallbackImageIfNoImage(application.user.image))}
className={styles.profilePicture}
/>
<Link className={styles.applicantName} href={`/users/${application.user.username}`}>
<h3>{application.user.firstname} {application.user.lastname}</h3>
</Link>
</div>
<div className={styles.applicationTextContainer}>
<p className={styles.applicationText}>{application.text}</p>
</div>
</div >
))
}
</div >
)
}

25 changes: 25 additions & 0 deletions src/app/committees/[shortName]/applicationPeriods/page.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@use '@/styles/ohma';
//TODO: fix styling to be aligned to the future styling pr



.periodTable{
border: 2px solid hsla(0, 0%, 70%, 0.507);
border-collapse: collapse;
text-align: center;
@include ohma.round;
}


.tableEntry {
border: 2px solid hsla(0, 0%, 70%, 0.507);
padding: 1em;
}

.currentPeriodEntry{
font-weight: bold;
background-color: hsla(120, 100%, 70%, 0.288);
}

.periodHeading{}
.periodSection{}
40 changes: 40 additions & 0 deletions src/app/committees/[shortName]/applicationPeriods/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import styles from './page.module.scss'
import { PeriodSection } from './periodTableSection'
import getCommittee from '@/app/committees/[shortName]/getCommittee'
import { unwrapActionReturn } from '@/app/redirectToErrorPage'
import { readCommitteeParticipatingPeriodAction } from '@/services/applications/committeeParticipation/actions'

export type PropTypes = {
params: Promise<{
shortName: string
}>
}


export default async function ApplicationPeriods({ params }: PropTypes) {
const committee = await getCommittee(params)
const shortName = (await params).shortName
const committeePeriodes = unwrapActionReturn(
await readCommitteeParticipatingPeriodAction({ params: { committeeId: committee.id } })
).sort((a, b) => b.startDate.getTime() - a.startDate.getTime())
if (committeePeriodes.length === 0) { return 'ingen søknadsperioder funnet' }
return (
<table className={styles.periodTable}>
<thead>
<tr className={styles.periodHeading}>
<th className={styles.tableEntry}>Start dato</th>
<th className={styles.tableEntry}>Slutt dato</th>
<th className={styles.tableEntry}>Omprioritering slutt dato</th>
<th className={styles.tableEntry}>Søknader</th>
<th className={styles.tableEntry}>Søknadstall</th>
</tr>
</thead>
<tbody>
{committeePeriodes.map((period, index) => (
<PeriodSection shortName={shortName} key={index} period={period}></PeriodSection>
))
}
</tbody>
</table >
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client' //Use client to show user correct local time
import styles from './page.module.scss'
import Link from 'next/link'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faLink } from '@fortawesome/free-solid-svg-icons'
import type { readCommitteeParticipatingPeriodAction } from '@/services/applications/committeeParticipation/actions'

export type CommitteeParticipationPeriodType =
Pick<Awaited<ReturnType<typeof readCommitteeParticipatingPeriodAction>> & { success: true }, 'data'>['data'][number]


export function PeriodSection({ period, shortName }: { period: CommitteeParticipationPeriodType, shortName: string }) {
const entriesClassName = `${styles.tableEntry} ${period.isOpen && styles.currentPeriodEntry}`
return (
<tr className={styles.periodSection}>
<td className={entriesClassName} >
{period.startDate.toLocaleDateString('en-GB')},
kl: {period.startDate.toLocaleTimeString('en-GB')}
</td>
<td className={entriesClassName} >
{period.endDate.toLocaleDateString('en-GB')},
kl: {period.endDate.toLocaleTimeString('en-GB')}
</td>
<td className={entriesClassName} >
{period.endPriorityDate.toLocaleDateString('en-GB')},
kl: {period.endPriorityDate.toLocaleTimeString('en-GB')}
</td>
<td className={entriesClassName} >
<Link href={`/committees/${shortName}/applicationPeriods/${period.participationId}`} >
<FontAwesomeIcon icon={faLink}>
</FontAwesomeIcon>
</Link>
</td>
<td className={entriesClassName} >{period.applicationCount}</td>
</tr>
)
}
14 changes: 13 additions & 1 deletion src/app/committees/[shortName]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import PageWrapper from '@/components/PageWrapper/PageWrapper'
import CommitteeImage from '@/components/CommitteeImage/CommitteeImage'
import { committeeAuth } from '@/services/groups/committees/auth'
import { ServerSession } from '@/auth/session/ServerSession'
import { committeeParticipationAuth } from '@/services/applications/committeeParticipation/auth'
import type { ReactNode } from 'react'

export type PropTypes = {
Expand All @@ -32,6 +33,14 @@ export default async function Committee({ params, children }: PropTypes) {
await ServerSession.fromNextAuth()
).toJsObject()

const canReadCommitteeApplication = committeeParticipationAuth.readAll.dynamicFields(
{
groupId: committee.groupId,
}).auth(
await ServerSession.fromNextAuth()
).toJsObject()


return (
<>
<BackdropImage image={committeeLogo}>
Expand All @@ -49,7 +58,10 @@ export default async function Committee({ params, children }: PropTypes) {
</div>
</PageWrapper>
</BackdropImage>
<Nav shortName={(await params).shortName} />
<Nav
shortName={(await params).shortName}
canReadCommitteeApplication={canReadCommitteeApplication}
/>
</>
)
}
79 changes: 79 additions & 0 deletions src/prisma/seeder/src/development/seedDevApplicationsAndPeriods.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@

import type { PrismaClient } from '@/prisma-generated-pn-client'


export default async function seedDevApplicationsAndPeriods(prisma: PrismaClient) {
const applicationText = `
Duis dolore minim pariatur quis do ut laboris sit esse laborum quis
sint.Nisi eu consectetur officia irure proident magna culpa sunt.Lorem
reprehenderit pariatur est fugiat ea.Labore aliqua in eu veniam ex velit excepteur
sunt amet amet minim voluptate qui pariatur.Exercitation proident cupidatat adipisicing in incididunt
excepteur id aliquip sit.Dolor velit deserunt pariatur ipsum velit aute eu eiusmod esse.Voluptate veniam
esse nostrud duis elit cillum laborum mollit magna consectetur dolore sit commodo.
`
const committees = await prisma.committee.findMany({})
const users = await prisma.user.findMany({})

const applicationPeriods = await prisma.applicationPeriod.createManyAndReturn({
data: [
{
endDate: new Date('2100-03-25'),
endPriorityDate: new Date('2100-03-28'),
name: 'name1',
startDate: new Date('2026-01-25'),
},
{
endDate: new Date('2025-03-25'),
endPriorityDate: new Date('2025-03-25'),
name: 'name2',
startDate: new Date('2025-03-01'),
},
{
endDate: new Date('2024-03-25'),
endPriorityDate: new Date('2024-03-25'),
name: 'name3',
startDate: new Date('2024-03-01'),
},
{
endDate: new Date('2023-03-25'),
endPriorityDate: new Date('2023-03-25'),
name: 'name4',
startDate: new Date('2023-03-01'),
},

]
})
const promises = applicationPeriods.map(applicationPeriod => (
committees.map(async (committee) => {
const participation = await prisma.committeeParticipationInApplicationPeriod.create({
data: {
applicationPeriod: {
connect: {
id: applicationPeriod.id
}
},
committee: {
connect: {
id: committee.id
}
}

}
})
await prisma.application.createMany({
data: users.map(user => ({
priority: participation.committeeId,
text:
committee.name
+ applicationPeriod.startDate.toUTCString()
+ applicationPeriod.endDate.toUTCString()
+ applicationText,
userId: user.id,
applicationPeriodCommiteeId: participation.id,
applicationPeriodId: participation.applicationPeriodId,
})),
})
})
))
await Promise.all(promises)
}
2 changes: 2 additions & 0 deletions src/prisma/seeder/src/seeder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import seedCabin from './seedCabin'
import seedPermissions from './seedPermissions'
import seedFlairs from './seedFlairs'
import seedInterestGroups from './seedInterestGroups'
import seedDevApplicationsAndPeriods from './development/seedDevApplicationsAndPeriods'
import { prisma } from '@/prisma/client'


Expand Down Expand Up @@ -69,6 +70,7 @@ export default async function seed(
await seedDevCompanies(prisma)
await seedDevJobAds(prisma)
await seedDevShop(prisma)
await seedDevApplicationsAndPeriods(prisma)
await seedDevEvents(prisma)
console.log('seed dev done')
}
7 changes: 7 additions & 0 deletions src/services/applications/committeeParticipation/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use server'

import { committeeParticipationOperations } from './operations'
import { makeAction } from '@/services/serverAction'

export const readCommitteeApplicationsInPeriodAction = makeAction(committeeParticipationOperations.read)
export const readCommitteeParticipatingPeriodAction = makeAction(committeeParticipationOperations.readAll)
6 changes: 6 additions & 0 deletions src/services/applications/committeeParticipation/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { RequirePermissionOrGroupAdmin } from '@/auth/authorizer/RequirePermissionOrGroupAdmin'

export const committeeParticipationAuth = {
read: RequirePermissionOrGroupAdmin.staticFields({ permission: 'APPLICATION_ADMIN' }),
readAll: RequirePermissionOrGroupAdmin.staticFields({ permission: 'APPLICATION_ADMIN' }),
}
Loading
Loading