diff --git a/app/.devcontainer/docker-compose.yml b/app/.devcontainer/docker-compose.yml index 36c6ac442..c286df712 100644 --- a/app/.devcontainer/docker-compose.yml +++ b/app/.devcontainer/docker-compose.yml @@ -14,7 +14,7 @@ services: command: sleep infinity db: - image: postgres:15.6-alpine3.19 + image: postgres:18.2-alpine3.23 restart: no environment: POSTGRES_PASSWORD: admin diff --git a/app/src/app/app/changelog/page.tsx b/app/src/app/app/changelog/page.tsx index 5b662d2b6..5e4c33e4b 100644 --- a/app/src/app/app/changelog/page.tsx +++ b/app/src/app/app/changelog/page.tsx @@ -23,6 +23,7 @@ import image20250614CmdK from "@/modules/changelog/assets/2025-06-14-cmdk.png"; import image20250906NewLayout from "@/modules/changelog/assets/2025-09-06-new-layout.png"; import image20251007sincome from "@/modules/changelog/assets/2025-10-07-sincome.png"; import image20251013rolesHistory from "@/modules/changelog/assets/2025-10-13-roles-history.png"; +import image20260214RoleTooltip from "@/modules/changelog/assets/2026-02-14-role-tooltip.png"; import { Link } from "@/modules/common/components/Link"; import { SmallBadge } from "@/modules/common/components/SmallBadge"; import { random } from "lodash"; @@ -44,6 +45,30 @@ export default async function Page() { return (
+ + +

+ Rollen haben nun ein Tooltip in welchem alle Details zu der + jeweiligen Rolle angezeigt werden. Auch kann direkt hier die + jeweilige Rolle von einem Citizen entfernt werden. +

+ +

+ Zukünftig wird hier das aktuelle Level von levelbaren Rollen + gezeigt. Auch kann direkt hier das Level angepasst werden. +

+ + + + +
+
+

@@ -367,7 +392,7 @@ export default async function Page() { quality={100} src={image20251013rolesHistory} alt="" - loading="eager" + loading="lazy" /> diff --git a/app/src/app/app/layout.tsx b/app/src/app/app/layout.tsx index 207bd5566..4a65befeb 100644 --- a/app/src/app/app/layout.tsx +++ b/app/src/app/app/layout.tsx @@ -11,6 +11,8 @@ import { ServiceWorkerLoader } from "@/modules/common/components/ServiceWorkerLo import { getUnleashFlag } from "@/modules/common/utils/getUnleashFlag"; import { UNLEASH_FLAG } from "@/modules/common/utils/UNLEASH_FLAG"; import { ChannelsProvider } from "@/modules/pusher/components/ChannelsContext"; +import { RolesContextProvider } from "@/modules/roles/components/RolesContext"; +import { getVisibleRoles } from "@/modules/roles/utils/getRoles"; import { CmdKProvider } from "@/modules/shell/components/CmdK/CmdKContext"; import { MobileActionBarLoader } from "@/modules/shell/components/Sidebar/MobileActionBarLoader"; import { TopBar } from "@/modules/shell/components/TopBar"; @@ -21,11 +23,13 @@ import { NuqsAdapter } from "nuqs/adapters/next/app"; import { Suspense } from "react"; export default async function AppLayout({ children }: LayoutProps<"/app">) { - const [authentication, disableAlgolia, apps] = await Promise.all([ - requireAuthenticationPage(), - getUnleashFlag(UNLEASH_FLAG.DisableAlgolia), - getAppLinks(), - ]); + const [authentication, disableAlgolia, apps, visibleRoles] = + await Promise.all([ + requireAuthenticationPage(), + getUnleashFlag(UNLEASH_FLAG.DisableAlgolia), + getAppLinks(), + getVisibleRoles(), + ]); return ( <> @@ -35,34 +39,36 @@ export default async function AppLayout({ children }: LayoutProps<"/app">) { -

- - - - - - + +
+ + + + + + -
- {children} -
-
-
-
+
+ {children} +
+
+
+
- - - + + + - {authentication.session.user.role === "admin" && ( - - )} + {authentication.session.user.role === "admin" && ( + + )} - + + diff --git a/app/src/app/app/leaderboards/page.tsx b/app/src/app/app/leaderboards/page.tsx index 97619f9c2..63f1112fe 100644 --- a/app/src/app/app/leaderboards/page.tsx +++ b/app/src/app/app/leaderboards/page.tsx @@ -46,11 +46,11 @@ export default async function Page() { {citizen.rank} - + {/* */} {citizen.displayname} {/* */} diff --git a/app/src/app/app/roles/[id]/inheritance/page.tsx b/app/src/app/app/roles/[id]/inheritance/page.tsx index c0499d3ed..28206ff38 100644 --- a/app/src/app/app/roles/[id]/inheritance/page.tsx +++ b/app/src/app/app/roles/[id]/inheritance/page.tsx @@ -1,6 +1,6 @@ import { requireAuthenticationPage } from "@/modules/auth/server"; import { InheritanceForm } from "@/modules/roles/components/InheritanceForm"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import { getRoleById, getRoles } from "@/modules/roles/queries"; import type { Metadata } from "next"; import { notFound } from "next/navigation"; @@ -30,7 +30,8 @@ export default async function Page({

Vererbungen

- Die Rolle {" "} + Die Rolle{" "} + {" "} erhält alle Berechtigungen von den folgenden ausgewählten Rollen. Im Karrieresystem gelten die folgenden Rollen ebenfalls als freigeschaltet. Verschachtelte Vererbungen werden nicht berücksichtigt. diff --git a/app/src/app/app/spynet/citizen/[id]/layout.tsx b/app/src/app/app/spynet/citizen/[id]/layout.tsx index cc1bdae71..9e7d034b9 100644 --- a/app/src/app/app/spynet/citizen/[id]/layout.tsx +++ b/app/src/app/app/spynet/citizen/[id]/layout.tsx @@ -35,9 +35,7 @@ export default async function Layout({ / -

- {citizen.handle || citizen.id} -

+

{citizen.handle || citizen.id}

diff --git a/app/src/app/app/spynet/organization/[id]/page.tsx b/app/src/app/app/spynet/organization/[id]/page.tsx index c01e5d82b..ec211c6ec 100644 --- a/app/src/app/app/spynet/organization/[id]/page.tsx +++ b/app/src/app/app/spynet/organization/[id]/page.tsx @@ -71,9 +71,7 @@ export default async function Page(props: Props) { / -

- {organization.name} -

+

{organization.name}

{/* {authentication.authorize("organization", "delete") && } */} diff --git a/app/src/modules/career/nodes/RoleCitizens/client/Node.tsx b/app/src/modules/career/nodes/RoleCitizens/client/Node.tsx index d3b8a4ccf..55dfa4588 100644 --- a/app/src/modules/career/nodes/RoleCitizens/client/Node.tsx +++ b/app/src/modules/career/nodes/RoleCitizens/client/Node.tsx @@ -2,7 +2,7 @@ import { Handles } from "@/modules/career/components/Handles"; import { CitizenLink } from "@/modules/common/components/CitizenLink"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import { FlowNodeRoleCitizensAlignment, FlowNodeType, @@ -231,8 +231,8 @@ export const Node: ComponentType> = (props) => { })} > {!props.data.roleCitizensHideRole && ( - )} diff --git a/app/src/modules/changelog/assets/2026-02-14-role-tooltip.png b/app/src/modules/changelog/assets/2026-02-14-role-tooltip.png new file mode 100644 index 000000000..122c4900b Binary files /dev/null and b/app/src/modules/changelog/assets/2026-02-14-role-tooltip.png differ diff --git a/app/src/modules/citizen/actions/deleteRoleAssignment.ts b/app/src/modules/citizen/actions/deleteRoleAssignment.ts new file mode 100644 index 000000000..ff5820636 --- /dev/null +++ b/app/src/modules/citizen/actions/deleteRoleAssignment.ts @@ -0,0 +1,68 @@ +"use server"; + +import { prisma } from "@/db"; +import { createAuthenticatedAction } from "@/modules/actions/utils/createAction"; +import { RoleAssignmentChangeType } from "@prisma/client"; +import { refresh } from "next/cache"; +import { z } from "zod"; + +const schema = z.object({ + citizenId: z.string(), + roleId: z.string(), +}); + +export const deleteRoleAssignment = createAuthenticatedAction( + "deleteRoleAssignment", + schema, + async (formData, authentication, data, t) => { + /** + * Authorize the request + */ + if (!authentication.session.entity) + return { + error: t("Common.forbidden"), + requestPayload: formData, + }; + if ( + !(await authentication.authorize("otherRole", "dismiss", [ + { + key: "roleId", + value: data.roleId, + }, + ])) + ) + return { + error: t("Common.forbidden"), + requestPayload: formData, + }; + + /** + * + */ + await prisma.$transaction([ + prisma.roleAssignment.delete({ + where: { + citizenId_roleId: { + citizenId: data.citizenId, + roleId: data.roleId, + }, + }, + }), + + prisma.roleAssignmentChange.create({ + data: { + citizenId: data.citizenId, + roleId: data.roleId, + type: RoleAssignmentChangeType.REMOVE, + createdById: authentication.session.entity.id, + }, + }), + ]); + + refresh(); + + return { + success: t("Common.successfullySaved"), + }; + }, +); diff --git a/app/src/modules/citizen/actions/updateRoleAssignment.ts b/app/src/modules/citizen/actions/updateRoleAssignment.ts index 1238aff55..61b53e6ea 100644 --- a/app/src/modules/citizen/actions/updateRoleAssignment.ts +++ b/app/src/modules/citizen/actions/updateRoleAssignment.ts @@ -5,7 +5,6 @@ import { createAuthenticatedAction } from "@/modules/actions/utils/createAction" import { triggerNotifications } from "@/modules/notifications/utils/triggerNotification"; import { getRoles } from "@/modules/roles/queries"; import { RoleAssignmentChangeType } from "@prisma/client"; -import { revalidatePath } from "next/cache"; import { z } from "zod"; export interface Change { @@ -155,11 +154,6 @@ export const updateRoleAssignments = createAuthenticatedAction( })), ); - /** - * Revalidate cache(s) - */ - revalidatePath("/app/account/notifications"); - return { success: t("Common.successfullySaved"), }; diff --git a/app/src/modules/citizen/components/CitizenInput.tsx b/app/src/modules/citizen/components/CitizenInput.tsx index b30a97fdd..516ddf74a 100644 --- a/app/src/modules/citizen/components/CitizenInput.tsx +++ b/app/src/modules/citizen/components/CitizenInput.tsx @@ -3,7 +3,7 @@ import Button from "@/modules/common/components/Button"; import { CitizenLink } from "@/modules/common/components/CitizenLink"; import { underlineCharacters } from "@/modules/common/utils/underlineCharacters"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import { api } from "@/trpc/react"; import { Combobox, @@ -283,8 +283,8 @@ const Multiple = ({ onClick={() => handleSelectRole(role.id)} className="group" > - diff --git a/app/src/modules/citizen/components/NotesTable.tsx b/app/src/modules/citizen/components/NotesTable.tsx index a4fb924c0..a1ff897d8 100644 --- a/app/src/modules/citizen/components/NotesTable.tsx +++ b/app/src/modules/citizen/components/NotesTable.tsx @@ -126,7 +126,7 @@ export const NotesTable = ({ rows, searchParams }: Props) => { {row.entityLog.content} diff --git a/app/src/modules/citizen/components/OtherTable.tsx b/app/src/modules/citizen/components/OtherTable.tsx index 95d2ae265..5e3ae314e 100644 --- a/app/src/modules/citizen/components/OtherTable.tsx +++ b/app/src/modules/citizen/components/OtherTable.tsx @@ -120,14 +120,14 @@ export const OtherTable = ({ rows, searchParams }: Props) => { {entityLogTypeTranslations[row.entityLog.type]} {row.entityLog.content} diff --git a/app/src/modules/citizen/components/ProfileTile.tsx b/app/src/modules/citizen/components/ProfileTile.tsx index 3d9037e41..47bc57769 100644 --- a/app/src/modules/citizen/components/ProfileTile.tsx +++ b/app/src/modules/citizen/components/ProfileTile.tsx @@ -2,7 +2,7 @@ import { requireAuthentication } from "@/modules/auth/server"; import Avatar from "@/modules/common/components/Avatar"; import { Link } from "@/modules/common/components/Link"; import { getPenaltyEntriesOfCurrentUser } from "@/modules/penalty-points/queries"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import { getMyAssignedRoles } from "@/modules/roles/utils/getRoles"; import { getMonthlySalaryOfCurrentCitizen, @@ -61,7 +61,11 @@ export const ProfileTile = async ({ className }: Props) => { {roles.length > 0 && (
{roles.map((role) => ( - + ))}
)} diff --git a/app/src/modules/citizen/components/RolesCell.tsx b/app/src/modules/citizen/components/RolesCell.tsx index 53771b46d..b83b353e5 100644 --- a/app/src/modules/citizen/components/RolesCell.tsx +++ b/app/src/modules/citizen/components/RolesCell.tsx @@ -1,6 +1,6 @@ import { requireAuthentication } from "@/modules/auth/server"; import { AddRoles } from "@/modules/citizen/components/roles/AddRoles"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import { getAssignedRoles } from "@/modules/roles/utils/getRoles"; import { type Entity, @@ -48,7 +48,11 @@ export const RolesCell = async ({ {roles.length > 0 ? (
{roles.map((role) => ( - + ))}
) : ( diff --git a/app/src/modules/citizen/components/roles/Roles.tsx b/app/src/modules/citizen/components/roles/Roles.tsx index 394cd5e70..b862d45e4 100644 --- a/app/src/modules/citizen/components/roles/Roles.tsx +++ b/app/src/modules/citizen/components/roles/Roles.tsx @@ -1,6 +1,6 @@ import { requireAuthentication } from "@/modules/auth/server"; import { Tile } from "@/modules/common/components/Tile"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import { getAssignableRoles, getAssignedRoles, @@ -53,7 +53,11 @@ export const Roles = async ({ className, entity }: Props) => { {assignedAndVisibleRoles.length > 0 ? (
{assignedAndVisibleRoles.map((role) => ( - + ))}
) : ( diff --git a/app/src/modules/citizen/components/roles/RolesHistory.tsx b/app/src/modules/citizen/components/roles/RolesHistory.tsx index 6d5aab379..49acd5955 100644 --- a/app/src/modules/citizen/components/roles/RolesHistory.tsx +++ b/app/src/modules/citizen/components/roles/RolesHistory.tsx @@ -3,7 +3,7 @@ import { requireAuthentication } from "@/modules/auth/server"; import { CitizenLink } from "@/modules/common/components/CitizenLink"; import { Tile } from "@/modules/common/components/Tile"; import { formatDate } from "@/modules/common/utils/formatDate"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import { getVisibleRoles } from "@/modules/roles/utils/getRoles"; import { RoleAssignmentChangeType, type Entity } from "@prisma/client"; import clsx from "clsx"; @@ -46,22 +46,23 @@ export const RolesHistory = async ({ className, entity }: Props) => { visibleRoles.some((visibleRole) => visibleRole.id === change.roleId), ) .map((change) => { - const role = visibleRoles.find((role) => role.id === change.roleId)!; - let message; switch (change.type) { case RoleAssignmentChangeType.ADD: message = ( <> - Die Rolle wurde - hinzugefügt. + Die Rolle{" "} + {" "} + wurde hinzugefügt. ); break; case RoleAssignmentChangeType.REMOVE: message = ( <> - Die Rolle wurde entfernt. + Die Rolle{" "} + {" "} + wurde entfernt. ); break; diff --git a/app/src/modules/common/components/Badge.tsx b/app/src/modules/common/components/Badge.tsx index 9a135cd26..6cf9b2786 100644 --- a/app/src/modules/common/components/Badge.tsx +++ b/app/src/modules/common/components/Badge.tsx @@ -32,19 +32,14 @@ export const Badge = ({
{label} - - {valueNode || value} - + {valueNode || value}
{cta && {cta}} diff --git a/app/src/modules/common/components/BooleanFilter.tsx b/app/src/modules/common/components/BooleanFilter.tsx index 00962fa7c..82d8df805 100644 --- a/app/src/modules/common/components/BooleanFilter.tsx +++ b/app/src/modules/common/components/BooleanFilter.tsx @@ -45,9 +45,7 @@ export const BooleanFilter = ({ className, identifier, children }: Props) => { className="flex gap-2 items-center cursor-pointer overflow-hidden" htmlFor={id} > - - {children} - + {children} ); diff --git a/app/src/modules/common/components/Popover.tsx b/app/src/modules/common/components/Popover.tsx index 3b7eca725..5bbfe1de1 100644 --- a/app/src/modules/common/components/Popover.tsx +++ b/app/src/modules/common/components/Popover.tsx @@ -64,9 +64,10 @@ export const Popover = ({
{enableHover && ( -
+ <> +
+
+ )} diff --git a/app/src/modules/events/components/ParticipantsTab.tsx b/app/src/modules/events/components/ParticipantsTab.tsx index ff9ceaa72..464037abe 100644 --- a/app/src/modules/events/components/ParticipantsTab.tsx +++ b/app/src/modules/events/components/ParticipantsTab.tsx @@ -218,10 +218,7 @@ export const ParticipantsTab = async ({ - + Rollen/Zertifikate diff --git a/app/src/modules/events/components/Position.tsx b/app/src/modules/events/components/Position.tsx index 9ea932a31..f2fbae649 100644 --- a/app/src/modules/events/components/Position.tsx +++ b/app/src/modules/events/components/Position.tsx @@ -152,9 +152,7 @@ export const Position = ({ className="font-bold" /> ) : ( -

- {position.name} -

+

{position.name}

)}
diff --git a/app/src/modules/events/components/RolesTable.tsx b/app/src/modules/events/components/RolesTable.tsx index e76b4e152..5aad15ef5 100644 --- a/app/src/modules/events/components/RolesTable.tsx +++ b/app/src/modules/events/components/RolesTable.tsx @@ -1,6 +1,6 @@ "use client"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import type { Role, Upload } from "@prisma/client"; import { createColumnHelper, @@ -43,7 +43,7 @@ export const RolesTable = ({ className, rows }: Props) => { id: "name", cell: (row) => { const { role } = row.row.original; - return ; + return ; }, }), columnHelper.accessor("count", { diff --git a/app/src/modules/fleet/components/ManufacturersTile.tsx b/app/src/modules/fleet/components/ManufacturersTile.tsx index 5f9e1e69a..d72a1a475 100644 --- a/app/src/modules/fleet/components/ManufacturersTile.tsx +++ b/app/src/modules/fleet/components/ManufacturersTile.tsx @@ -60,10 +60,7 @@ export const ManufacturersTile = async () => { )} - + { GRID_COLS, )} > - +
)} -
+
{variant.name}
diff --git a/app/src/modules/fleet/components/VariantsTile.tsx b/app/src/modules/fleet/components/VariantsTile.tsx index bd1fac763..24b304779 100644 --- a/app/src/modules/fleet/components/VariantsTile.tsx +++ b/app/src/modules/fleet/components/VariantsTile.tsx @@ -63,10 +63,7 @@ export const VariantsTile = async ({ GRID_COLS, )} > - + {row.name} @@ -79,7 +76,7 @@ export const VariantsTile = async ({ ) => { return (
  • -

    +

    {unit.title}-{positions[type]}

    {member.user.name} @@ -67,7 +64,7 @@ const SquadronFlightPosition = ({

  • {member.ship.variant.name} diff --git a/app/src/modules/operations/components/SquadronFlightPositionEmpty.tsx b/app/src/modules/operations/components/SquadronFlightPositionEmpty.tsx index 53d6d4a8c..716fc084d 100644 --- a/app/src/modules/operations/components/SquadronFlightPositionEmpty.tsx +++ b/app/src/modules/operations/components/SquadronFlightPositionEmpty.tsx @@ -114,17 +114,11 @@ const SquadronFlightPositionEmpty = ({ type, unit }: Readonly) => { onClick={() => setIsOpen(true)} title="Position besetzen" > -

    +

    {unit.title}-{positions[type]}

    -

    +

    Unbesetzt

    diff --git a/app/src/modules/roles/components/DeleteRole.tsx b/app/src/modules/roles/components/DeleteRole.tsx index 3cf359de6..02814a818 100644 --- a/app/src/modules/roles/components/DeleteRole.tsx +++ b/app/src/modules/roles/components/DeleteRole.tsx @@ -36,7 +36,7 @@ export const DeleteRole = ({ className, role }: Props) => { - {isPending ? : }{" "} + {isPending ? : } Löschen diff --git a/app/src/modules/roles/components/InheritanceForm.tsx b/app/src/modules/roles/components/InheritanceForm.tsx index 846604b55..348c94e3d 100644 --- a/app/src/modules/roles/components/InheritanceForm.tsx +++ b/app/src/modules/roles/components/InheritanceForm.tsx @@ -2,7 +2,7 @@ import { Button2 } from "@/modules/common/components/Button2"; import Note from "@/modules/common/components/Note"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import type { Role, Upload } from "@prisma/client"; import clsx from "clsx"; import { useActionState } from "react"; @@ -53,8 +53,8 @@ export const InheritanceForm = ({ className, currentRole, roles }: Props) => { - diff --git a/app/src/modules/roles/components/PermissionMatrix.tsx b/app/src/modules/roles/components/PermissionMatrix.tsx index afea29d3c..90c31091a 100644 --- a/app/src/modules/roles/components/PermissionMatrix.tsx +++ b/app/src/modules/roles/components/PermissionMatrix.tsx @@ -639,9 +639,7 @@ const Row = ({ role, permissions, gridTemplateColumns }: RowProps) => {
    )} -

    - {role.name} -

    +

    {role.name}

    diff --git a/app/src/modules/roles/components/RolesContext.tsx b/app/src/modules/roles/components/RolesContext.tsx new file mode 100644 index 000000000..251431bae --- /dev/null +++ b/app/src/modules/roles/components/RolesContext.tsx @@ -0,0 +1,45 @@ +"use client"; + +import type { Role, Upload } from "@prisma/client"; +import type { ReactNode } from "react"; +import { createContext, useContext, useMemo } from "react"; + +interface RolesContext { + readonly roles: (Role & { + icon: Upload | null; + thumbnail: Upload | null; + })[]; +} + +const RolesContext = createContext(undefined); + +interface Props { + readonly children: ReactNode; + readonly roles: (Role & { + icon: Upload | null; + thumbnail: Upload | null; + })[]; +} + +export const RolesContextProvider = ({ children, roles }: Props) => { + const value = useMemo( + () => ({ + roles, + }), + [roles], + ); + + return ( + {children} + ); +}; + +/** + * Check for undefined since the defaultValue of the context is undefined. If + * it's still undefined, the provider component is missing. + */ +export function useRolesContext() { + const context = useContext(RolesContext); + if (!context) throw new Error("Provider is missing."); + return context; +} diff --git a/app/src/modules/roles/components/SingleRole.tsx b/app/src/modules/roles/components/SingleRole.tsx deleted file mode 100644 index 08d14ff3f..000000000 --- a/app/src/modules/roles/components/SingleRole.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { env } from "@/env"; -import type { Upload } from "@prisma/client"; -import { type Role } from "@prisma/client"; -import clsx from "clsx"; -import Image from "next/image"; - -interface Props { - readonly className?: string; - readonly role: Role & { - icon: Upload | null; - }; - readonly showPlaceholder?: boolean; -} - -export const SingleRole = ({ - className, - role, - showPlaceholder = false, -}: Props) => { - return ( - - {role.icon && ( - - - - )} - - {!role.iconId && showPlaceholder && } - - - {role.name} - - - ); -}; diff --git a/app/src/modules/roles/components/SingleRoleBadge.tsx b/app/src/modules/roles/components/SingleRoleBadge.tsx new file mode 100644 index 000000000..8021a1531 --- /dev/null +++ b/app/src/modules/roles/components/SingleRoleBadge.tsx @@ -0,0 +1,169 @@ +"use client"; + +import { env } from "@/env"; +import { useAction } from "@/modules/actions/utils/useAction"; +import { useAuthentication } from "@/modules/auth/hooks/useAuthentication"; +import { deleteRoleAssignment } from "@/modules/citizen/actions/deleteRoleAssignment"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/modules/common/components/AlertDialog"; +import { Button2, Button2Variant } from "@/modules/common/components/Button2"; +import { Note } from "@/modules/common/components/Note"; +import { Popover } from "@/modules/common/components/Popover"; +import { type Role } from "@prisma/client"; +import clsx from "clsx"; +import Image from "next/image"; +import { useId } from "react"; +import { FaSpinner, FaTrash } from "react-icons/fa"; +import { useRolesContext } from "./RolesContext"; + +interface Props { + readonly className?: string; + readonly roleId: Role["id"]; + readonly showPlaceholder?: boolean; + readonly citizenId?: string; +} + +export const SingleRoleBadge = ({ + className, + roleId, + showPlaceholder = false, + citizenId, +}: Props) => { + const { roles } = useRolesContext(); + const authentication = useAuthentication(); + const { state, formAction, isPending } = useAction(deleteRoleAssignment); + const formId = useId(); + + const role = roles.find((role) => role.id === roleId); + if (!role) return null; + + const canDismiss = + authentication && + authentication.authorize("otherRole", "dismiss", [ + { + key: "roleId", + value: role.id, + }, + ]); + + return ( + + {role.icon && ( + + + + )} + + {!role.iconId && showPlaceholder && } + + {role.name} + + } + > +
    +
    + {role.icon ? ( + + + + ) : ( + + )} + + {role.name} +
    + + {citizenId && canDismiss && ( +
    +
    + + + + + + + {isPending ? ( + + ) : ( + + )} + Entfernen + + + + + + Rolle entfernen? + + Willst du die Rolle{" "} + {role.name} wirklich + entfernen? + + + + + Abbrechen + + + Entfernen + + + + + + {state && "error" in state && ( + + )} + +
    + )} +
    +
    + ); +}; diff --git a/app/src/modules/silc/components/RoleSalariesClient/RoleSelector.tsx b/app/src/modules/silc/components/RoleSalariesClient/RoleSelector.tsx index 8e90ea591..b3e1e5cb5 100644 --- a/app/src/modules/silc/components/RoleSalariesClient/RoleSelector.tsx +++ b/app/src/modules/silc/components/RoleSalariesClient/RoleSelector.tsx @@ -1,6 +1,6 @@ import Button from "@/modules/common/components/Button"; import { api } from "@/modules/common/utils/api"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import type { Role } from "@prisma/client"; import * as Popover from "@radix-ui/react-popover"; import { useState, type CSSProperties } from "react"; @@ -45,10 +45,10 @@ export const RoleSelector = ({ style, defaultValue, onChange }: Props) => { className="flex items-center justify-between gap-1 bg-neutral-700/50 hover:bg-neutral-600/50 pr-3 rounded-secondary" style={style} > - role.role.id === selectedRole)!.role + roleId={ + data.find((role) => role.role.id === selectedRole)!.role.id } showPlaceholder /> @@ -91,8 +91,8 @@ export const RoleSelector = ({ style, defaultValue, onChange }: Props) => { onClick={() => handleSelectRole(role.role.id)} className="group" > - diff --git a/app/src/modules/silc/components/SilcTransactionsTableClient.tsx b/app/src/modules/silc/components/SilcTransactionsTableClient.tsx index 8293f350d..2fc981918 100644 --- a/app/src/modules/silc/components/SilcTransactionsTableClient.tsx +++ b/app/src/modules/silc/components/SilcTransactionsTableClient.tsx @@ -66,7 +66,7 @@ export const SilcTransactionsTableClient = ({ return ( @@ -97,10 +97,7 @@ export const SilcTransactionsTableClient = ({ id: "description", enableSorting: false, cell: (row) => ( - + {row.getValue()} ), @@ -117,7 +114,7 @@ export const SilcTransactionsTableClient = ({ return ( diff --git a/app/src/modules/spynet/components/ActivityTile/mapRoleAssignmentChangeEntries.tsx b/app/src/modules/spynet/components/ActivityTile/mapRoleAssignmentChangeEntries.tsx index d22e5d49e..9e4188dd1 100644 --- a/app/src/modules/spynet/components/ActivityTile/mapRoleAssignmentChangeEntries.tsx +++ b/app/src/modules/spynet/components/ActivityTile/mapRoleAssignmentChangeEntries.tsx @@ -1,6 +1,6 @@ import { requireAuthentication } from "@/modules/auth/server"; import { CitizenLink } from "@/modules/common/components/CitizenLink"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import { getVisibleRoles } from "@/modules/roles/utils/getRoles"; import { RoleAssignmentChangeType, @@ -27,15 +27,14 @@ export const mapRoleAssignmentChangeEntries = async ( visibleRoles.some((visibleRole) => visibleRole.id === change.roleId), ) .map((change) => { - const role = visibleRoles.find((role) => role.id === change.roleId)!; - let message; switch (change.type) { case RoleAssignmentChangeType.ADD: message = (

    Citizen wurde die Rolle{" "} - hinzugefügt + {" "} + hinzugefügt

    ); break; @@ -43,7 +42,8 @@ export const mapRoleAssignmentChangeEntries = async ( message = (

    Citizen wurde die Rolle{" "} - entfernt + {" "} + entfernt

    ); break; diff --git a/app/src/modules/tasks/components/CreateTask/RequiredRoles.tsx b/app/src/modules/tasks/components/CreateTask/RequiredRoles.tsx index e92877358..6d99da307 100644 --- a/app/src/modules/tasks/components/CreateTask/RequiredRoles.tsx +++ b/app/src/modules/tasks/components/CreateTask/RequiredRoles.tsx @@ -1,6 +1,6 @@ import { Button2, Button2Variant } from "@/modules/common/components/Button2"; import { api } from "@/modules/common/utils/api"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import type { Role } from "@prisma/client"; import * as Popover from "@radix-ui/react-popover"; import clsx from "clsx"; @@ -63,8 +63,8 @@ export const RequiredRoles = ({ className, defaultValue }: Props) => { onClick={() => handleSelectRole(role.id)} className="group" > - @@ -103,9 +103,9 @@ export const RequiredRoles = ({ className, defaultValue }: Props) => { } className="flex items-center gap-1 bg-neutral-700/50 pr-2 rounded-secondary" > - diff --git a/app/src/modules/tasks/components/Overview.tsx b/app/src/modules/tasks/components/Overview.tsx index 19399e27b..8b1d6cca5 100644 --- a/app/src/modules/tasks/components/Overview.tsx +++ b/app/src/modules/tasks/components/Overview.tsx @@ -10,7 +10,7 @@ import { Markdown } from "@/modules/common/components/Markdown"; import { Tile } from "@/modules/common/components/Tile"; import { Tooltip } from "@/modules/common/components/Tooltip"; import { formatDate } from "@/modules/common/utils/formatDate"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import { TaskRewardType, TaskVisibility, @@ -164,7 +164,11 @@ export const Overview = ({ {task.requiredRoles.length > 0 ? (
    {task.requiredRoles.map((role) => ( - + ))}
    ) : ( diff --git a/app/src/modules/tasks/components/ToggleAssignmentForCurrentUser.tsx b/app/src/modules/tasks/components/ToggleAssignmentForCurrentUser.tsx index ed867fe4b..4a43cfb47 100644 --- a/app/src/modules/tasks/components/ToggleAssignmentForCurrentUser.tsx +++ b/app/src/modules/tasks/components/ToggleAssignmentForCurrentUser.tsx @@ -2,7 +2,7 @@ import { useAuthentication } from "@/modules/auth/hooks/useAuthentication"; import { Button2 } from "@/modules/common/components/Button2"; -import { SingleRole } from "@/modules/roles/components/SingleRole"; +import { SingleRoleBadge } from "@/modules/roles/components/SingleRoleBadge"; import { TaskVisibility, type Role, @@ -148,7 +148,7 @@ export const ToggleAssignmentForCurrentUser = ({

    {task.requiredRoles.map((role) => ( - + ))}
    diff --git a/app/src/modules/users/components/Table.tsx b/app/src/modules/users/components/Table.tsx index afd113852..de56ed373 100644 --- a/app/src/modules/users/components/Table.tsx +++ b/app/src/modules/users/components/Table.tsx @@ -171,7 +171,7 @@ export const Table = ({ users }: Props) => {