@@ -8,7 +8,7 @@ import {
|
|||||||
Spinner,
|
Spinner,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { Suspense, useState, useEffect } from "react";
|
import { Suspense, useState, useEffect, useRef } from "react";
|
||||||
import type { ItemOf, NodeOf } from "/types";
|
import type { ItemOf, NodeOf } from "/types";
|
||||||
import { graphql, useFragment, useRefetchableFragment } from "react-relay";
|
import { graphql, useFragment, useRefetchableFragment } from "react-relay";
|
||||||
import { usePeople } from "/hooks/graph/PeopleGraph.ts";
|
import { usePeople } from "/hooks/graph/PeopleGraph.ts";
|
||||||
@@ -19,6 +19,7 @@ import type { DocumentDetailPageDocumentFragment$data } from "../__generated__/D
|
|||||||
import { useOutletContext } from "react-router";
|
import { useOutletContext } from "react-router";
|
||||||
import type { DocumentSignaturesTab_signature$key } from "/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_signature.graphql.ts";
|
import type { DocumentSignaturesTab_signature$key } from "/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_signature.graphql.ts";
|
||||||
import type { DocumentSignaturesTab_version$key } from "/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_version.graphql.ts";
|
import type { DocumentSignaturesTab_version$key } from "/pages/organizations/documents/tabs/__generated__/DocumentSignaturesTab_version.graphql.ts";
|
||||||
|
import type { DocumentSignaturesTabRefetchQuery } from "./__generated__/DocumentSignaturesTabRefetchQuery.graphql";
|
||||||
|
|
||||||
type Version = NodeOf<DocumentDetailPageDocumentFragment$data["versions"]>;
|
type Version = NodeOf<DocumentDetailPageDocumentFragment$data["versions"]>;
|
||||||
|
|
||||||
@@ -33,7 +34,10 @@ const versionFragment = graphql`
|
|||||||
id
|
id
|
||||||
status
|
status
|
||||||
signatures(first: $count, after: $cursor, filter: $signatureFilter)
|
signatures(first: $count, after: $cursor, filter: $signatureFilter)
|
||||||
@connection(key: "DocumentSignaturesTab_signatures", filters: ["filter"]) {
|
@connection(
|
||||||
|
key: "DocumentSignaturesTab_signatures"
|
||||||
|
filters: ["filter"]
|
||||||
|
) {
|
||||||
__id
|
__id
|
||||||
edges {
|
edges {
|
||||||
node {
|
node {
|
||||||
@@ -54,7 +58,9 @@ const versionFragment = graphql`
|
|||||||
type SignatureState = "REQUESTED" | "SIGNED";
|
type SignatureState = "REQUESTED" | "SIGNED";
|
||||||
|
|
||||||
export default function DocumentSignaturesTab() {
|
export default function DocumentSignaturesTab() {
|
||||||
const { version: versionFromContext } = useOutletContext<{ version: Version }>();
|
const { version: versionFromContext } = useOutletContext<{
|
||||||
|
version: Version;
|
||||||
|
}>();
|
||||||
const [selectedStates, setSelectedStates] = useState<SignatureState[]>([]);
|
const [selectedStates, setSelectedStates] = useState<SignatureState[]>([]);
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
@@ -64,9 +70,7 @@ export default function DocumentSignaturesTab() {
|
|||||||
|
|
||||||
const toggleState = (state: SignatureState) => {
|
const toggleState = (state: SignatureState) => {
|
||||||
setSelectedStates((prev) =>
|
setSelectedStates((prev) =>
|
||||||
prev.includes(state)
|
prev.includes(state) ? prev.filter((s) => s !== state) : [...prev, state]
|
||||||
? prev.filter((s) => s !== state)
|
|
||||||
: [...prev, state]
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,29 +106,40 @@ export default function DocumentSignaturesTab() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Suspense fallback={<Spinner centered />}>
|
<Suspense fallback={<Spinner centered />}>
|
||||||
<SignatureList version={versionFromContext} selectedStates={selectedStates} />
|
<SignatureList
|
||||||
|
version={versionFromContext}
|
||||||
|
selectedStates={selectedStates}
|
||||||
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SignatureList(props: { version: Version; selectedStates: SignatureState[] }) {
|
function SignatureList(props: {
|
||||||
const [version, refetch] = useRefetchableFragment<any, DocumentSignaturesTab_version$key>(
|
version: Version;
|
||||||
versionFragment,
|
selectedStates: SignatureState[];
|
||||||
props.version
|
}) {
|
||||||
);
|
const [version, refetch] = useRefetchableFragment<
|
||||||
|
DocumentSignaturesTabRefetchQuery,
|
||||||
|
DocumentSignaturesTab_version$key
|
||||||
|
>(versionFragment, props.version);
|
||||||
const signatures = version.signatures?.edges?.map((edge) => edge.node) ?? [];
|
const signatures = version.signatures?.edges?.map((edge) => edge.node) ?? [];
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const signatureMap = new Map(signatures.map((s) => [s.signedBy.id, s]));
|
const signatureMap = new Map(signatures.map((s) => [s.signedBy.id, s]));
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
const people = usePeople(organizationId, { excludeContractEnded: true });
|
const people = usePeople(organizationId, { excludeContractEnded: true });
|
||||||
const signable = version.status === "PUBLISHED";
|
const signable = version.status === "PUBLISHED";
|
||||||
|
const isFirstRender = useRef(true);
|
||||||
|
|
||||||
// Refetch when filter changes
|
// Refetch when filter changes (skip initial render)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const filter = props.selectedStates.length > 0
|
if (isFirstRender.current) {
|
||||||
? { states: props.selectedStates }
|
isFirstRender.current = false;
|
||||||
: null;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filter =
|
||||||
|
props.selectedStates.length > 0 ? { states: props.selectedStates } : null;
|
||||||
|
|
||||||
refetch({ signatureFilter: filter });
|
refetch({ signatureFilter: filter });
|
||||||
}, [JSON.stringify(props.selectedStates), refetch]);
|
}, [JSON.stringify(props.selectedStates), refetch]);
|
||||||
@@ -146,9 +161,10 @@ function SignatureList(props: { version: Version; selectedStates: SignatureState
|
|||||||
}
|
}
|
||||||
|
|
||||||
// When a filter is active, only show people who have signatures in the filtered results
|
// When a filter is active, only show people who have signatures in the filtered results
|
||||||
const filteredPeople = props.selectedStates.length > 0
|
const filteredPeople =
|
||||||
? people.filter((p) => signatureMap.has(p.id))
|
props.selectedStates.length > 0
|
||||||
: people;
|
? people.filter((p) => signatureMap.has(p.id))
|
||||||
|
: people;
|
||||||
|
|
||||||
if (filteredPeople.length === 0 && props.selectedStates.length > 0) {
|
if (filteredPeople.length === 0 && props.selectedStates.length > 0) {
|
||||||
return (
|
return (
|
||||||
@@ -174,9 +190,6 @@ function SignatureList(props: { version: Version; selectedStates: SignatureState
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragments
|
|
||||||
*/
|
|
||||||
const signatureFragment = graphql`
|
const signatureFragment = graphql`
|
||||||
fragment DocumentSignaturesTab_signature on DocumentVersionSignature {
|
fragment DocumentSignaturesTab_signature on DocumentVersionSignature {
|
||||||
id
|
id
|
||||||
@@ -237,14 +250,14 @@ function SignatureItem(props: {
|
|||||||
{
|
{
|
||||||
successMessage: __("Signature request sent successfully"),
|
successMessage: __("Signature request sent successfully"),
|
||||||
errorMessage: __("Failed to send signature request"),
|
errorMessage: __("Failed to send signature request"),
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
const [cancelSignature, isCancellingSignature] = useMutationWithToasts(
|
const [cancelSignature, isCancellingSignature] = useMutationWithToasts(
|
||||||
cancelSignatureMutation,
|
cancelSignatureMutation,
|
||||||
{
|
{
|
||||||
successMessage: __("Request cancelled successfully"),
|
successMessage: __("Request cancelled successfully"),
|
||||||
errorMessage: __("Failed to cancel signature request"),
|
errorMessage: __("Failed to cancel signature request"),
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// No signature request for this user
|
// No signature request for this user
|
||||||
@@ -304,8 +317,8 @@ function SignatureItem(props: {
|
|||||||
{sprintf(
|
{sprintf(
|
||||||
label,
|
label,
|
||||||
dateTimeFormat(
|
dateTimeFormat(
|
||||||
isSigned ? signature.signedAt : signature.requestedAt,
|
isSigned ? signature.signedAt : signature.requestedAt
|
||||||
),
|
)
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ func (pvs DocumentVersionSignatureState) Value() (driver.Value, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (states DocumentVersionSignatureStates) Value() (driver.Value, error) {
|
func (states DocumentVersionSignatureStates) Value() (driver.Value, error) {
|
||||||
|
if states == nil || len(states) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
result.WriteString("{")
|
result.WriteString("{")
|
||||||
for i, state := range states {
|
for i, state := range states {
|
||||||
|
|||||||
Reference in New Issue
Block a user