Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-10-21 13:52:36 +02:00
parent 06c05db3a4
commit b1a92e3715
2 changed files with 43 additions and 26 deletions

View File

@@ -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,7 +161,8 @@ 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 =
props.selectedStates.length > 0
? people.filter((p) => signatureMap.has(p.id)) ? people.filter((p) => signatureMap.has(p.id))
: people; : people;
@@ -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>

View File

@@ -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 {