From f3bddfc58816fb40e9331ac34b0b4e3c98fe4a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Silje=20H=C3=A5heim?= Date: Wed, 18 Feb 2026 21:44:17 +0100 Subject: [PATCH 1/7] feat: add CompanyListItem component --- .../web/src/app/bedrifter/CompanyListItem.tsx | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 apps/web/src/app/bedrifter/CompanyListItem.tsx diff --git a/apps/web/src/app/bedrifter/CompanyListItem.tsx b/apps/web/src/app/bedrifter/CompanyListItem.tsx new file mode 100644 index 0000000000..f48d3c9974 --- /dev/null +++ b/apps/web/src/app/bedrifter/CompanyListItem.tsx @@ -0,0 +1,99 @@ +import type { + Attendance, + Company, + Event, + JobListing, +} from "@dotkomonline/types"; +import { Title, cn, Text, Badge } from "@dotkomonline/ui"; +import { IconBriefcase, IconMapPin } from "@tabler/icons-react"; +import { createEventPageUrl } from "@dotkomonline/utils"; +import { isPast } from "date-fns"; +import Link from "next/link"; +import type { FC } from "react"; +import Image from "next/image"; +import { PlaceHolderImage } from "@/components/atoms/PlaceHolderImage"; +import { title } from "process"; +import { DividerHorizontalIcon } from "@radix-ui/react-icons"; + +export interface CompanyListItemProps { + company: Company; + hasJobListings: boolean; +} + +export const CompanyListItem: FC = ({ + company, + hasJobListings, +}: CompanyListItemProps) => { + return ( + +
+
+ {company.imageUrl ? ( + {company.name} + ) : ( + + )} +
+
+
+
+ + {company.name} + +
+
+
+ + + {company.location} + +
+ {hasJobListings && ( +
+ + + Har ledig stilling{" "} + +
+ )} +
+
+ + ); +}; + +export const EventListItemSkeleton: FC = () => { + return ( +
+
+ +
+
+ +
+
+
+
+ +
+
+
+
+
+
+ ); +}; From cce91d18b2b79d4a2bb25690dcaa1fa319745a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Silje=20H=C3=A5heim?= Date: Wed, 18 Feb 2026 21:52:55 +0100 Subject: [PATCH 2/7] feat: render companies with CompanyListItem and integrate job listings --- .../rpc/src/modules/company/company-router.ts | 133 +++++++++++++----- .../src/modules/company/company-service.ts | 109 +++++++++----- .../job-listing/job-listing-repository.ts | 62 ++++---- apps/web/src/app/bedrifter/page.tsx | 48 +++++-- 4 files changed, 240 insertions(+), 112 deletions(-) diff --git a/apps/rpc/src/modules/company/company-router.ts b/apps/rpc/src/modules/company/company-router.ts index 4078918e3d..0344eaddb0 100644 --- a/apps/rpc/src/modules/company/company-router.ts +++ b/apps/rpc/src/modules/company/company-router.ts @@ -1,14 +1,23 @@ -import type { PresignedPost } from "@aws-sdk/s3-presigned-post" -import { CompanySchema, CompanyWriteSchema } from "@dotkomonline/types" -import type { inferProcedureInput, inferProcedureOutput } from "@trpc/server" -import { z } from "zod" -import { isEditor } from "../../authorization" -import { withAuditLogEntry, withAuthentication, withAuthorization, withDatabaseTransaction } from "../../middlewares" -import { PaginateInputSchema } from "../../query" -import { procedure, t } from "../../trpc" +import type { PresignedPost } from "@aws-sdk/s3-presigned-post"; +import { CompanySchema, CompanyWriteSchema } from "@dotkomonline/types"; +import type { inferProcedureInput, inferProcedureOutput } from "@trpc/server"; +import { z } from "zod"; +import { isEditor } from "../../authorization"; +import { + withAuditLogEntry, + withAuthentication, + withAuthorization, + withDatabaseTransaction, +} from "../../middlewares"; +import { PaginateInputSchema } from "../../query"; +import { procedure, t } from "../../trpc"; -export type CreateCompanyInput = inferProcedureInput -export type CreateCompanyOutput = inferProcedureOutput +export type CreateCompanyInput = inferProcedureInput< + typeof createCompanyProcedure +>; +export type CreateCompanyOutput = inferProcedureOutput< + typeof createCompanyProcedure +>; const createCompanyProcedure = procedure .input(CompanyWriteSchema) .use(withAuthentication()) @@ -16,76 +25,123 @@ const createCompanyProcedure = procedure .use(withDatabaseTransaction()) .use(withAuditLogEntry()) .mutation(async ({ input, ctx }) => { - return ctx.companyService.create(ctx.handle, input) - }) + return ctx.companyService.create(ctx.handle, input); + }); -export type EditCompanyInput = inferProcedureInput -export type EditCompanyOutput = inferProcedureOutput +export type EditCompanyInput = inferProcedureInput; +export type EditCompanyOutput = inferProcedureOutput< + typeof editCompanyProcedure +>; const editCompanyProcedure = procedure .input( z.object({ id: CompanySchema.shape.id, input: CompanyWriteSchema, - }) + }), ) .use(withAuthentication()) .use(withAuthorization(isEditor())) .use(withDatabaseTransaction()) .use(withAuditLogEntry()) .mutation(async ({ input, ctx }) => { - return ctx.companyService.update(ctx.handle, input.id, input.input) - }) + return ctx.companyService.update(ctx.handle, input.id, input.input); + }); -export type AllCompaniesInput = inferProcedureInput -export type AllCompaniesOutput = inferProcedureOutput +export type AllCompaniesInput = inferProcedureInput< + typeof allCompaniesProcedure +>; +export type AllCompaniesOutput = inferProcedureOutput< + typeof allCompaniesProcedure +>; const allCompaniesProcedure = procedure .input(PaginateInputSchema) .use(withDatabaseTransaction()) - .query(async ({ input, ctx }) => ctx.companyService.findMany(ctx.handle, input)) + .query(async ({ input, ctx }) => + ctx.companyService.findMany(ctx.handle, input), + ); -export type FindCompanyByIdInput = inferProcedureInput -export type FindCompanyByIdOutput = inferProcedureOutput +export type FindCompanyByIdInput = inferProcedureInput< + typeof findCompanyByIdProcedure +>; +export type FindCompanyByIdOutput = inferProcedureOutput< + typeof findCompanyByIdProcedure +>; const findCompanyByIdProcedure = procedure .input(CompanySchema.shape.id) .use(withDatabaseTransaction()) - .query(async ({ input, ctx }) => ctx.companyService.findById(ctx.handle, input)) + .query(async ({ input, ctx }) => + ctx.companyService.findById(ctx.handle, input), + ); -export type GetCompanyByIdInput = inferProcedureInput -export type GetCompanyByIdOutput = inferProcedureOutput +export type GetCompanyByIdInput = inferProcedureInput< + typeof getCompanyByIdProcedure +>; +export type GetCompanyByIdOutput = inferProcedureOutput< + typeof getCompanyByIdProcedure +>; const getCompanyByIdProcedure = procedure .input(CompanySchema.shape.id) .use(withDatabaseTransaction()) - .query(async ({ input, ctx }) => ctx.companyService.getById(ctx.handle, input)) + .query(async ({ input, ctx }) => + ctx.companyService.getById(ctx.handle, input), + ); -export type FindCompanyBySlugInput = inferProcedureInput -export type FindCompanyBySlugOutput = inferProcedureOutput +export type FindCompanyBySlugInput = inferProcedureInput< + typeof findCompanyBySlugProcedure +>; +export type FindCompanyBySlugOutput = inferProcedureOutput< + typeof findCompanyBySlugProcedure +>; const findCompanyBySlugProcedure = procedure .input(CompanySchema.shape.slug) .use(withDatabaseTransaction()) - .query(async ({ input, ctx }) => ctx.companyService.findBySlug(ctx.handle, input)) + .query(async ({ input, ctx }) => + ctx.companyService.findBySlug(ctx.handle, input), + ); -export type GetCompanyBySlugInput = inferProcedureInput -export type GetCompanyBySlugOutput = inferProcedureOutput +export type GetCompanyBySlugInput = inferProcedureInput< + typeof getCompanyBySlugProcedure +>; +export type GetCompanyBySlugOutput = inferProcedureOutput< + typeof getCompanyBySlugProcedure +>; const getCompanyBySlugProcedure = procedure .input(CompanySchema.shape.slug) .use(withDatabaseTransaction()) - .query(async ({ input, ctx }) => ctx.companyService.getBySlug(ctx.handle, input)) + .query(async ({ input, ctx }) => + ctx.companyService.getBySlug(ctx.handle, input), + ); -export type CreateCompanyFileUploadInput = inferProcedureInput -export type CreateCompanyFileUploadOutput = inferProcedureOutput +export type CreateCompanyFileUploadInput = inferProcedureInput< + typeof createCompanyFileUploadProcedure +>; +export type CreateCompanyFileUploadOutput = inferProcedureOutput< + typeof createCompanyFileUploadProcedure +>; const createCompanyFileUploadProcedure = procedure .input( z.object({ filename: z.string(), contentType: z.string(), - }) + }), ) .output(z.custom()) .use(withAuthentication()) .use(withAuthorization(isEditor())) .mutation(async ({ ctx, input }) => { - return ctx.companyService.createFileUpload(input.filename, input.contentType, ctx.principal.subject) - }) + return ctx.companyService.createFileUpload( + input.filename, + input.contentType, + ctx.principal.subject, + ); + }); + +const getCompanyJobListings = procedure + .input(CompanySchema.shape.id) + .use(withDatabaseTransaction()) + .query(async ({ ctx, input }) => { + return ctx.companyService.getJobListings(ctx.handle, input); + }); export const companyRouter = t.router({ create: createCompanyProcedure, @@ -96,4 +152,5 @@ export const companyRouter = t.router({ findBySlug: findCompanyBySlugProcedure, getBySlug: getCompanyBySlugProcedure, createFileUpload: createCompanyFileUploadProcedure, -}) + jobListings: getCompanyJobListings, +}); diff --git a/apps/rpc/src/modules/company/company-service.ts b/apps/rpc/src/modules/company/company-service.ts index d4c00ba727..4dfaee2440 100644 --- a/apps/rpc/src/modules/company/company-service.ts +++ b/apps/rpc/src/modules/company/company-service.ts @@ -1,87 +1,115 @@ -import type { S3Client } from "@aws-sdk/client-s3" -import type { PresignedPost } from "@aws-sdk/s3-presigned-post" -import type { DBHandle } from "@dotkomonline/db" -import type { Company, CompanyId, CompanySlug, CompanyWrite, UserId } from "@dotkomonline/types" -import { createS3PresignedPost, slugify } from "@dotkomonline/utils" -import { NotFoundError } from "../../error" -import type { Pageable } from "../../query" -import type { CompanyRepository } from "./company-repository" +import type { S3Client } from "@aws-sdk/client-s3"; +import type { PresignedPost } from "@aws-sdk/s3-presigned-post"; +import type { DBHandle } from "@dotkomonline/db"; +import type { + Company, + CompanyId, + CompanySlug, + CompanyWrite, + JobListingId, + UserId, +} from "@dotkomonline/types"; +import { createS3PresignedPost, slugify } from "@dotkomonline/utils"; +import { NotFoundError } from "../../error"; +import type { Pageable } from "../../query"; +import type { CompanyRepository } from "./company-repository"; +import { + getJobListingService, + JobListingService, +} from "../job-listing/job-listing-service"; export interface CompanyService { - findById(handle: DBHandle, companyId: CompanyId): Promise + findById(handle: DBHandle, companyId: CompanyId): Promise; /** * Get a company by its id * * @throws {NotFoundError} if the company does not exist */ - getById(handle: DBHandle, companyId: CompanyId): Promise - findBySlug(handle: DBHandle, companySlug: CompanySlug): Promise + getById(handle: DBHandle, companyId: CompanyId): Promise; + findBySlug( + handle: DBHandle, + companySlug: CompanySlug, + ): Promise; /** * Get a company by its slug * * @throws {NotFoundError} if the company does not exist */ - getBySlug(handle: DBHandle, companySlug: CompanySlug): Promise - findMany(handle: DBHandle, page: Pageable): Promise - create(handle: DBHandle, data: CompanyWrite): Promise + getBySlug(handle: DBHandle, companySlug: CompanySlug): Promise; + findMany(handle: DBHandle, page: Pageable): Promise; + create(handle: DBHandle, data: CompanyWrite): Promise; /** * Update an existing company * * @throws {NotFoundError} if the company does not exist */ - update(handle: DBHandle, companyId: CompanyId, data: Partial): Promise - createFileUpload(filename: string, contentType: string, createdByUserId: UserId): Promise + update( + handle: DBHandle, + companyId: CompanyId, + data: Partial, + ): Promise; + createFileUpload( + filename: string, + contentType: string, + createdByUserId: UserId, + ): Promise; + + getJobListings( + handle: DBHandle, + companyId: CompanyId, + ): Promise; } export function getCompanyService( companyRepository: CompanyRepository, + jobListingService: JobListingService, s3Client: S3Client, - s3BucketName: string + s3BucketName: string, ): CompanyService { return { async findById(handle, companyId) { - const company = await companyRepository.findById(handle, companyId) - return company + const company = await companyRepository.findById(handle, companyId); + return company; }, async getById(handle, companyId) { - const company = await this.findById(handle, companyId) + const company = await this.findById(handle, companyId); if (!company) { - throw new NotFoundError(`Company(ID=${companyId}) not found`) + throw new NotFoundError(`Company(ID=${companyId}) not found`); } - return company + return company; }, async findBySlug(handle, companySlug) { - const company = await companyRepository.findBySlug(handle, companySlug) - return company + const company = await companyRepository.findBySlug(handle, companySlug); + return company; }, async getBySlug(handle, companySlug) { - const company = await companyRepository.findBySlug(handle, companySlug) + const company = await companyRepository.findBySlug(handle, companySlug); if (!company) { - throw new NotFoundError(`Company(Slug=${companySlug}) not found`) + throw new NotFoundError(`Company(Slug=${companySlug}) not found`); } - return company + return company; }, async findMany(handle, page) { - return await companyRepository.findMany(handle, page) + return await companyRepository.findMany(handle, page); }, async create(handle, payload) { - return await companyRepository.create(handle, payload) + return await companyRepository.create(handle, payload); }, async update(handle, companyId, payload): Promise { - return await companyRepository.update(handle, companyId, payload) + return await companyRepository.update(handle, companyId, payload); }, async createFileUpload(filename, contentType, createdByUserId) { - const uuid = crypto.randomUUID() - const key = `company/${Date.now()}-${uuid}-${slugify(filename)}` + const uuid = crypto.randomUUID(); + const key = `company/${Date.now()}-${uuid}-${slugify(filename)}`; - const maxSizeKiB = 5 * 1024 // 5 MiB, arbitrarily set + const maxSizeKiB = 5 * 1024; // 5 MiB, arbitrarily set return await createS3PresignedPost(s3Client, { bucket: s3BucketName, @@ -89,7 +117,18 @@ export function getCompanyService( maxSizeKiB, contentType, createdByUserId, - }) + }); + }, + + async getJobListings(handle, companyId) { + const jobListings = await jobListingService.findMany( + handle, + { + byCompany: [companyId], + }, + { take: 10 }, + ); + return jobListings.map((jobListing) => jobListing.id); }, - } + }; } diff --git a/apps/rpc/src/modules/job-listing/job-listing-repository.ts b/apps/rpc/src/modules/job-listing/job-listing-repository.ts index ed2530abf6..3ea44b47dd 100644 --- a/apps/rpc/src/modules/job-listing/job-listing-repository.ts +++ b/apps/rpc/src/modules/job-listing/job-listing-repository.ts @@ -1,4 +1,4 @@ -import type { DBHandle } from "@dotkomonline/db" +import type { DBHandle } from "@dotkomonline/db"; import { type CompanyId, type JobListing, @@ -9,27 +9,37 @@ import { JobListingLocationSchema, JobListingSchema, type JobListingWrite, -} from "@dotkomonline/types" -import { parseOrReport } from "../../invariant" -import { type Pageable, pageQuery } from "../../query" +} from "@dotkomonline/types"; +import { parseOrReport } from "../../invariant"; +import { type Pageable, pageQuery } from "../../query"; export interface JobListingRepository { create( handle: DBHandle, companyId: CompanyId, jobListingData: JobListingWrite, - locationIdsData: JobListingLocationId[] - ): Promise + locationIdsData: JobListingLocationId[], + ): Promise; update( handle: DBHandle, jobListingId: JobListingId, jobListingData: Partial, - locationIdsData: JobListingLocationId[] - ): Promise - findById(handle: DBHandle, jobListingId: JobListingId): Promise - findMany(handle: DBHandle, query: JobListingFilterQuery, page: Pageable): Promise - findActiveJobListings(handle: DBHandle, page: Pageable): Promise - findJobListingLocations(handle: DBHandle): Promise + locationIdsData: JobListingLocationId[], + ): Promise; + findById( + handle: DBHandle, + jobListingId: JobListingId, + ): Promise; + findMany( + handle: DBHandle, + query: JobListingFilterQuery, + page: Pageable, + ): Promise; + findActiveJobListings( + handle: DBHandle, + page: Pageable, + ): Promise; + findJobListingLocations(handle: DBHandle): Promise; } export function getJobListingRepository(): JobListingRepository { @@ -53,9 +63,9 @@ export function getJobListingRepository(): JobListingRepository { company: true, locations: true, }, - }) + }); - return parseOrReport(JobListingSchema, listing) + return parseOrReport(JobListingSchema, listing); }, async update(handle, jobListingId, jobListingData, locationIdsData) { @@ -86,9 +96,9 @@ export function getJobListingRepository(): JobListingRepository { company: true, locations: true, }, - }) + }); - return parseOrReport(JobListingSchema, listing) + return parseOrReport(JobListingSchema, listing); }, async findById(handle, jobListingId) { @@ -98,9 +108,9 @@ export function getJobListingRepository(): JobListingRepository { company: true, locations: true, }, - }) + }); - return parseOrReport(JobListingSchema.nullable(), listing) + return parseOrReport(JobListingSchema.nullable(), listing); }, async findMany(handle, query, page) { @@ -131,13 +141,13 @@ export function getJobListingRepository(): JobListingRepository { }, }, include: { company: true, locations: true }, - }) + }); return jobListings.map((listing) => parseOrReport(JobListingSchema, { ...listing, - }) - ) + }), + ); }, async findActiveJobListings(handle, page) { @@ -153,17 +163,17 @@ export function getJobListingRepository(): JobListingRepository { }, include: { company: true, locations: true }, ...pageQuery(page), - }) + }); - return parseOrReport(JobListingSchema.array(), listings) + return parseOrReport(JobListingSchema.array(), listings); }, async findJobListingLocations(handle) { const locations = await handle.jobListingLocation.findMany({ distinct: "name", - }) + }); - return parseOrReport(JobListingLocationSchema.array(), locations) + return parseOrReport(JobListingLocationSchema.array(), locations); }, - } + }; } diff --git a/apps/web/src/app/bedrifter/page.tsx b/apps/web/src/app/bedrifter/page.tsx index 311ac564cf..dc8a9c80bb 100644 --- a/apps/web/src/app/bedrifter/page.tsx +++ b/apps/web/src/app/bedrifter/page.tsx @@ -1,18 +1,40 @@ -import { server } from "@/utils/trpc/server" -import Link from "next/link" +import { server } from "@/utils/trpc/server"; +import Link from "next/link"; +import { CompanyListItem } from "./CompanyListItem"; +import { Title } from "@dotkomonline/ui"; const CompanyPage = async () => { - const data = await server.company.all.query() + const data = await server.company.all.query(); + const { items } = await server.jobListing.findMany.query(); + const jobListings = items ?? []; + + console.log(data); + console.log(items); return ( -
    - {data?.map((company) => ( -
  • - {company.name} -
  • - ))} -
- ) -} +
+
+ + Bedrifter + +
+
    + {data?.map((company) => { + const hasJobListings = jobListings.some( + (job) => job.company.id === company.id, + ); + + return ( + + ); + })} +
+
+ ); +}; -export default CompanyPage +export default CompanyPage; From f33f6f2c84a51d317a1ee37a8ea06f87218d984e Mon Sep 17 00:00:00 2001 From: Sondre Alfnes Date: Wed, 11 Mar 2026 19:12:26 +0100 Subject: [PATCH 3/7] Linting --- .../rpc/src/modules/company/company-router.ts | 129 ++++++------------ .../src/modules/company/company-service.ts | 103 ++++++-------- .../job-listing/job-listing-repository.ts | 62 ++++----- .../web/src/app/bedrifter/CompanyListItem.tsx | 59 +++----- apps/web/src/app/bedrifter/page.tsx | 36 ++--- 5 files changed, 143 insertions(+), 246 deletions(-) diff --git a/apps/rpc/src/modules/company/company-router.ts b/apps/rpc/src/modules/company/company-router.ts index 0344eaddb0..2403623b7b 100644 --- a/apps/rpc/src/modules/company/company-router.ts +++ b/apps/rpc/src/modules/company/company-router.ts @@ -1,23 +1,14 @@ -import type { PresignedPost } from "@aws-sdk/s3-presigned-post"; -import { CompanySchema, CompanyWriteSchema } from "@dotkomonline/types"; -import type { inferProcedureInput, inferProcedureOutput } from "@trpc/server"; -import { z } from "zod"; -import { isEditor } from "../../authorization"; -import { - withAuditLogEntry, - withAuthentication, - withAuthorization, - withDatabaseTransaction, -} from "../../middlewares"; -import { PaginateInputSchema } from "../../query"; -import { procedure, t } from "../../trpc"; +import type { PresignedPost } from "@aws-sdk/s3-presigned-post" +import { CompanySchema, CompanyWriteSchema } from "@dotkomonline/types" +import type { inferProcedureInput, inferProcedureOutput } from "@trpc/server" +import { z } from "zod" +import { isEditor } from "../../authorization" +import { withAuditLogEntry, withAuthentication, withAuthorization, withDatabaseTransaction } from "../../middlewares" +import { PaginateInputSchema } from "../../query" +import { procedure, t } from "../../trpc" -export type CreateCompanyInput = inferProcedureInput< - typeof createCompanyProcedure ->; -export type CreateCompanyOutput = inferProcedureOutput< - typeof createCompanyProcedure ->; +export type CreateCompanyInput = inferProcedureInput +export type CreateCompanyOutput = inferProcedureOutput const createCompanyProcedure = procedure .input(CompanyWriteSchema) .use(withAuthentication()) @@ -25,123 +16,83 @@ const createCompanyProcedure = procedure .use(withDatabaseTransaction()) .use(withAuditLogEntry()) .mutation(async ({ input, ctx }) => { - return ctx.companyService.create(ctx.handle, input); - }); + return ctx.companyService.create(ctx.handle, input) + }) -export type EditCompanyInput = inferProcedureInput; -export type EditCompanyOutput = inferProcedureOutput< - typeof editCompanyProcedure ->; +export type EditCompanyInput = inferProcedureInput +export type EditCompanyOutput = inferProcedureOutput const editCompanyProcedure = procedure .input( z.object({ id: CompanySchema.shape.id, input: CompanyWriteSchema, - }), + }) ) .use(withAuthentication()) .use(withAuthorization(isEditor())) .use(withDatabaseTransaction()) .use(withAuditLogEntry()) .mutation(async ({ input, ctx }) => { - return ctx.companyService.update(ctx.handle, input.id, input.input); - }); + return ctx.companyService.update(ctx.handle, input.id, input.input) + }) -export type AllCompaniesInput = inferProcedureInput< - typeof allCompaniesProcedure ->; -export type AllCompaniesOutput = inferProcedureOutput< - typeof allCompaniesProcedure ->; +export type AllCompaniesInput = inferProcedureInput +export type AllCompaniesOutput = inferProcedureOutput const allCompaniesProcedure = procedure .input(PaginateInputSchema) .use(withDatabaseTransaction()) - .query(async ({ input, ctx }) => - ctx.companyService.findMany(ctx.handle, input), - ); + .query(async ({ input, ctx }) => ctx.companyService.findMany(ctx.handle, input)) -export type FindCompanyByIdInput = inferProcedureInput< - typeof findCompanyByIdProcedure ->; -export type FindCompanyByIdOutput = inferProcedureOutput< - typeof findCompanyByIdProcedure ->; +export type FindCompanyByIdInput = inferProcedureInput +export type FindCompanyByIdOutput = inferProcedureOutput const findCompanyByIdProcedure = procedure .input(CompanySchema.shape.id) .use(withDatabaseTransaction()) - .query(async ({ input, ctx }) => - ctx.companyService.findById(ctx.handle, input), - ); + .query(async ({ input, ctx }) => ctx.companyService.findById(ctx.handle, input)) -export type GetCompanyByIdInput = inferProcedureInput< - typeof getCompanyByIdProcedure ->; -export type GetCompanyByIdOutput = inferProcedureOutput< - typeof getCompanyByIdProcedure ->; +export type GetCompanyByIdInput = inferProcedureInput +export type GetCompanyByIdOutput = inferProcedureOutput const getCompanyByIdProcedure = procedure .input(CompanySchema.shape.id) .use(withDatabaseTransaction()) - .query(async ({ input, ctx }) => - ctx.companyService.getById(ctx.handle, input), - ); + .query(async ({ input, ctx }) => ctx.companyService.getById(ctx.handle, input)) -export type FindCompanyBySlugInput = inferProcedureInput< - typeof findCompanyBySlugProcedure ->; -export type FindCompanyBySlugOutput = inferProcedureOutput< - typeof findCompanyBySlugProcedure ->; +export type FindCompanyBySlugInput = inferProcedureInput +export type FindCompanyBySlugOutput = inferProcedureOutput const findCompanyBySlugProcedure = procedure .input(CompanySchema.shape.slug) .use(withDatabaseTransaction()) - .query(async ({ input, ctx }) => - ctx.companyService.findBySlug(ctx.handle, input), - ); + .query(async ({ input, ctx }) => ctx.companyService.findBySlug(ctx.handle, input)) -export type GetCompanyBySlugInput = inferProcedureInput< - typeof getCompanyBySlugProcedure ->; -export type GetCompanyBySlugOutput = inferProcedureOutput< - typeof getCompanyBySlugProcedure ->; +export type GetCompanyBySlugInput = inferProcedureInput +export type GetCompanyBySlugOutput = inferProcedureOutput const getCompanyBySlugProcedure = procedure .input(CompanySchema.shape.slug) .use(withDatabaseTransaction()) - .query(async ({ input, ctx }) => - ctx.companyService.getBySlug(ctx.handle, input), - ); + .query(async ({ input, ctx }) => ctx.companyService.getBySlug(ctx.handle, input)) -export type CreateCompanyFileUploadInput = inferProcedureInput< - typeof createCompanyFileUploadProcedure ->; -export type CreateCompanyFileUploadOutput = inferProcedureOutput< - typeof createCompanyFileUploadProcedure ->; +export type CreateCompanyFileUploadInput = inferProcedureInput +export type CreateCompanyFileUploadOutput = inferProcedureOutput const createCompanyFileUploadProcedure = procedure .input( z.object({ filename: z.string(), contentType: z.string(), - }), + }) ) .output(z.custom()) .use(withAuthentication()) .use(withAuthorization(isEditor())) .mutation(async ({ ctx, input }) => { - return ctx.companyService.createFileUpload( - input.filename, - input.contentType, - ctx.principal.subject, - ); - }); + return ctx.companyService.createFileUpload(input.filename, input.contentType, ctx.principal.subject) + }) const getCompanyJobListings = procedure .input(CompanySchema.shape.id) .use(withDatabaseTransaction()) .query(async ({ ctx, input }) => { - return ctx.companyService.getJobListings(ctx.handle, input); - }); + return ctx.companyService.getJobListings(ctx.handle, input) + }) export const companyRouter = t.router({ create: createCompanyProcedure, @@ -153,4 +104,4 @@ export const companyRouter = t.router({ getBySlug: getCompanyBySlugProcedure, createFileUpload: createCompanyFileUploadProcedure, jobListings: getCompanyJobListings, -}); +}) diff --git a/apps/rpc/src/modules/company/company-service.ts b/apps/rpc/src/modules/company/company-service.ts index 0e88733373..9b8d35ce60 100644 --- a/apps/rpc/src/modules/company/company-service.ts +++ b/apps/rpc/src/modules/company/company-service.ts @@ -1,114 +1,93 @@ -import type { S3Client } from "@aws-sdk/client-s3"; -import type { PresignedPost } from "@aws-sdk/s3-presigned-post"; -import type { DBHandle } from "@dotkomonline/db"; -import type { - Company, - CompanyId, - CompanySlug, - CompanyWrite, - JobListingId, - UserId, -} from "@dotkomonline/types"; -import { createS3PresignedPost, slugify } from "@dotkomonline/utils"; -import { NotFoundError } from "../../error"; -import type { Pageable } from "../../query"; -import type { CompanyRepository } from "./company-repository"; -import { JobListingService } from "../job-listing/job-listing-service"; +import type { S3Client } from "@aws-sdk/client-s3" +import type { PresignedPost } from "@aws-sdk/s3-presigned-post" +import type { DBHandle } from "@dotkomonline/db" +import type { Company, CompanyId, CompanySlug, CompanyWrite, JobListingId, UserId } from "@dotkomonline/types" +import { createS3PresignedPost, slugify } from "@dotkomonline/utils" +import { NotFoundError } from "../../error" +import type { Pageable } from "../../query" +import type { CompanyRepository } from "./company-repository" +import type { JobListingService } from "../job-listing/job-listing-service" -import { COMPANY_IMAGE_MAX_SIZE_KIB } from "@dotkomonline/types"; +import { COMPANY_IMAGE_MAX_SIZE_KIB } from "@dotkomonline/types" export interface CompanyService { - findById(handle: DBHandle, companyId: CompanyId): Promise; + findById(handle: DBHandle, companyId: CompanyId): Promise /** * Get a company by its id * * @throws {NotFoundError} if the company does not exist */ - getById(handle: DBHandle, companyId: CompanyId): Promise; - findBySlug( - handle: DBHandle, - companySlug: CompanySlug, - ): Promise; + getById(handle: DBHandle, companyId: CompanyId): Promise + findBySlug(handle: DBHandle, companySlug: CompanySlug): Promise /** * Get a company by its slug * * @throws {NotFoundError} if the company does not exist */ - getBySlug(handle: DBHandle, companySlug: CompanySlug): Promise; - findMany(handle: DBHandle, page: Pageable): Promise; - create(handle: DBHandle, data: CompanyWrite): Promise; + getBySlug(handle: DBHandle, companySlug: CompanySlug): Promise + findMany(handle: DBHandle, page: Pageable): Promise + create(handle: DBHandle, data: CompanyWrite): Promise /** * Update an existing company * * @throws {NotFoundError} if the company does not exist */ - update( - handle: DBHandle, - companyId: CompanyId, - data: Partial, - ): Promise; - createFileUpload( - filename: string, - contentType: string, - createdByUserId: UserId, - ): Promise; + update(handle: DBHandle, companyId: CompanyId, data: Partial): Promise + createFileUpload(filename: string, contentType: string, createdByUserId: UserId): Promise - getJobListings( - handle: DBHandle, - companyId: CompanyId, - ): Promise; + getJobListings(handle: DBHandle, companyId: CompanyId): Promise } export function getCompanyService( companyRepository: CompanyRepository, jobListingService: JobListingService, s3Client: S3Client, - s3BucketName: string, + s3BucketName: string ): CompanyService { return { async findById(handle, companyId) { - const company = await companyRepository.findById(handle, companyId); - return company; + const company = await companyRepository.findById(handle, companyId) + return company }, async getById(handle, companyId) { - const company = await this.findById(handle, companyId); + const company = await this.findById(handle, companyId) if (!company) { - throw new NotFoundError(`Company(ID=${companyId}) not found`); + throw new NotFoundError(`Company(ID=${companyId}) not found`) } - return company; + return company }, async findBySlug(handle, companySlug) { - const company = await companyRepository.findBySlug(handle, companySlug); - return company; + const company = await companyRepository.findBySlug(handle, companySlug) + return company }, async getBySlug(handle, companySlug) { - const company = await companyRepository.findBySlug(handle, companySlug); + const company = await companyRepository.findBySlug(handle, companySlug) if (!company) { - throw new NotFoundError(`Company(Slug=${companySlug}) not found`); + throw new NotFoundError(`Company(Slug=${companySlug}) not found`) } - return company; + return company }, async findMany(handle, page) { - return await companyRepository.findMany(handle, page); + return await companyRepository.findMany(handle, page) }, async create(handle, payload) { - return await companyRepository.create(handle, payload); + return await companyRepository.create(handle, payload) }, async update(handle, companyId, payload): Promise { - return await companyRepository.update(handle, companyId, payload); + return await companyRepository.update(handle, companyId, payload) }, async createFileUpload(filename, contentType, createdByUserId) { - const uuid = crypto.randomUUID(); - const key = `company/${Date.now()}-${uuid}-${slugify(filename)}`; + const uuid = crypto.randomUUID() + const key = `company/${Date.now()}-${uuid}-${slugify(filename)}` - const maxSizeKiB = 5 * 1024; // 5 MiB, arbitrarily set + const maxSizeKiB = 5 * 1024 // 5 MiB, arbitrarily set return await createS3PresignedPost(s3Client, { bucket: s3BucketName, @@ -116,7 +95,7 @@ export function getCompanyService( maxSizeKiB: COMPANY_IMAGE_MAX_SIZE_KIB, contentType, createdByUserId, - }); + }) }, async getJobListings(handle, companyId) { @@ -125,9 +104,9 @@ export function getCompanyService( { byCompany: [companyId], }, - { take: 10 }, - ); - return jobListings.map((jobListing) => jobListing.id); + { take: 10 } + ) + return jobListings.map((jobListing) => jobListing.id) }, - }; + } } diff --git a/apps/rpc/src/modules/job-listing/job-listing-repository.ts b/apps/rpc/src/modules/job-listing/job-listing-repository.ts index 3ea44b47dd..ed2530abf6 100644 --- a/apps/rpc/src/modules/job-listing/job-listing-repository.ts +++ b/apps/rpc/src/modules/job-listing/job-listing-repository.ts @@ -1,4 +1,4 @@ -import type { DBHandle } from "@dotkomonline/db"; +import type { DBHandle } from "@dotkomonline/db" import { type CompanyId, type JobListing, @@ -9,37 +9,27 @@ import { JobListingLocationSchema, JobListingSchema, type JobListingWrite, -} from "@dotkomonline/types"; -import { parseOrReport } from "../../invariant"; -import { type Pageable, pageQuery } from "../../query"; +} from "@dotkomonline/types" +import { parseOrReport } from "../../invariant" +import { type Pageable, pageQuery } from "../../query" export interface JobListingRepository { create( handle: DBHandle, companyId: CompanyId, jobListingData: JobListingWrite, - locationIdsData: JobListingLocationId[], - ): Promise; + locationIdsData: JobListingLocationId[] + ): Promise update( handle: DBHandle, jobListingId: JobListingId, jobListingData: Partial, - locationIdsData: JobListingLocationId[], - ): Promise; - findById( - handle: DBHandle, - jobListingId: JobListingId, - ): Promise; - findMany( - handle: DBHandle, - query: JobListingFilterQuery, - page: Pageable, - ): Promise; - findActiveJobListings( - handle: DBHandle, - page: Pageable, - ): Promise; - findJobListingLocations(handle: DBHandle): Promise; + locationIdsData: JobListingLocationId[] + ): Promise + findById(handle: DBHandle, jobListingId: JobListingId): Promise + findMany(handle: DBHandle, query: JobListingFilterQuery, page: Pageable): Promise + findActiveJobListings(handle: DBHandle, page: Pageable): Promise + findJobListingLocations(handle: DBHandle): Promise } export function getJobListingRepository(): JobListingRepository { @@ -63,9 +53,9 @@ export function getJobListingRepository(): JobListingRepository { company: true, locations: true, }, - }); + }) - return parseOrReport(JobListingSchema, listing); + return parseOrReport(JobListingSchema, listing) }, async update(handle, jobListingId, jobListingData, locationIdsData) { @@ -96,9 +86,9 @@ export function getJobListingRepository(): JobListingRepository { company: true, locations: true, }, - }); + }) - return parseOrReport(JobListingSchema, listing); + return parseOrReport(JobListingSchema, listing) }, async findById(handle, jobListingId) { @@ -108,9 +98,9 @@ export function getJobListingRepository(): JobListingRepository { company: true, locations: true, }, - }); + }) - return parseOrReport(JobListingSchema.nullable(), listing); + return parseOrReport(JobListingSchema.nullable(), listing) }, async findMany(handle, query, page) { @@ -141,13 +131,13 @@ export function getJobListingRepository(): JobListingRepository { }, }, include: { company: true, locations: true }, - }); + }) return jobListings.map((listing) => parseOrReport(JobListingSchema, { ...listing, - }), - ); + }) + ) }, async findActiveJobListings(handle, page) { @@ -163,17 +153,17 @@ export function getJobListingRepository(): JobListingRepository { }, include: { company: true, locations: true }, ...pageQuery(page), - }); + }) - return parseOrReport(JobListingSchema.array(), listings); + return parseOrReport(JobListingSchema.array(), listings) }, async findJobListingLocations(handle) { const locations = await handle.jobListingLocation.findMany({ distinct: "name", - }); + }) - return parseOrReport(JobListingLocationSchema.array(), locations); + return parseOrReport(JobListingLocationSchema.array(), locations) }, - }; + } } diff --git a/apps/web/src/app/bedrifter/CompanyListItem.tsx b/apps/web/src/app/bedrifter/CompanyListItem.tsx index f48d3c9974..7991a87c08 100644 --- a/apps/web/src/app/bedrifter/CompanyListItem.tsx +++ b/apps/web/src/app/bedrifter/CompanyListItem.tsx @@ -1,35 +1,27 @@ -import type { - Attendance, - Company, - Event, - JobListing, -} from "@dotkomonline/types"; -import { Title, cn, Text, Badge } from "@dotkomonline/ui"; -import { IconBriefcase, IconMapPin } from "@tabler/icons-react"; -import { createEventPageUrl } from "@dotkomonline/utils"; -import { isPast } from "date-fns"; -import Link from "next/link"; -import type { FC } from "react"; -import Image from "next/image"; -import { PlaceHolderImage } from "@/components/atoms/PlaceHolderImage"; -import { title } from "process"; -import { DividerHorizontalIcon } from "@radix-ui/react-icons"; +import type { Attendance, Company, Event, JobListing } from "@dotkomonline/types" +import { Title, cn, Text, Badge } from "@dotkomonline/ui" +import { IconBriefcase, IconMapPin } from "@tabler/icons-react" +import { createEventPageUrl } from "@dotkomonline/utils" +import { isPast } from "date-fns" +import Link from "next/link" +import type { FC } from "react" +import Image from "next/image" +import { PlaceHolderImage } from "@/components/atoms/PlaceHolderImage" +import { title } from "process" +import { DividerHorizontalIcon } from "@radix-ui/react-icons" export interface CompanyListItemProps { - company: Company; - hasJobListings: boolean; + company: Company + hasJobListings: boolean } -export const CompanyListItem: FC = ({ - company, - hasJobListings, -}: CompanyListItemProps) => { +export const CompanyListItem: FC = ({ company, hasJobListings }: CompanyListItemProps) => { return (
@@ -48,33 +40,26 @@ export const CompanyListItem: FC = ({
- + <Title element="h2" className="font-bold border-b-0 text-lg line-clamp-1 sm:line-clamp-2 "> {company.name}
- - {company.location} - + {company.location}
{hasJobListings && (
- - Har ledig stilling{" "} - + Har ledig stilling
)}
- ); -}; + ) +} export const EventListItemSkeleton: FC = () => { return ( @@ -95,5 +80,5 @@ export const EventListItemSkeleton: FC = () => {
- ); -}; + ) +} diff --git a/apps/web/src/app/bedrifter/page.tsx b/apps/web/src/app/bedrifter/page.tsx index dc8a9c80bb..641b5bddb2 100644 --- a/apps/web/src/app/bedrifter/page.tsx +++ b/apps/web/src/app/bedrifter/page.tsx @@ -1,15 +1,15 @@ -import { server } from "@/utils/trpc/server"; -import Link from "next/link"; -import { CompanyListItem } from "./CompanyListItem"; -import { Title } from "@dotkomonline/ui"; +import { server } from "@/utils/trpc/server" +import Link from "next/link" +import { CompanyListItem } from "./CompanyListItem" +import { Title } from "@dotkomonline/ui" const CompanyPage = async () => { - const data = await server.company.all.query(); - const { items } = await server.jobListing.findMany.query(); - const jobListings = items ?? []; + const data = await server.company.all.query() + const { items } = await server.jobListing.findMany.query() + const jobListings = items ?? [] - console.log(data); - console.log(items); + console.log(data) + console.log(items) return (
@@ -20,21 +20,13 @@ const CompanyPage = async () => {
    {data?.map((company) => { - const hasJobListings = jobListings.some( - (job) => job.company.id === company.id, - ); + const hasJobListings = jobListings.some((job) => job.company.id === company.id) - return ( - - ); + return })}
- ); -}; + ) +} -export default CompanyPage; +export default CompanyPage From 23f24c7011abf224634705e594f63ddf4b4f6b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Silje=20H=C3=A5heim?= Date: Wed, 11 Mar 2026 19:53:21 +0100 Subject: [PATCH 4/7] Add darkmode --- apps/web/src/app/bedrifter/CompanyListItem.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apps/web/src/app/bedrifter/CompanyListItem.tsx b/apps/web/src/app/bedrifter/CompanyListItem.tsx index 7991a87c08..6ac03fba18 100644 --- a/apps/web/src/app/bedrifter/CompanyListItem.tsx +++ b/apps/web/src/app/bedrifter/CompanyListItem.tsx @@ -20,12 +20,12 @@ export const CompanyListItem: FC = ({ company, hasJobListi -
-
+
+
{company.imageUrl ? ( = ({ company, hasJobListi )}
-
-
- + <div className="flex flex-col gap-3 dark:text-gray-200"> + <div className=" sm:block flex gap-1 "> + <Title + element="h2" + className="self-start text-left font-bold border-b-0 text-lg line-clamp-1 sm:line-clamp-2" + > {company.name}
From 1064eff7d70950645f5d2424e013563219afb4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Silje=20H=C3=A5heim?= Date: Wed, 11 Mar 2026 19:54:00 +0100 Subject: [PATCH 5/7] add link in navbar --- apps/web/src/components/Navbar/Navbar.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/web/src/components/Navbar/Navbar.tsx b/apps/web/src/components/Navbar/Navbar.tsx index 9047f7a610..04c771c895 100644 --- a/apps/web/src/components/Navbar/Navbar.tsx +++ b/apps/web/src/components/Navbar/Navbar.tsx @@ -130,6 +130,12 @@ const links: MenuLink[] = [ icon: IconMessage, description: "Interessert i å vise bedriften din for studentene våre? Meld interesse!", }, + { + title: "Bedrifter", + href: "/bedrifter", + icon: IconBriefcase, + description: "Utforsk bedriftene som sammerbeider med Online linjeforening", + }, ], }, ] From 6cd32f193075613515ecfd49b2d091cda4337148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Silje=20H=C3=A5heim?= Date: Wed, 11 Mar 2026 20:18:10 +0100 Subject: [PATCH 6/7] display number of ads for each company --- .../web/src/app/bedrifter/CompanyListItem.tsx | 58 +++++++++++-------- apps/web/src/app/bedrifter/page.tsx | 42 +++++++++----- 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/apps/web/src/app/bedrifter/CompanyListItem.tsx b/apps/web/src/app/bedrifter/CompanyListItem.tsx index 6ac03fba18..f70f3fdfb8 100644 --- a/apps/web/src/app/bedrifter/CompanyListItem.tsx +++ b/apps/web/src/app/bedrifter/CompanyListItem.tsx @@ -1,27 +1,35 @@ -import type { Attendance, Company, Event, JobListing } from "@dotkomonline/types" -import { Title, cn, Text, Badge } from "@dotkomonline/ui" -import { IconBriefcase, IconMapPin } from "@tabler/icons-react" -import { createEventPageUrl } from "@dotkomonline/utils" -import { isPast } from "date-fns" -import Link from "next/link" -import type { FC } from "react" -import Image from "next/image" -import { PlaceHolderImage } from "@/components/atoms/PlaceHolderImage" -import { title } from "process" -import { DividerHorizontalIcon } from "@radix-ui/react-icons" +import type { + Attendance, + Company, + Event, + JobListing, +} from "@dotkomonline/types"; +import { Title, cn, Text, Badge } from "@dotkomonline/ui"; +import { IconBriefcase, IconMapPin } from "@tabler/icons-react"; +import { createEventPageUrl } from "@dotkomonline/utils"; +import { isPast } from "date-fns"; +import Link from "next/link"; +import type { FC } from "react"; +import Image from "next/image"; +import { PlaceHolderImage } from "@/components/atoms/PlaceHolderImage"; +import { title } from "process"; +import { DividerHorizontalIcon } from "@radix-ui/react-icons"; export interface CompanyListItemProps { - company: Company - hasJobListings: boolean + company: Company; + jobListingCount: number; } -export const CompanyListItem: FC = ({ company, hasJobListings }: CompanyListItemProps) => { +export const CompanyListItem: FC = ({ + company, + jobListingCount, +}: CompanyListItemProps) => { return (
@@ -50,19 +58,23 @@ export const CompanyListItem: FC = ({ company, hasJobListi
- {company.location} + + {company.location} +
- {hasJobListings && ( + {jobListingCount > 0 && (
- Har ledig stilling + + {jobListingCount} ledige stillinger +
)}
- ) -} + ); +}; export const EventListItemSkeleton: FC = () => { return ( @@ -83,5 +95,5 @@ export const EventListItemSkeleton: FC = () => {
- ) -} + ); +}; diff --git a/apps/web/src/app/bedrifter/page.tsx b/apps/web/src/app/bedrifter/page.tsx index 641b5bddb2..d0d6705e57 100644 --- a/apps/web/src/app/bedrifter/page.tsx +++ b/apps/web/src/app/bedrifter/page.tsx @@ -1,15 +1,23 @@ -import { server } from "@/utils/trpc/server" -import Link from "next/link" -import { CompanyListItem } from "./CompanyListItem" -import { Title } from "@dotkomonline/ui" +import { server } from "@/utils/trpc/server"; +import Link from "next/link"; +import { CompanyListItem } from "./CompanyListItem"; +import { Title } from "@dotkomonline/ui"; const CompanyPage = async () => { - const data = await server.company.all.query() - const { items } = await server.jobListing.findMany.query() - const jobListings = items ?? [] + const data = await server.company.all.query(); + const { items } = await server.jobListing.findMany.query(); + const jobListings = items ?? []; + const countsByCompanyID = new Map(); - console.log(data) - console.log(items) + for (const job of jobListings) { + countsByCompanyID.set( + job.company.id, + (countsByCompanyID.get(job.company.id) ?? 0) + 1, + ); + } + + console.log(data); + console.log(items); return (
@@ -20,13 +28,19 @@ const CompanyPage = async () => {
    {data?.map((company) => { - const hasJobListings = jobListings.some((job) => job.company.id === company.id) + const jobListingCount = countsByCompanyID.get(company.id) ?? 0; - return + return ( + + ); })}
- ) -} + ); +}; -export default CompanyPage +export default CompanyPage; From ae6411fee9e83b54cb34f8b9beb4e3f721b4c61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Silje=20H=C3=A5heim?= Date: Wed, 25 Mar 2026 17:27:05 +0100 Subject: [PATCH 7/7] css --- apps/web/src/app/bedrifter/CompanyListItem.tsx | 2 +- apps/web/src/app/bedrifter/page.tsx | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/web/src/app/bedrifter/CompanyListItem.tsx b/apps/web/src/app/bedrifter/CompanyListItem.tsx index f70f3fdfb8..64cf205b0b 100644 --- a/apps/web/src/app/bedrifter/CompanyListItem.tsx +++ b/apps/web/src/app/bedrifter/CompanyListItem.tsx @@ -28,7 +28,7 @@ export const CompanyListItem: FC = ({ diff --git a/apps/web/src/app/bedrifter/page.tsx b/apps/web/src/app/bedrifter/page.tsx index d0d6705e57..e6c713f8f9 100644 --- a/apps/web/src/app/bedrifter/page.tsx +++ b/apps/web/src/app/bedrifter/page.tsx @@ -9,15 +9,12 @@ const CompanyPage = async () => { const jobListings = items ?? []; const countsByCompanyID = new Map(); - for (const job of jobListings) { + jobListings.forEach((job) => { countsByCompanyID.set( job.company.id, (countsByCompanyID.get(job.company.id) ?? 0) + 1, ); - } - - console.log(data); - console.log(items); + }); return (
@@ -26,7 +23,7 @@ const CompanyPage = async () => { Bedrifter
-
    +
      {data?.map((company) => { const jobListingCount = countsByCompanyID.get(company.id) ?? 0;