diff --git a/apps/console/src/layouts/MainLayout.tsx b/apps/console/src/layouts/MainLayout.tsx
index 6c17788af..072331665 100644
--- a/apps/console/src/layouts/MainLayout.tsx
+++ b/apps/console/src/layouts/MainLayout.tsx
@@ -10,7 +10,7 @@ import {
IconBank,
IconBook,
IconBox,
- IconCalendar1,
+ IconCalendar2,
IconCheckmark1,
IconChevronGrabberVertical,
IconCircleProgress,
@@ -122,7 +122,7 @@ function MainLayoutContent({
{isAuthorized("Organization", "listMeetings") && (
)}
@@ -441,7 +441,7 @@ function OrganizationSelector({
{isLoading ? __("Loading...") : currentOrganization?.name || ""}
}
- >
+ >
{
- e.stopPropagation();
+ e.stopPropagation();
}}
autoFocus
/>
diff --git a/apps/console/src/pages/organizations/people/PeopleListPage.tsx b/apps/console/src/pages/organizations/people/PeopleListPage.tsx
index ba7f9b509..32a38b795 100644
--- a/apps/console/src/pages/organizations/people/PeopleListPage.tsx
+++ b/apps/console/src/pages/organizations/people/PeopleListPage.tsx
@@ -11,6 +11,7 @@ import {
ActionDropdown,
DropdownItem,
IconTrashCan,
+ IconCalendar2,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import type { PeopleGraphPaginatedQuery } from "/hooks/graph/__generated__/PeopleGraphPaginatedQuery.graphql";
@@ -22,9 +23,10 @@ import type { NodeOf } from "/types";
import { usePageTitle } from "@probo/hooks";
import { getRole } from "@probo/helpers";
import { CreatePeopleDialog } from "./dialogs/CreatePeopleDialog";
+import { SetEndOfContractDialog, type SetEndOfContractDialogRef } from "./dialogs/SetEndOfContractDialog";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { PermissionsContext } from "/providers/PermissionsContext";
-import { use } from "react";
+import { use, useRef } from "react";
type People = NodeOf
;
@@ -108,40 +110,56 @@ function PeopleRow({
const deletePeople = useDeletePeople(people, connectionId);
const contractEnded = isContractEnded(people);
const { isAuthorized } = use(PermissionsContext);
+ const dialogRef = useRef(null);
return (
-
-
-
-
-
- {people.fullName}
-
- {people.primaryEmailAddress}
+ <>
+
+
+
+
+
+
+ {people.fullName}
+
+ {people.primaryEmailAddress}
+
-
- |
- {getRole(__, people.kind)} |
- {people.position} |
- {hasAnyAction && (
-
-
- {isAuthorized("People", "deletePeople") && (
-
- {__("Delete")}
-
- )}
-
|
- )}
-
+ {getRole(__, people.kind)} |
+ {people.position} |
+ {hasAnyAction && (
+
+
+ {isAuthorized("People", "updatePeople") && (
+ dialogRef.current?.open()}
+ >
+ {__("Set end of contract")}
+
+ )}
+ {isAuthorized("People", "deletePeople") && (
+
+ {__("Delete")}
+
+ )}
+
+ |
+ )}
+ |
+ >
);
}
diff --git a/apps/console/src/pages/organizations/people/dialogs/SetEndOfContractDialog.tsx b/apps/console/src/pages/organizations/people/dialogs/SetEndOfContractDialog.tsx
new file mode 100644
index 000000000..af0cb4e47
--- /dev/null
+++ b/apps/console/src/pages/organizations/people/dialogs/SetEndOfContractDialog.tsx
@@ -0,0 +1,107 @@
+import { useTranslate } from "@probo/i18n";
+import {
+ Button,
+ Dialog,
+ DialogContent,
+ DialogFooter,
+ Field,
+ Input,
+ Spinner,
+ useDialogRef,
+} from "@probo/ui";
+import { forwardRef, useImperativeHandle } from "react";
+import { z } from "zod";
+import { formatDatetime, toDateInput } from "@probo/helpers";
+import { useFormWithSchema } from "/hooks/useFormWithSchema";
+import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
+import { updatePeopleMutation } from "/hooks/graph/PeopleGraph";
+
+const schema = z.object({
+ contractEndDate: z.string().optional(),
+});
+
+export type SetEndOfContractDialogRef = {
+ open: () => void;
+ close: () => void;
+};
+
+type Props = {
+ peopleId: string;
+ currentContractEndDate?: string | null;
+};
+
+export const SetEndOfContractDialog = forwardRef(function SetEndOfContractDialog(
+ {
+ peopleId,
+ currentContractEndDate,
+ },
+ ref
+) {
+ const { __ } = useTranslate();
+ const dialogRef = useDialogRef();
+
+ useImperativeHandle(ref, () => ({
+ open: () => dialogRef.current?.open(),
+ close: () => dialogRef.current?.close(),
+ }));
+
+ const {
+ register,
+ handleSubmit,
+ formState: { isSubmitting },
+ reset,
+ } = useFormWithSchema(schema, {
+ defaultValues: {
+ contractEndDate: toDateInput(currentContractEndDate),
+ },
+ });
+
+ const [mutate] = useMutationWithToasts(updatePeopleMutation, {
+ successMessage: __("End of contract updated successfully"),
+ errorMessage: __("Failed to update end of contract"),
+ });
+
+ const onSubmit = handleSubmit(async (data) => {
+ await mutate({
+ variables: {
+ input: {
+ id: peopleId,
+ contractEndDate: formatDatetime(data.contractEndDate),
+ },
+ },
+ });
+
+ dialogRef.current?.close();
+ });
+
+ const handleClose = () => {
+ reset();
+ };
+
+ return (
+
+ );
+});
diff --git a/pkg/coredata/people.go b/pkg/coredata/people.go
index 7de3a3bdc..cc12ac52e 100644
--- a/pkg/coredata/people.go
+++ b/pkg/coredata/people.go
@@ -22,6 +22,7 @@ import (
"time"
"github.com/jackc/pgx/v5"
+ "github.com/jackc/pgx/v5/pgconn"
"go.gearno.de/kit/pg"
"go.probo.inc/probo/pkg/gid"
"go.probo.inc/probo/pkg/mail"
@@ -52,6 +53,10 @@ type (
ErrPeopleAlreadyExists struct {
message string
}
+
+ ErrPeopleReferenced struct {
+ message string
+ }
)
func (e ErrPeopleNotFound) Error() string {
@@ -62,6 +67,10 @@ func (e ErrPeopleAlreadyExists) Error() string {
return e.message
}
+func (e ErrPeopleReferenced) Error() string {
+ return e.message
+}
+
func (p People) CursorKey(orderBy PeopleOrderField) page.CursorKey {
switch orderBy {
case PeopleOrderFieldCreatedAt:
@@ -349,7 +358,19 @@ DELETE FROM peoples WHERE %s AND id = @people_id
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, q, args)
- return err
+ if err != nil {
+ var pgErr *pgconn.PgError
+ if errors.As(err, &pgErr) {
+ if pgErr.Code == "23503" {
+ return &ErrPeopleReferenced{
+ message: fmt.Sprintf("person with id %s cannot be deleted because it is referenced by other records", p.ID),
+ }
+ }
+ }
+ return fmt.Errorf("cannot delete person: %w", err)
+ }
+
+ return nil
}
func (p *Peoples) CountByOrganizationID(
diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go
index 6890d9e8f..8348f8454 100644
--- a/pkg/server/api/console/v1/v1_resolver.go
+++ b/pkg/server/api/console/v1/v1_resolver.go
@@ -2126,6 +2126,10 @@ func (r *mutationResolver) DeletePeople(ctx context.Context, input types.DeleteP
err := prb.Peoples.Delete(ctx, input.PeopleID)
if err != nil {
+ var errReferenced *coredata.ErrPeopleReferenced
+ if errors.As(err, &errReferenced) {
+ return nil, gqlutils.Conflict(errReferenced)
+ }
panic(fmt.Errorf("cannot delete people: %w", err))
}