Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4d69ebc
feat: add competence management system
MaxiLein Oct 24, 2025
b45cca0
updated API configuration and adjust pagination position in competenc…
MaxiLein Oct 28, 2025
f76e14f
trivial
MaxiLein Nov 1, 2025
455befb
Debug logging
MaxiLein Nov 1, 2025
beb3d25
Merge branch 'main' into usertask-matching-prototype
MaxiLein Nov 1, 2025
45645db
Add competence matching env variable
MaxiLein Nov 5, 2025
81d1739
format displayed description
MaxiLein Nov 5, 2025
3ce5cfa
Add competence-matching as feature-flag (TRUE)
MaxiLein Nov 5, 2025
6a5c1b2
Merge branch 'main' into usertask-matching-prototype
MaxiLein Nov 5, 2025
a41e93b
Merge branch 'main' into usertask-matching-prototype
MaxiLein Nov 12, 2025
5d367ce
fix: update imports for potential owner store and options generation
MaxiLein Nov 12, 2025
91d3b2f
Update src/management-system-v2/components/competence/utils/debug.ts
MaxiLein Nov 12, 2025
b508a97
fix: update import path for potential owner store and fix typo in com…
MaxiLein Nov 12, 2025
222cf4e
fix: ensure type safety for competence in user competence functions
MaxiLein Nov 12, 2025
bbe32a8
env-variables: competence matching configuration and validation
MaxiLein Nov 12, 2025
3d5f65c
refactor: move competence matching API config to fetch-matches.ts and…
MaxiLein Nov 17, 2025
b7ed4b9
revert refine for values (will look at it later)
MaxiLein Nov 17, 2025
afa0bad
remove competence service paths from setable env vars
MaxiLein Nov 17, 2025
e93a7af
Merge branch 'main' into usertask-matching-prototype
MaxiLein Nov 20, 2025
546e70f
Merge branch 'main' into usertask-matching-prototype
MaxiLein Nov 24, 2025
ad1352a
Merge branch 'main' into usertask-matching-prototype
MaxiLein Nov 30, 2025
200dac4
deactivate contradiction indicator for now
MaxiLein Nov 30, 2025
7679c6c
Merge branch 'main' into usertask-matching-prototype
MaxiLein Dec 1, 2025
3d7570a
Merge branch 'main' into usertask-matching-prototype
MaxiLein Dec 8, 2025
b27ede8
Merge branch 'main' into usertask-matching-prototype
MaxiLein Jan 20, 2026
0370fff
Adress comments
MaxiLein Jan 20, 2026
a45368b
Merge branch 'main' into usertask-matching-prototype
MaxiLein Feb 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getCurrentUser } from '@/components/auth';
import Content from '@/components/content';
import { notFound } from 'next/navigation';
import { env } from '@/lib/ms-config/env-vars';
import { getMSConfig } from '@/lib/ms-config/ms-config';
import UserCompetenceManager from '@/components/competence/user-competences/user-competence-manager';
import { getAllCompetencesOfUser } from '@/lib/data/db/competence';
import UnauthorizedFallback from '@/components/unauthorized-fallback';

const UserCompetencePage = async () => {
const { user, userId } = await getCurrentUser();

if (user?.isGuest) return <UnauthorizedFallback />;

if (!env.PROCEED_PUBLIC_IAM_ACTIVE) return notFound();

const msConfig = await getMSConfig();
if (!msConfig.PROCEED_PUBLIC_COMPETENCE_MATCHING_ACTIVE) return notFound();

// Fetch user's competences
const userCompetences = await getAllCompetencesOfUser(userId);

return (
<Content title="My Competences">
<UserCompetenceManager initialUserCompetences={userCompetences} userId={userId} />
</Content>
);
};

export default UserCompetencePage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { getCurrentUser, getCurrentEnvironment } from '@/components/auth';
import Content from '@/components/content';
import { notFound } from 'next/navigation';
import { env } from '@/lib/ms-config/env-vars';
import { getMSConfig } from '@/lib/ms-config/ms-config';
import UnauthorizedFallback from '@/components/unauthorized-fallback';
import CompetencesDashboard from '@/components/competence/organization/competences-dashboard';
import { getAllSpaceCompetences } from '@/lib/data/db/competence';
import { getUsersInSpace } from '@/lib/data/db/iam/memberships';
import { User } from '@/lib/data/user-schema';

