Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

172 feature reconfigure form signing approving and category logic #178

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
5 changes: 3 additions & 2 deletions apps/web/src/components/FormImageCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useRouter } from 'next/router';
import React from 'react';
import { useStorage } from '@web/hooks/useStorage';
import { Document, Page, pdfjs } from 'react-pdf';
import { isFullySigned } from '@web/utils/formInstanceUtils';

/**
* @param formName - the name of the form
Expand All @@ -28,7 +29,7 @@ export const FormImageCard = ({
const d2 = new Date(date2);
const diffInTime = d2.getTime() - d1.getTime();
const diffInDays = Math.ceil(diffInTime / (1000 * 60 * 60 * 24));
return diffInDays == 0 ? 'today' : `${diffInDays} days ago`;
return diffInDays == 0 ? 'today' : `${diffInDays}d ago`;
};

return (
Expand Down Expand Up @@ -90,7 +91,7 @@ export const FormImageCard = ({
borderRadius="20px"
padding="3px 12px"
>
TO DO
{isFullySigned(formInstance) ? 'NEEDS APPROVAL' : 'TO DO'}
</Text>
<Text
textAlign="center"
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/context/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const AuthProvider = ({ children }: any) => {
// Reset the error state if we change page
useEffect(() => {
if (error) setError(undefined);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.pathname]);

// Check if there is a currently active session
Expand Down Expand Up @@ -68,6 +69,7 @@ export const AuthProvider = ({ children }: any) => {
});
})
.finally(() => setLoadingInitial(false));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// Flags the component loading state and posts the login
Expand Down Expand Up @@ -120,6 +122,7 @@ export const AuthProvider = ({ children }: any) => {
login,
logout,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So many overrides 🫠

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:fear:

[user, loading, error],
);

Expand Down
94 changes: 69 additions & 25 deletions apps/web/src/hooks/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from './../../../web/src/client';
import { useAuth } from './useAuth';
import { useQuery } from '@tanstack/react-query';
import { isFullySigned, nextSigner } from '@web/utils/formInstanceUtils';

/**
* @returns an object containing the todo, pending, and completed forms
Expand Down Expand Up @@ -39,6 +40,30 @@ export const useForm = () => {
[],
);

/**
* Determines if a form instance is created by the current user
*
* @param formInstance the form instance to check
* @returns true if the form instance is created by the current user, false otherwise
*/
const isOriginator = (formInstance: FormInstanceEntity) => {
return formInstance.originator.id === user?.id;
};

/**
* Determines if a form instance is signed by the current user
*
* @param formInstance the form instance to check
* @returns true if the form instance is signed by the current user, false otherwise
*/
const isSignedByUser = (formInstance: FormInstanceEntity) => {
const signatures: SignatureEntity[] = formInstance.signatures;

return signatures.some((signature: SignatureEntity) => {
return signature.assignedUserId === user?.id && signature.signed;
});
};

useMemo(() => {
if (!assignedFIData || !createdFIData || !user) {
setTodoForms([]);
Expand All @@ -50,45 +75,64 @@ export const useForm = () => {
// Forms assigned to current user that they still need to sign
const todoForms: FormInstanceEntity[] = assignedFIData.filter(
(formInstance: FormInstanceEntity) => {
const signatures: SignatureEntity[] = formInstance.signatures;

// Sort signatures by order
signatures.sort((a: SignatureEntity, b: SignatureEntity) => {
return a.order - b.order;
});
return nextSigner(formInstance)?.assignedUserId === user?.id;
},
);

// Find the first signature that doesn't have a signature
const firstUnsignedSignature: SignatureEntity | undefined =
signatures.find((signature: SignatureEntity) => {
return signature.signed === false;
});
// Forms created by current user that are fully signed but not marked completed
const todoApproveForms: FormInstanceEntity[] = createdFIData.filter(
(formInstance: FormInstanceEntity) => {
return (
isFullySigned(formInstance) &&
isOriginator(formInstance) &&
!formInstance.markedCompleted
);
},
);

// If there is no unsigned signature, return false
if (!firstUnsignedSignature) {
return false;
}
// Forms created by current user that are not fully signed but the next signer is not the current user since that would be in the todo list
const originatorPendingForms: FormInstanceEntity[] = createdFIData.filter(
(formInstance: FormInstanceEntity) => {
return (
!isFullySigned(formInstance) &&
nextSigner(formInstance)?.assignedUserId !== user?.id
);
},
);

return firstUnsignedSignature.signerPositionId === user.positionId;
// Forms assigned to current user that are not fully signed or marked completed but are signed by the current user and not created by the current user
const signerPendingForms: FormInstanceEntity[] = assignedFIData.filter(
(formInstance: FormInstanceEntity) => {
return (
isSignedByUser(formInstance) &&
!formInstance.markedCompleted &&
!isOriginator(formInstance)
);
},
);

// Forms created by current user that aren't completed (all/some/no signatures + not marked completed)
const pendingForms: FormInstanceEntity[] = createdFIData.filter(
// Forms created by current user that are fully signed and marked completed
const originatorCompletedForms: FormInstanceEntity[] = createdFIData.filter(
(formInstance: FormInstanceEntity) => {
return !formInstance.markedCompleted;
return isFullySigned(formInstance) && formInstance.markedCompleted;
},
);

// Forms created by current user that are completed (all signatures + marked completed)
const completedForms: FormInstanceEntity[] = createdFIData.filter(
// Forms assigned to current user that are fully signed and marked completed but not created by the current user
const signerCompletedForms: FormInstanceEntity[] = assignedFIData.filter(
(formInstance: FormInstanceEntity) => {
return formInstance.markedCompleted;
return (
isFullySigned(formInstance) &&
formInstance.markedCompleted &&
!isOriginator(formInstance)
);
},
);

setTodoForms(todoForms);
setPendingForms(pendingForms);
setCompletedForms(completedForms);
setTodoForms([...todoForms, ...todoApproveForms]);
setPendingForms([...originatorPendingForms, ...signerPendingForms]);
setCompletedForms([...originatorCompletedForms, ...signerCompletedForms]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [assignedFIData, createdFIData, user]);

return {
Expand Down
43 changes: 43 additions & 0 deletions apps/web/src/utils/formInstanceUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { FormInstanceEntity, SignatureEntity } from '@web/client';

/**
* Determines if a form instance is fully signed
*
* @param formInstance the form instance to check
* @returns true if the form instance is fully signed, false otherwise
*/
export const isFullySigned = (formInstance: FormInstanceEntity) => {
const signatures: SignatureEntity[] = formInstance.signatures;

const unsignedSignatures: SignatureEntity[] = signatures.filter(
(signature: SignatureEntity) => {
return !signature.signed;
},
);

return unsignedSignatures.length === 0;
};

/**
* Finds the next signer in the signature chain of a form instance
*
* @param formInstance the form instance to check
* @returns the next signer in the signature chain
*/
export const nextSigner = (formInstance: FormInstanceEntity) => {
const signatures: SignatureEntity[] = formInstance.signatures;

// Sort signatures by order
signatures.sort((a: SignatureEntity, b: SignatureEntity) => {
return a.order - b.order;
});

// Find the first signature that doesn't have a signature
const firstUnsignedSignature: SignatureEntity | undefined = signatures.find(
(signature: SignatureEntity) => {
return signature.signed === false;
},
);

return firstUnsignedSignature;
};
Loading