Skip to content

Commit

Permalink
Merge pull request #680 from bcgov/feat/attachments-in-tasklist
Browse files Browse the repository at this point in the history
Feat: attachments in tasklist
  • Loading branch information
matthieu-foucault committed Jun 2, 2022
2 parents 15950bf + 252560d commit df93c96
Show file tree
Hide file tree
Showing 13 changed files with 281 additions and 330 deletions.
9 changes: 1 addition & 8 deletions app/components/Attachment/AttachmentTableRow.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { Button } from "@button-inc/bcgov-theme";
import Link from "next/link";
import {
getAttachmentDownloadRoute,
getAttachmentViewPageRoute,
} from "pageRoutes";
import { getAttachmentDownloadRoute } from "pageRoutes";
import { useFragment, graphql } from "react-relay";
import { AttachmentTableRow_attachment$key } from "__generated__/AttachmentTableRow_attachment.graphql";

Expand Down Expand Up @@ -44,10 +41,6 @@ const AttachmentTableRow: React.FC<Props> = ({ attachment }) => {
<td>{fullName}</td>
<td>{createdAt}</td>
<td className="links">
<Link href={getAttachmentViewPageRoute(id)} passHref>
<Button size="small">View</Button>
</Link>
&nbsp;
<Link href={getAttachmentDownloadRoute(id)} passHref>
<Button size="small">Download</Button>
</Link>
Expand Down
62 changes: 62 additions & 0 deletions app/components/TaskList/AttachmentsTaskListSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import Link from "next/link";
import { useRouter } from "next/router";

interface Props {
icon: IconDefinition;
title: string;
linkUrl: { pathname: string; query: { projectRevision: string } };
}

const AttachmentsTaskListSection: React.FC<Props> = ({
icon,
title,
linkUrl,
}) => {
const router = useRouter();
return (
<li
aria-current={
router.pathname ===
"/cif/project-revision/[projectRevision]/attachments"
? "step"
: false
}
>
<Link href={linkUrl} passHref>
<h3>
<span className="link">
{title} <FontAwesomeIcon icon={icon} />
</span>
</h3>
</Link>
<style jsx>{`
li {
margin-bottom: 0;
cursor: pointer;
}
li[aria-current="step"],
li[aria-current="step"] div {
background-color: #fafafc;
}
h3 {
font-size: 1rem;
line-height: 1;
border-bottom: 1px solid #d1d1d1;
padding: 10px 0 10px 0.5em;
margin: 0;
display: flex;
justify-content: space-between;
}
h3,
ul {
list-style: none;
margin: 0;
}
`}</style>
</li>
);
};

export default AttachmentsTaskListSection;
12 changes: 12 additions & 0 deletions app/components/TaskList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { faPaperclip } from "@fortawesome/free-solid-svg-icons";
import { useRouter } from "next/router";
import {
getProjectRevisionPageRoute,
getProjectRevisionFormPageRoute,
getProjectRevisionAttachmentsPageRoute,
} from "pageRoutes";
import { useMemo } from "react";
import { graphql, useFragment } from "react-relay";
import { TaskList_projectRevision$key } from "__generated__/TaskList_projectRevision.graphql";
import AttachmentsTaskListSection from "./AttachmentsTaskListSection";
import TaskListItem from "./TaskListItem";
import TaskListSection from "./TaskListSection";
import { TaskListMode } from "./types";
Expand Down Expand Up @@ -175,6 +178,15 @@ const TaskList: React.FC<Props> = ({ projectRevision, mode }) => {
/>
</TaskListSection>
)}