const OrganizationCompetencesPage = async ({ params }: { params: { environmentId: string } }) => {
const { user, userId } = await getCurrentUser();
const { activeEnvironment } = await getCurrentEnvironment(params.environmentId);

if (user?.isGuest) return <UnauthorizedFallback />;

if (!env.PROCEED_PUBLIC_IAM_ACTIVE) return notFound();

const msConfig = await getMSConfig();
if (!msConfig.PROCEED_PUBLIC_COMPETENCE_MATCHING_ACTIVE) return notFound();

// Only show for organization spaces
if (!activeEnvironment.isOrganization) return notFound();

// TODO: Add authorization check - can('view', 'Competence') or can('manage', 'User')

// Fetch organization data
const spaceCompetences = await getAllSpaceCompetences(activeEnvironment.spaceId);
const organizationMembers = (await getUsersInSpace(activeEnvironment.spaceId)) as User[];

return (
<Content title="Organization Competences">
<CompetencesDashboard
spaceId={activeEnvironment.spaceId}
initialSpaceCompetences={spaceCompetences}
organizationMembers={organizationMembers}
currentUserId={userId}
/>
</Content>
);
};

export default OrganizationCompetencesPage;
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
SolutionOutlined,
HomeOutlined,
AppstoreOutlined,
TrophyOutlined,
} from '@ant-design/icons';
import { TbUser, TbUserEdit } from 'react-icons/tb';

Expand Down Expand Up @@ -312,6 +313,15 @@ const DashboardLayout = async (
});
}

// TODO: Add proper authorization check for competences
if (can('manage', 'User') && msConfig.PROCEED_PUBLIC_COMPETENCE_MATCHING_ACTIVE) {
children.push({
key: 'competences',
label: <Link href={spaceURL(activeEnvironment, `/iam/competences`)}>Competences</Link>,
icon: <TrophyOutlined />,
});
}

