Add vendor compliance report tab
Signed-off-by: Bryan Frimin <bryan@getprobo.com> Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
committed by
Sacha Al Himdani
parent
df23fd635f
commit
7cb84d8ce8
@@ -34,13 +34,7 @@ export const riskSchema = z.object({
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
ownerId: z.string(),
|
||||
treatment: z.enum([
|
||||
"AVOIDED",
|
||||
"MITIGATED",
|
||||
"TRANSFERRED",
|
||||
"ACCEPTED",
|
||||
"%future added value",
|
||||
]),
|
||||
treatment: z.enum(["AVOIDED", "MITIGATED", "TRANSFERRED", "ACCEPTED"]),
|
||||
inherentLikelihood: z.number({ coerce: true }).min(1).max(5),
|
||||
inherentImpact: z.number({ coerce: true }).min(1).max(5),
|
||||
residualLikelihood: z.number({ coerce: true }).min(1).max(5),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { useFormWithSchema } from "../useFormWithSchema";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useFragment, useMutation } from "react-relay";
|
||||
import { useFragment } from "react-relay";
|
||||
import type { useVendorFormFragment$key } from "./__generated__/useVendorFormFragment.graphql";
|
||||
import { useMutationWithToasts } from "../useMutationWithToasts";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
|
||||
@@ -131,6 +131,7 @@ export const vendorNodeQuery = graphql`
|
||||
name
|
||||
websiteUrl
|
||||
...useVendorFormFragment
|
||||
...VendorComplianceTabFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<57ff86dca8786cdaf42f34f9cbe379a1>>
|
||||
* @generated SignedSource<<6d50678dccfbe35e4ade26b58cda2ad3>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -18,7 +18,7 @@ export type VendorGraphNodeQuery$data = {
|
||||
readonly id?: string;
|
||||
readonly name?: string;
|
||||
readonly websiteUrl?: string | null | undefined;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"useVendorFormFragment">;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"VendorComplianceTabFragment" | "useVendorFormFragment">;
|
||||
};
|
||||
};
|
||||
export type VendorGraphNodeQuery = {
|
||||
@@ -62,8 +62,22 @@ v4 = {
|
||||
"name": "websiteUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = [
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = [
|
||||
(v2/*: any*/)
|
||||
],
|
||||
v7 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 50
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
@@ -90,6 +104,11 @@ return {
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "useVendorFormFragment"
|
||||
},
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "VendorComplianceTabFragment"
|
||||
}
|
||||
],
|
||||
"type": "Vendor",
|
||||
@@ -116,13 +135,7 @@ return {
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/),
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
@@ -213,7 +226,7 @@ return {
|
||||
"kind": "LinkedField",
|
||||
"name": "businessOwner",
|
||||
"plural": false,
|
||||
"selections": (v5/*: any*/),
|
||||
"selections": (v6/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
@@ -223,8 +236,147 @@ return {
|
||||
"kind": "LinkedField",
|
||||
"name": "securityOwner",
|
||||
"plural": false,
|
||||
"selections": (v5/*: any*/),
|
||||
"selections": (v6/*: any*/),
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v7/*: any*/),
|
||||
"concreteType": "VendorComplianceReportConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "complianceReports",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "VendorComplianceReportEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "VendorComplianceReport",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "reportDate",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "validUntil",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "reportName",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fileUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fileSize",
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "PageInfo",
|
||||
"kind": "LinkedField",
|
||||
"name": "pageInfo",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "endCursor",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasNextPage",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "hasPreviousPage",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "startCursor",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": "complianceReports(first:50)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v7/*: any*/),
|
||||
"filters": [
|
||||
"orderBy"
|
||||
],
|
||||
"handle": "connection",
|
||||
"key": "VendorComplianceTabFragment_complianceReports",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "complianceReports"
|
||||
}
|
||||
],
|
||||
"type": "Vendor",
|
||||
@@ -236,16 +388,16 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "3e7b2628c65e007a1603b982e41fea12",
|
||||
"cacheID": "f4827b073b1e66d0f2fa29283f276ac1",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "VendorGraphNodeQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query VendorGraphNodeQuery(\n $vendorId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n websiteUrl\n ...useVendorFormFragment\n }\n id\n }\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n"
|
||||
"text": "query VendorGraphNodeQuery(\n $vendorId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n websiteUrl\n ...useVendorFormFragment\n ...VendorComplianceTabFragment\n }\n id\n }\n}\n\nfragment VendorComplianceTabFragment on Vendor {\n complianceReports(first: 50) {\n edges {\n node {\n id\n ...VendorComplianceTabFragment_report\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorComplianceTabFragment_report on VendorComplianceReport {\n id\n reportDate\n validUntil\n reportName\n fileUrl\n fileSize\n}\n\nfragment useVendorFormFragment on Vendor {\n id\n name\n description\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n websiteUrl\n legalName\n headquarterAddress\n certifications\n securityPageUrl\n trustPageUrl\n businessOwner {\n id\n }\n securityOwner {\n id\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "9a718caa45c75f17f4b910ca2c555781";
|
||||
(node as any).hash = "97226bc4f316909815617a30a3a88cd9";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
Card,
|
||||
Field,
|
||||
FileButton,
|
||||
IconPlusLarge,
|
||||
Label,
|
||||
PageHeader,
|
||||
Spinner,
|
||||
@@ -23,7 +22,7 @@ import type {
|
||||
SettingsPageFragment$key,
|
||||
} from "./__generated__/SettingsPageFragment.graphql";
|
||||
import type { ChangeEventHandler } from "react";
|
||||
import { groupBy, sprintf } from "@probo/helpers";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import type { NodeOf } from "/types";
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -12,23 +12,17 @@ import {
|
||||
DropdownItem,
|
||||
IconPencil,
|
||||
IconTrashCan,
|
||||
useConfirm,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { PeopleGraphPaginatedQuery } from "/hooks/graph/__generated__/PeopleGraphPaginatedQuery.graphql";
|
||||
import { useMutation, type PreloadedQuery } from "react-relay";
|
||||
import {
|
||||
deletePeopleMutation,
|
||||
useDeletePeople,
|
||||
usePeopleQuery,
|
||||
} from "/hooks/graph/PeopleGraph";
|
||||
import { type PreloadedQuery } from "react-relay";
|
||||
import { useDeletePeople, usePeopleQuery } from "/hooks/graph/PeopleGraph";
|
||||
import { SortableTable, SortableTh } from "/components/SortableTable";
|
||||
import type { PeopleGraphPaginatedFragment$data } from "/hooks/graph/__generated__/PeopleGraphPaginatedFragment.graphql";
|
||||
import type { NodeOf } from "/types";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { getRole, sprintf } from "@probo/helpers";
|
||||
import { getRole } from "@probo/helpers";
|
||||
import { CreatePeopleDialog } from "./dialogs/CreatePeopleDialog";
|
||||
import type { PeopleGraphDeleteMutation } from "/hooks/graph/__generated__/PeopleGraphDeleteMutation.graphql";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { Link } from "react-router";
|
||||
|
||||
|
||||
@@ -38,6 +38,11 @@ export function PolicySignaturesDialog(props: Props) {
|
||||
versions[0].id
|
||||
);
|
||||
const selectedVersion = versions.find((v) => v.id === selectedVersionId);
|
||||
|
||||
if (!selectedVersion) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog trigger={props.children}>
|
||||
<DialogContent className="flex" scrollableChildren>
|
||||
@@ -68,7 +73,7 @@ export function PolicySignaturesDialog(props: Props) {
|
||||
</Suspense>
|
||||
</main>
|
||||
</DialogContent>
|
||||
<DialogFooter></DialogFooter>
|
||||
<DialogFooter />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
useDialogRef,
|
||||
useToast,
|
||||
} from "@probo/ui";
|
||||
import { useState, type RefObject } from "react";
|
||||
import { type RefObject } from "react";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useMutation } from "react-relay";
|
||||
import type { PolicyPagePolicyFragment$data } from "../__generated__/PolicyPagePolicyFragment.graphql";
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useVendorSearch } from "/hooks/useVendorSearch";
|
||||
import { faviconUrl } from "@probo/helpers";
|
||||
import type { Vendor } from "@probo/vendors";
|
||||
import { useCreateVendorMutation } from "/hooks/graph/VendorGraph";
|
||||
import { type ReactNode, useState } from "react";
|
||||
import { type ReactNode } from "react";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
} from "/hooks/graph/VendorGraph";
|
||||
import {
|
||||
ActionDropdown,
|
||||
Avatar,
|
||||
Breadcrumb,
|
||||
DropdownItem,
|
||||
IconTrashCan,
|
||||
|
||||
@@ -3,6 +3,24 @@ import { graphql } from "relay-runtime";
|
||||
import type { VendorComplianceTabFragment$key } from "./__generated__/VendorComplianceTabFragment.graphql";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import {
|
||||
ActionDropdown,
|
||||
DropdownItem,
|
||||
Dropzone,
|
||||
IconTrashCan,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
useConfirm,
|
||||
} from "@probo/ui";
|
||||
import { useFragment, useMutation, useRefetchableFragment } from "react-relay";
|
||||
import type { VendorComplianceTabFragment_report$key } from "./__generated__/VendorComplianceTabFragment_report.graphql";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { fileSize } from "@probo/helpers/src/file.ts";
|
||||
import { SortableTable, SortableTh } from "/components/SortableTable";
|
||||
|
||||
const complianceReportsFragment = graphql`
|
||||
fragment VendorComplianceTabFragment on Vendor
|
||||
@@ -21,8 +39,10 @@ const complianceReportsFragment = graphql`
|
||||
before: $before
|
||||
orderBy: $order
|
||||
) @connection(key: "VendorComplianceTabFragment_complianceReports") {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...VendorComplianceTabFragment_report
|
||||
}
|
||||
}
|
||||
@@ -32,6 +52,7 @@ const complianceReportsFragment = graphql`
|
||||
|
||||
const complianceReportFragment = graphql`
|
||||
fragment VendorComplianceTabFragment_report on VendorComplianceReport {
|
||||
id
|
||||
reportDate
|
||||
validUntil
|
||||
reportName
|
||||
@@ -40,12 +61,161 @@ const complianceReportFragment = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
const uploadReportMutation = graphql`
|
||||
mutation VendorComplianceTabUploadReportMutation(
|
||||
$input: UploadVendorComplianceReportInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
uploadVendorComplianceReport(input: $input) {
|
||||
vendorComplianceReportEdge @appendEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
...VendorComplianceTabFragment_report
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const deleteReportMutation = graphql`
|
||||
mutation VendorComplianceTabDeleteReportMutation(
|
||||
$input: DeleteVendorComplianceReportInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
deleteVendorComplianceReport(input: $input) {
|
||||
deletedVendorComplianceReportId @deleteEdge(connections: $connections)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function VendorComplianceTab() {
|
||||
const { vendor } = useOutletContext<{
|
||||
vendor: VendorComplianceTabFragment$key & { name: string };
|
||||
vendor: VendorComplianceTabFragment$key & { name: string; id: string };
|
||||
}>();
|
||||
const [data, refetch] = useRefetchableFragment(
|
||||
complianceReportsFragment,
|
||||
vendor
|
||||
);
|
||||
const connectionId = data.complianceReports.__id;
|
||||
const reports = data.complianceReports.edges.map((edge) => edge.node);
|
||||
const { __ } = useTranslate();
|
||||
|
||||
usePageTitle(vendor.name + " - " + __("Compliance reports"));
|
||||
return <div>Vendor Compliance</div>;
|
||||
|
||||
const [mutate, isMutating] = useMutation(uploadReportMutation);
|
||||
|
||||
const handleDrop = (files: File[]) => {
|
||||
const reportDate = new Date().toISOString();
|
||||
for (const file of files) {
|
||||
mutate({
|
||||
variables: {
|
||||
connections: [connectionId],
|
||||
input: {
|
||||
vendorId: vendor.id,
|
||||
reportDate,
|
||||
reportName: file.name,
|
||||
file: null,
|
||||
},
|
||||
},
|
||||
uploadables: {
|
||||
"input.file": file,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<Dropzone
|
||||
description={__("Only PDF files up to 10MB are allowed")}
|
||||
isUploading={isMutating}
|
||||
onDrop={handleDrop}
|
||||
accept={{
|
||||
"application/pdf": [".pdf"],
|
||||
}}
|
||||
maxSize={10}
|
||||
/>
|
||||
<SortableTable refetch={refetch}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Report name")}</Th>
|
||||
<SortableTh field="REPORT_DATE">{__("Report date")}</SortableTh>
|
||||
<Th>{__("Valid until")}</Th>
|
||||
<Th>{__("File size")}</Th>
|
||||
<Th>{__("Actions")}</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{reports.map((report) => (
|
||||
<ReportRow
|
||||
key={report.id}
|
||||
reportKey={report}
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</SortableTable>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type ReportRowProps = {
|
||||
reportKey: VendorComplianceTabFragment_report$key;
|
||||
connectionId: string;
|
||||
};
|
||||
|
||||
function ReportRow(props: ReportRowProps) {
|
||||
const { __ } = useTranslate();
|
||||
const report = useFragment<VendorComplianceTabFragment_report$key>(
|
||||
complianceReportFragment,
|
||||
props.reportKey
|
||||
);
|
||||
const { dateFormat } = useTranslate();
|
||||
const confirm = useConfirm();
|
||||
const [deleteReport] = useMutationWithToasts(deleteReportMutation, {
|
||||
successMessage: __("Report deleted successfully"),
|
||||
errorMessage: __("Failed to delete report"),
|
||||
});
|
||||
|
||||
const handleDelete = () => {
|
||||
confirm(
|
||||
() =>
|
||||
deleteReport({
|
||||
variables: {
|
||||
connections: [props.connectionId],
|
||||
input: {
|
||||
reportId: report.id,
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
message: sprintf(
|
||||
__(
|
||||
'This will permanently delete the report "%s". This action cannot be undone.'
|
||||
),
|
||||
report.reportName
|
||||
),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Tr>
|
||||
<Td>{report.reportName}</Td>
|
||||
<Td>{dateFormat(report.reportDate)}</Td>
|
||||
<Td>{dateFormat(report.validUntil)}</Td>
|
||||
<Td>{fileSize(__, report.fileSize)}</Td>
|
||||
<Td width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
onClick={handleDelete}
|
||||
variant="danger"
|
||||
>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
</ActionDropdown>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<75defbef64a4ee18b514465a44c8eea1>>
|
||||
* @generated SignedSource<<10fdd09e95fc5f2995b91c73cf9fb945>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -211,6 +211,7 @@ return {
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v12/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -246,7 +247,6 @@ return {
|
||||
"name": "fileSize",
|
||||
"storageKey": null
|
||||
},
|
||||
(v12/*: any*/),
|
||||
(v11/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
@@ -299,6 +299,18 @@ return {
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
@@ -324,16 +336,16 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "76e0b5dbc01424695b8c642e27c0f4ed",
|
||||
"cacheID": "c819e002dfe9f20004fb5bfb4f95b143",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "ComplianceReportListQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query ComplianceReportListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: VendorComplianceReportOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorComplianceTabFragment_16fISc\n id\n }\n}\n\nfragment VendorComplianceTabFragment_16fISc on Vendor {\n complianceReports(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n ...VendorComplianceTabFragment_report\n id\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorComplianceTabFragment_report on VendorComplianceReport {\n reportDate\n validUntil\n reportName\n fileUrl\n fileSize\n}\n"
|
||||
"text": "query ComplianceReportListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: VendorComplianceReportOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...VendorComplianceTabFragment_16fISc\n id\n }\n}\n\nfragment VendorComplianceTabFragment_16fISc on Vendor {\n complianceReports(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n ...VendorComplianceTabFragment_report\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment VendorComplianceTabFragment_report on VendorComplianceReport {\n id\n reportDate\n validUntil\n reportName\n fileUrl\n fileSize\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "7aae49a4b414ed4db5e7b39f8969c02f";
|
||||
(node as any).hash = "1fe4e0b1f9fb781c3b01e539145d610a";
|
||||
|
||||
export default node;
|
||||
|
||||
132
apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorComplianceTabDeleteReportMutation.graphql.ts
generated
vendored
Normal file
132
apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorComplianceTabDeleteReportMutation.graphql.ts
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* @generated SignedSource<<6fc7bf1e239dfeccb007ee5a9beefaff>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type DeleteVendorComplianceReportInput = {
|
||||
reportId: string;
|
||||
};
|
||||
export type VendorComplianceTabDeleteReportMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: DeleteVendorComplianceReportInput;
|
||||
};
|
||||
export type VendorComplianceTabDeleteReportMutation$data = {
|
||||
readonly deleteVendorComplianceReport: {
|
||||
readonly deletedVendorComplianceReportId: string;
|
||||
};
|
||||
};
|
||||
export type VendorComplianceTabDeleteReportMutation = {
|
||||
response: VendorComplianceTabDeleteReportMutation$data;
|
||||
variables: VendorComplianceTabDeleteReportMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "connections"
|
||||
},
|
||||
v1 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
},
|
||||
v2 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "deletedVendorComplianceReportId",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "VendorComplianceTabDeleteReportMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteVendorComplianceReportPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteVendorComplianceReport",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "VendorComplianceTabDeleteReportMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteVendorComplianceReportPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteVendorComplianceReport",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "deleteEdge",
|
||||
"key": "",
|
||||
"kind": "ScalarHandle",
|
||||
"name": "deletedVendorComplianceReportId",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "3bfaddbf09d2cd72a0bb72687c1e187e",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "VendorComplianceTabDeleteReportMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation VendorComplianceTabDeleteReportMutation(\n $input: DeleteVendorComplianceReportInput!\n) {\n deleteVendorComplianceReport(input: $input) {\n deletedVendorComplianceReportId\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "7151aea21838384dc10781b64f0209ae";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<079a3066496976bd1238ba194f5a0660>>
|
||||
* @generated SignedSource<<e5f0f2600db655bd96ca2c7ea52f8b44>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -12,8 +12,10 @@ import { ReaderFragment } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type VendorComplianceTabFragment$data = {
|
||||
readonly complianceReports: {
|
||||
readonly __id: string;
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"VendorComplianceTabFragment_report">;
|
||||
};
|
||||
}>;
|
||||
@@ -31,7 +33,14 @@ import ComplianceReportListQuery_graphql from './ComplianceReportListQuery.graph
|
||||
const node: ReaderFragment = (function(){
|
||||
var v0 = [
|
||||
"complianceReports"
|
||||
];
|
||||
],
|
||||
v1 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"argumentDefinitions": [
|
||||
{
|
||||
@@ -124,6 +133,7 @@ return {
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
@@ -187,23 +197,29 @@ return {
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
}
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"type": "Vendor",
|
||||
"abstractKey": null
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "7aae49a4b414ed4db5e7b39f8969c02f";
|
||||
(node as any).hash = "1fe4e0b1f9fb781c3b01e539145d610a";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<01d2d791b86ae99330d4ba2ecb01f1ed>>
|
||||
* @generated SignedSource<<8f56e9dbd8ce8cf0c81d8f2e9f1ad06e>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -13,6 +13,7 @@ import { FragmentRefs } from "relay-runtime";
|
||||
export type VendorComplianceTabFragment_report$data = {
|
||||
readonly fileSize: number;
|
||||
readonly fileUrl: string;
|
||||
readonly id: string;
|
||||
readonly reportDate: any;
|
||||
readonly reportName: string;
|
||||
readonly validUntil: any | null | undefined;
|
||||
@@ -29,6 +30,13 @@ const node: ReaderFragment = {
|
||||
"metadata": null,
|
||||
"name": "VendorComplianceTabFragment_report",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -69,6 +77,6 @@ const node: ReaderFragment = {
|
||||
"abstractKey": null
|
||||
};
|
||||
|
||||
(node as any).hash = "d39b70dfd0a316b158d7dc7253d0ff5e";
|
||||
(node as any).hash = "92ad78c9e4b4add9b1f622485d81a6ff";
|
||||
|
||||
export default node;
|
||||
|
||||
226
apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorComplianceTabUploadReportMutation.graphql.ts
generated
vendored
Normal file
226
apps/console2/src/pages/organizations/vendors/tabs/__generated__/VendorComplianceTabUploadReportMutation.graphql.ts
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* @generated SignedSource<<6093b0bc54fa90962376639ec87fa149>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type UploadVendorComplianceReportInput = {
|
||||
file: any;
|
||||
reportDate: any;
|
||||
reportName: string;
|
||||
validUntil?: any | null | undefined;
|
||||
vendorId: string;
|
||||
};
|
||||
export type VendorComplianceTabUploadReportMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: UploadVendorComplianceReportInput;
|
||||
};
|
||||
export type VendorComplianceTabUploadReportMutation$data = {
|
||||
readonly uploadVendorComplianceReport: {
|
||||
readonly vendorComplianceReportEdge: {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"VendorComplianceTabFragment_report">;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type VendorComplianceTabUploadReportMutation = {
|
||||
response: VendorComplianceTabUploadReportMutation$data;
|
||||
variables: VendorComplianceTabUploadReportMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "connections"
|
||||
},
|
||||
v1 = {
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "input"
|
||||
},
|
||||
v2 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "input",
|
||||
"variableName": "input"
|
||||
}
|
||||
],
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "VendorComplianceTabUploadReportMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "UploadVendorComplianceReportPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "uploadVendorComplianceReport",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "VendorComplianceReportEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "vendorComplianceReportEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "VendorComplianceReport",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "VendorComplianceTabFragment_report"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "VendorComplianceTabUploadReportMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "UploadVendorComplianceReportPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "uploadVendorComplianceReport",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "VendorComplianceReportEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "vendorComplianceReportEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "VendorComplianceReport",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "reportDate",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "validUntil",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "reportName",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fileUrl",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "fileSize",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "appendEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "vendorComplianceReportEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "fb9be687deba8567502e207b0969b2b5",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "VendorComplianceTabUploadReportMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation VendorComplianceTabUploadReportMutation(\n $input: UploadVendorComplianceReportInput!\n) {\n uploadVendorComplianceReport(input: $input) {\n vendorComplianceReportEdge {\n node {\n id\n ...VendorComplianceTabFragment_report\n }\n }\n }\n}\n\nfragment VendorComplianceTabFragment_report on VendorComplianceReport {\n id\n reportDate\n validUntil\n reportName\n fileUrl\n fileSize\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "4ed863a250b0686340056f4f77eb8ab8";
|
||||
|
||||
export default node;
|
||||
13
packages/helpers/src/file.test.ts
Normal file
13
packages/helpers/src/file.test.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { isEmpty } from "./array";
|
||||
import { fileSize } from "./file";
|
||||
|
||||
describe("file", () => {
|
||||
it("should display a file size in a human readable format", () => {
|
||||
const fakeTranslator = (s: string) => s;
|
||||
expect(fileSize(fakeTranslator, 4911)).toMatchInlineSnapshot(
|
||||
`"4.8 KB"`,
|
||||
);
|
||||
expect(fileSize(fakeTranslator, 20)).toMatchInlineSnapshot(`"20 B"`);
|
||||
});
|
||||
});
|
||||
19
packages/helpers/src/file.ts
Normal file
19
packages/helpers/src/file.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Return the file size in a human readable format
|
||||
*/
|
||||
export function fileSize(__: (s: string) => string, size: number): string {
|
||||
if (size < 0) return "";
|
||||
if (size === 0) return `0 ${__("B")}`;
|
||||
|
||||
const units = [__("B"), __("KB"), __("MB"), __("GB"), __("TB")];
|
||||
const i = Math.floor(Math.log(size) / Math.log(1024));
|
||||
|
||||
// Don't go beyond available units
|
||||
const unitIndex = Math.min(i, units.length - 1);
|
||||
|
||||
// Convert to the appropriate unit with 2 decimal places
|
||||
const convertedSize = size / Math.pow(1024, unitIndex);
|
||||
const formattedSize = Math.round(convertedSize * 100) / 100;
|
||||
|
||||
return `${formattedSize} ${units[unitIndex]}`;
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export { usePageTitle } from "./usePageTitle";
|
||||
export { useToggle } from "./useToggle";
|
||||
export { useRefSync } from "./useRefSync";
|
||||
|
||||
7
packages/hooks/src/useRefSync.ts
Normal file
7
packages/hooks/src/useRefSync.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { useRef } from "react";
|
||||
|
||||
export function useRefSync<T>(value: T) {
|
||||
const ref = useRef(value);
|
||||
ref.current = value;
|
||||
return ref;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ type Props = PropsWithChildren<{
|
||||
}>;
|
||||
|
||||
export const dropdown = tv({
|
||||
base: "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-dropdown-menu-content-transform-origin] z-50 p-2 shadow-mid min-w-[8rem] bg-level-1 overflow-y-auto overflow-x-hidden rounded-2xl border-border-low",
|
||||
base: "z-50 p-2 shadow-mid min-w-[8rem] bg-level-1 overflow-y-auto overflow-x-hidden rounded-2xl border-border-low",
|
||||
});
|
||||
|
||||
export function Dropdown({ children, toggle, className, open }: Props) {
|
||||
|
||||
@@ -9,4 +9,24 @@ export default {
|
||||
|
||||
type Story = StoryObj<typeof Dropzone>;
|
||||
|
||||
export const Default: Story = {};
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
description: "Maximum 500 MB file size",
|
||||
isUploading: false,
|
||||
disabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
disabled: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Uploading: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
isUploading: true,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,24 +1,129 @@
|
||||
import { useRefSync } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useCallback } from "react";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { useDropzone, type FileRejection } from "react-dropzone";
|
||||
import { tv } from "tailwind-variants";
|
||||
import { IconPageCross, IconUpload } from "../Icons";
|
||||
import { Spinner } from "../Spinner/Spinner";
|
||||
|
||||
type Props = {};
|
||||
type Props = {
|
||||
description: string;
|
||||
isUploading: boolean;
|
||||
disabled?: boolean;
|
||||
accept?: Record<string, string[]>;
|
||||
maxSize?: number; // maxSize in MB
|
||||
onDrop: (acceptedFiles: File[]) => void;
|
||||
};
|
||||
|
||||
export function Dropzone({}: Props) {
|
||||
const onDrop = useCallback((acceptedFiles) => {
|
||||
// Do something with the files
|
||||
}, []);
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
onDrop,
|
||||
export const dropzone = tv({
|
||||
slots: {
|
||||
wrapper:
|
||||
"bg-subtle border border border-border-low p-2 rounded-[20px] outline-none focus-visible:shadow-focus",
|
||||
zone: "bg-secondary min-h-46 border border-border-low border-dashed rounded-2xl flex items-center justify-center",
|
||||
title: "flex gap-2 text-sm font-medium text-center justify-center",
|
||||
description: "text-xs text-txt-tertiary text-center",
|
||||
},
|
||||
variants: {
|
||||
isDragActive: {
|
||||
true: {
|
||||
wrapper: "border-border-success shadow-focus",
|
||||
zone: "border-border-success",
|
||||
},
|
||||
},
|
||||
hasError: {
|
||||
true: {
|
||||
wrapper: "bg-danger",
|
||||
zone: "bg-invert",
|
||||
title: "text-txt-danger",
|
||||
description: "text-txt-danger",
|
||||
},
|
||||
},
|
||||
disabled: {
|
||||
true: {
|
||||
wrapper: "opacity-60 cursor-default",
|
||||
zone: "bg-highlight",
|
||||
},
|
||||
false: {
|
||||
zone: "hover:bg-secondary-hover",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
isDragActive: false,
|
||||
disabled: false,
|
||||
hasError: false,
|
||||
},
|
||||
});
|
||||
|
||||
const MB = 1024;
|
||||
|
||||
export function Dropzone(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const onDropRef = useRefSync(props.onDrop);
|
||||
const onDrop = useCallback(
|
||||
(files: File[]) => {
|
||||
onDropRef.current(files);
|
||||
},
|
||||
[onDropRef],
|
||||
);
|
||||
const { getRootProps, getInputProps, isDragActive, fileRejections } =
|
||||
useDropzone({
|
||||
disabled: props.disabled || props.isUploading,
|
||||
accept: props.accept,
|
||||
onDrop,
|
||||
maxSize: props.maxSize ? props.maxSize * MB : undefined,
|
||||
});
|
||||
const error = getDropzoneError(__, fileRejections);
|
||||
const { wrapper, zone, title, description } = dropzone({
|
||||
...props,
|
||||
isDragActive,
|
||||
hasError: !!error,
|
||||
});
|
||||
|
||||
console.log(error);
|
||||
|
||||
return (
|
||||
<div {...getRootProps()}>
|
||||
<input {...getInputProps()} />
|
||||
{isDragActive ? (
|
||||
<p>Drop the files here ...</p>
|
||||
) : (
|
||||
<p>Drag 'n' drop some files here, or click to select files</p>
|
||||
)}
|
||||
<div {...getRootProps()} className={wrapper()}>
|
||||
<div className={zone()}>
|
||||
<input {...getInputProps({ max: 10 })} />
|
||||
{props.isUploading ? (
|
||||
<div className={title({ isDragActive: true })}>
|
||||
<Spinner />
|
||||
{__("Uploading file")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<div className={title()}>
|
||||
{error ? (
|
||||
<IconPageCross size={20} />
|
||||
) : (
|
||||
<IconUpload size={20} />
|
||||
)}
|
||||
{error ?? __("Drag and drop or browse files")}
|
||||
</div>
|
||||
<div className={description()}>{props.description}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getDropzoneError(
|
||||
__: (s: string) => string,
|
||||
fileRejections: readonly FileRejection[],
|
||||
) {
|
||||
if (fileRejections.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const code = fileRejections[0].errors[0].code;
|
||||
switch (code) {
|
||||
case "file-invalid-type":
|
||||
return __("This file is not supported");
|
||||
case "file-too-large":
|
||||
return __("This file is too large");
|
||||
default:
|
||||
return __("Something went wrong");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
Root,
|
||||
Trigger,
|
||||
Overlay,
|
||||
Content,
|
||||
Title,
|
||||
Description,
|
||||
Cancel,
|
||||
} from "@radix-ui/react-alert-dialog";
|
||||
import {
|
||||
useCallback,
|
||||
useRef,
|
||||
useState,
|
||||
type ComponentProps,
|
||||
type ReactNode,
|
||||
type RefObject,
|
||||
} from "react";
|
||||
import { useCallback, useState, type ComponentProps } from "react";
|
||||
import { Button } from "../../Atoms/Button/Button";
|
||||
import { Root as Portal } from "@radix-ui/react-portal";
|
||||
import { dialog } from "./Dialog";
|
||||
|
||||
@@ -32,6 +32,7 @@ export { PropertyRow } from "./Atoms/PropertyRow/PropertyRow";
|
||||
export { Table, Thead, Tr, Tbody, Td, Th } from "./Atoms/Table/Table";
|
||||
export { Tabs, TabLink } from "./Atoms/Tabs/Tabs";
|
||||
export { Markdown } from "./Atoms/Markdown/Markdown";
|
||||
export { Dropzone } from "./Atoms/Dropzone/Dropzone";
|
||||
|
||||
// Molecules
|
||||
export {
|
||||
|
||||
Reference in New Issue
Block a user