{/* Attachments Section */}
{mode === "view" && (
<AttachmentsTaskListSection
icon={faPaperclip}
title="Attachments"
linkUrl={getProjectRevisionAttachmentsPageRoute(id)}
/>
)}
</ol>
<style jsx>{`
ol {
Expand Down
23 changes: 15 additions & 8 deletions app/mutations/attachment/createAttachment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ import type { createAttachmentMutation } from "createAttachmentMutation.graphql"
import useMutationWithErrorMessage from "mutations/useMutationWithErrorMessage";

const mutation = graphql`
mutation createAttachmentMutation($input: CreateAttachmentInput!) {
mutation createAttachmentMutation(
$connections: [ID!]!
$input: CreateAttachmentInput!
) {
createAttachment(input: $input) {
attachment {
file
fileName
fileSize
fileType
createdBy
projectId
attachmentEdge @appendEdge(connections: $connections) {
cursor
node {
file
fileName
fileSize
fileType
createdBy
projectId
...AttachmentTableRow_attachment
}
}
}
}
Expand Down
30 changes: 9 additions & 21 deletions app/pageRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ export const getProjectRevisionFormPageRoute = (
},
});

export const getProjectRevisionAttachmentsPageRoute = (
projectRevisionId: string
) => ({
pathname: `/cif/project-revision/[projectRevision]/attachments/`,
query: {
projectRevision: projectRevisionId,
},
});

///// Project

export const getProjectsPageRoute = () => ({
Expand All @@ -71,27 +80,6 @@ export const getProjectViewPageRoute = (projectId: string) => ({
},
});

export const getAttachmentsPageRoute = (projectId: string) => ({
pathname: "/cif/project/[project]/attachments",
query: {
project: projectId,
},
});

export const getAttachmentUploadPageRoute = (projectId: string) => ({
pathname: `/cif/project/[project]/upload-attachment`,
query: {
project: projectId,
},
});

export const getAttachmentViewPageRoute = (attachmentId: string) => ({
pathname: `/cif/attachments/[attachment]`,
query: {
attachment: attachmentId,
},
});

export const getAttachmentDownloadRoute = (attachmentId: string) => ({
pathname: `/download/${attachmentId}`,
});
44 changes: 0 additions & 44 deletions app/pages/cif/attachments/[attachment]/index.tsx

This file was deleted.

131 changes: 131 additions & 0 deletions app/pages/cif/project-revision/[projectRevision]/attachments.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import DefaultLayout from "components/Layout/DefaultLayout";
import { withRelay, RelayProps } from "relay-nextjs";
import { graphql, usePreloadedQuery } from "react-relay";
import { attachmentsQuery } from "__generated__/attachmentsQuery.graphql";
import withRelayOptions from "lib/relay/withRelayOptions";
import useRedirectTo404IfFalsy from "hooks/useRedirectTo404IfFalsy";
import Table from "components/Table";
import { NoHeaderFilter, TextFilter } from "components/Table/Filters";
import AttachmentTableRow from "components/Attachment/AttachmentTableRow";
import TaskList from "components/TaskList";
import { FilePicker } from "@button-inc/bcgov-theme";
import { useCreateAttachment } from "mutations/attachment/createAttachment";
import bytesToSize from "lib/helpers/bytesToText";
import LoadingSpinner from "components/LoadingSpinner";

const pageQuery = graphql`
query attachmentsQuery($projectRevision: ID!) {
session {
...DefaultLayout_session
}
projectRevision(id: $projectRevision) {
project: projectByProjectId {
projectName
rowId
attachments: attachmentsByProjectId(first: 2147483647)
@connection(key: "connection_attachments") {
__id
totalCount
edges {
node {
file
...AttachmentTableRow_attachment
}
}
}
}
...TaskList_projectRevision
}
}
`;

const tableFilters = [
new TextFilter("File Name", "fileName"),
new TextFilter("Type", "type"),
new TextFilter("Size", "size"),
new TextFilter("Uploaded by", "uploadedBy"),
new TextFilter("Received", "received"),
new NoHeaderFilter(),
];

export function ProjectAttachments({
preloadedQuery,
}: RelayProps<{}, attachmentsQuery>) {
const { session, projectRevision } = usePreloadedQuery(
pageQuery,
preloadedQuery
);

const [createAttachment, isCreatingAttachment] = useCreateAttachment();

const isRedirecting = useRedirectTo404IfFalsy(projectRevision);
if (isRedirecting) return null;

const saveAttachment = async (e) => {
var file = e.target.files[0];
const variables = {
input: {
attachment: {
file: file,
fileName: file.name,
fileSize: bytesToSize(file.size),
fileType: file.type,
projectId: projectRevision.project.rowId,
},
},
connections: [projectRevision.project.attachments.__id],
};
createAttachment({
variables,
onError: (err) => console.error(err),
});
};

const taskList = <TaskList projectRevision={projectRevision} mode={"view"} />;

return (
<DefaultLayout session={session} leftSideNav={taskList}>
<h2>{projectRevision.project.projectName}</h2>
<h3>Attachments List</h3>
{isCreatingAttachment ? (
<div>
<div className="loadingSpinnerContainer">
<LoadingSpinner></LoadingSpinner>
<span>Uploading file...</span>
</div>
</div>
) : (
<FilePicker onChange={saveAttachment} name={"upload-attachment"}>
Upload New Attachment
</FilePicker>
)}
<Table
paginated
totalRowCount={projectRevision.project.attachments.totalCount}
filters={tableFilters}
pageQuery={pageQuery}
>
{projectRevision.project.attachments.edges.map(({ node }) => (
<AttachmentTableRow key={node.file} attachment={node} />
))}
</Table>
<style jsx>{`
header > section {
display: flex;
justify-content: space-between;
}
.loadingSpinnerContainer {
display: flex;
}
.loadingSpinnerContainer > span {
margin: auto 0.5em;
}
div :global(div.spinner) {
margin: 0;
}
`}</style>
</DefaultLayout>
);
}

export default withRelay(ProjectAttachments, pageQuery, withRelayOptions);
Loading

0 comments on commit df93c96

Please sign in to comment.