layoutMenuItems.push({
key: 'iam-group',
label: 'Organization',
Expand Down Expand Up @@ -343,6 +353,19 @@ const DashboardLayout = async (
icon: <TbUserEdit />,
selectedRegex: profileRegex,
},
...(msConfig.PROCEED_PUBLIC_COMPETENCE_MATCHING_ACTIVE
? [
{
key: 'personal-competence',
label: user?.isGuest ? (
<GuestWarningButton>My Competences</GuestWarningButton>
) : (
<SpaceLink href="/user-competence">My Competences</SpaceLink>
),
icon: <TrophyOutlined />,
},
]
: []),
{
key: 'personal-spaces',
label: user?.isGuest ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import { getRolesWithMembers } from '@/lib/data/db/iam/roles';
import { getProcessBPMN } from '@/lib/data/processes';
import BPMNTimeline from '@/components/bpmn-timeline';
import { UnauthorizedError } from '@/lib/ability/abilityHelper';
import { RoleType, UserType } from './use-potentialOwner-store';
import {
RoleType,
UserType,
} from '@/components/competence/potential-owner/use-potentialOwner-store';
import type { Process } from '@/lib/data/process-schema';
import { redirect } from 'next/navigation';
import { spaceURL } from '@/lib/utils';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ import DescriptionSection from './description-section';
import PlannedCostInput from './planned-cost-input';
import { checkIfProcessExistsByName, updateProcessMetaData } from '@/lib/data/processes';
import { useEnvironment } from '@/components/auth-can';
import { PotentialOwner, ResponsibleParty } from './potential-owner';
import {
PotentialOwner,
ResponsibleParty,
} from '@/components/competence/potential-owner/potential-owner';
import { EnvVarsContext } from '@/components/env-vars-context';
import { getBackgroundColor, getBorderColor, getTextColor } from '@/lib/helpers/bpmn-js-helpers';
import { Shape } from 'bpmn-js/lib/model/Types';
Expand All @@ -45,6 +48,7 @@ import { useSession } from 'next-auth/react';
import { usePathname } from 'next/navigation';
import { BPMNCanvasRef } from '@/components/bpmn-canvas';
import VariableDefinition from './variable-definition';
import SuggestPotentialOwner from '@/components/competence/potential-owner/suggest-potential-owner';
import IsExecutableSection from './is-executable';

// Elements that should not display the planned duration field
Expand Down Expand Up @@ -503,6 +507,10 @@ const PropertiesPanelContent: React.FC<PropertiesPanelContentProperties> = ({
modeler={modeler}
readOnly={readOnly || !isExecutable}
/>
{env.PROCEED_PUBLIC_COMPETENCE_MATCHING_ACTIVE && (
<SuggestPotentialOwner selectedElement={selectedElement} modeler={modeler} />
)}
<Divider />
</>
)}
<VariableDefinition readOnly={readOnly || !isExecutable} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import usePotentialOwnerStore, {
UserType,
RoleType,
useInitialisePotentialOwnerStore,
} from './use-potentialOwner-store';
} from '@/components/competence/potential-owner/use-potentialOwner-store';
import { useProcessView } from './process-view-context';

type SubprocessInfo = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ import {
import { defaultForm } from '@/components/html-form-editor/utils';
import usePotentialOwnerStore, {
useInitialisePotentialOwnerStore,
} from '../processes/[mode]/[processId]/use-potentialOwner-store';
import { generateOptions } from '../processes/[mode]/[processId]/potential-owner';
} from '@/components/competence/potential-owner/use-potentialOwner-store';
import { generateOptions } from '@/components/competence/potential-owner/potential-owner';
import { DefaultOptionType } from 'antd/es/cascader';
import { addUserTasks } from '@/lib/data/user-tasks';

Expand Down
40 changes: 40 additions & 0 deletions src/management-system-v2/app/admin/ms-config/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,42 @@ async function ConfigPage() {
},
],
},
{
name: 'Competence Matching',
key: 'msconfig-competence-matching',
children: [
{
type: 'boolean',
name: 'PROCEED_PUBLIC_COMPETENCE_MATCHING_ACTIVE',
key: 'PROCEED_PUBLIC_COMPETENCE_MATCHING_ACTIVE',
value: msConfig.PROCEED_PUBLIC_COMPETENCE_MATCHING_ACTIVE,
},
{
name: 'Competence Matching Service API',
key: 'msconfig-competence-matching-service-api',
children: [
{
type: 'string',
name: 'PROCEED_PUBLIC_COMPETENCE_MATCHING_SERVICE_URL',
key: 'PROCEED_PUBLIC_COMPETENCE_MATCHING_SERVICE_URL',
value: msConfig.PROCEED_PUBLIC_COMPETENCE_MATCHING_SERVICE_URL,
},
// {
// type: 'string',
// name: 'PROCEED_PUBLIC_COMPETENCE_MATCHING_SERVICE_COMPETENCE_LIST_PATH',
// key: 'PROCEED_PUBLIC_COMPETENCE_MATCHING_SERVICE_COMPETENCE_LIST_PATH',
// value: msConfig.PROCEED_PUBLIC_COMPETENCE_MATCHING_SERVICE_COMPETENCE_LIST_PATH,
// },
// {
// type: 'string',
// name: 'PROCEED_PUBLIC_COMPETENCE_MATCHING_SERVICE_MATCH_PATH',
// key: 'PROCEED_PUBLIC_COMPETENCE_MATCHING_SERVICE_MATCH_PATH',
// value: msConfig.PROCEED_PUBLIC_COMPETENCE_MATCHING_SERVICE_MATCH_PATH,
// },
],
},
],
},
{
name: 'Scheduler',
key: 'msconfig-scheduler',
Expand Down Expand Up @@ -330,6 +366,10 @@ async function ConfigPage() {
groupKey: 'PROCEED_PUBLIC_IAM_LOGIN_MAIL_ACTIVE',
disablerKey: 'PROCEED_PUBLIC_MAILSERVER_ACTIVE',
},
{
groupKey: 'msconfig-competence-matching-service-api',
disablerKey: 'PROCEED_PUBLIC_COMPETENCE_MATCHING_ACTIVE',
},
]}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { NextRequest, NextResponse } from 'next/server';
import { getAllSpaceCompetences, getAllCompetencesOfUser } from '@/lib/data/db/competence';
import { getCurrentUser } from '@/components/auth';
import { getMSConfig } from '@/lib/ms-config/ms-config';

export async function GET(request: NextRequest, { params }: { params: { spaceId: string } }) {
try {
const msConfig = await getMSConfig();

// Check if competence matching is enabled via environment variable
if (!msConfig.PROCEED_PUBLIC_COMPETENCE_MATCHING_ACTIVE) {
return NextResponse.json({ error: 'Competence matching is not enabled' }, { status: 404 });
}

const { userId } = await getCurrentUser();

// Get all space competences
const spaceCompetences = await getAllSpaceCompetences(params.spaceId);

// Get user's claimed competences
const userCompetences = await getAllCompetencesOfUser(userId);

// Mark which competences are already claimed
const competencesWithClaimStatus = spaceCompetences.map((comp) => ({
...comp,
isClaimed: userCompetences.some((uc) => uc.competenceId === comp.id),
}));

return NextResponse.json(competencesWithClaimStatus);
} catch (error) {
console.error('Failed to fetch space competences:', error);
Comment thread
MaxiLein marked this conversation as resolved.
return NextResponse.json({ error: 'Failed to fetch competences' }, { status: 500 });
}
}
Loading
Loading