Add link dialog for measure evidences
This commit is contained in:
@@ -99,7 +99,7 @@ export function SortableTh({
|
|||||||
size={16}
|
size={16}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
isCurrentField && "text-txt-primary",
|
isCurrentField && "text-txt-primary",
|
||||||
isCurrentField && !isDesc && "rotate-180"
|
isCurrentField && !isDesc && "rotate-180",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
import {
|
||||||
|
Breadcrumb,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
Dropzone,
|
||||||
|
Field,
|
||||||
|
Spinner,
|
||||||
|
TabItem,
|
||||||
|
Tabs,
|
||||||
|
type DialogRef,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { graphql, useMutation } from "react-relay";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||||
|
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||||
|
|
||||||
|
const uploadEvidenceMutation = graphql`
|
||||||
|
mutation CreateEvidenceDialogUploadMutation(
|
||||||
|
$input: UploadMeasureEvidenceInput!
|
||||||
|
$connections: [ID!]!
|
||||||
|
) {
|
||||||
|
uploadMeasureEvidence(input: $input) {
|
||||||
|
evidenceEdge @appendEdge(connections: $connections) {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
...MeasureEvidencesTabFragment_evidence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
measureId: string;
|
||||||
|
connectionId: string;
|
||||||
|
ref: DialogRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function CreateEvidenceDialog(props: Props) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const [tab, setTab] = useState("link");
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
title={
|
||||||
|
<Breadcrumb
|
||||||
|
items={[
|
||||||
|
{ label: __("Measures detail") },
|
||||||
|
{ label: __("Create Evidence") },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
ref={props.ref}
|
||||||
|
className="max-w-lg"
|
||||||
|
>
|
||||||
|
<Tabs className="px-6">
|
||||||
|
<TabItem active={tab === "upload"} onClick={() => setTab("upload")}>
|
||||||
|
{__("Upload")}
|
||||||
|
</TabItem>
|
||||||
|
<TabItem active={tab === "link"} onClick={() => setTab("link")}>
|
||||||
|
{__("Link")}
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
||||||
|
{tab === "upload" && <EvidenceUpload {...props} />}
|
||||||
|
{tab === "link" && <EvidenceLink {...props} />}
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EvidenceUpload({ measureId, connectionId }: Omit<Props, "ref">) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
const [mutate, isUpdating] = useMutation(uploadEvidenceMutation);
|
||||||
|
const handleDrop = (files: File[]) => {
|
||||||
|
for (const file of files) {
|
||||||
|
mutate({
|
||||||
|
variables: {
|
||||||
|
connections: [connectionId],
|
||||||
|
input: {
|
||||||
|
measureId: measureId,
|
||||||
|
file: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
uploadables: {
|
||||||
|
"input.file": file,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DialogContent padded>
|
||||||
|
<Dropzone
|
||||||
|
description={__(
|
||||||
|
"Only PDF, DOCX, XLSX, PPTX, JPG, PNG, WEBP files up to 10MB are allowed"
|
||||||
|
)}
|
||||||
|
isUploading={isUpdating}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
accept={{
|
||||||
|
"application/pdf": [".pdf"],
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
||||||
|
[".docx"],
|
||||||
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
||||||
|
[".xlsx"],
|
||||||
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
||||||
|
[".pptx"],
|
||||||
|
"image/jpeg": [".jpg", ".jpeg"],
|
||||||
|
"image/png": [".png"],
|
||||||
|
"image/webp": [".webp"],
|
||||||
|
}}
|
||||||
|
maxSize={10}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkSchema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
url: z.string().url(),
|
||||||
|
});
|
||||||
|
|
||||||
|
function EvidenceLink({ measureId, connectionId, ref }: Props) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const { handleSubmit, register, formState } = useFormWithSchema(linkSchema, {
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
url: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [mutate] = useMutationWithToasts(uploadEvidenceMutation, {
|
||||||
|
successMessage: __("Evidence created successfully"),
|
||||||
|
errorMessage: __("Failed to create evidence"),
|
||||||
|
});
|
||||||
|
const onSubmit = handleSubmit(async (data) => {
|
||||||
|
const fileName = `${data.name.trim()}.uri`;
|
||||||
|
const file = new File([data.url.trim()], fileName, {
|
||||||
|
type: "text/uri-list",
|
||||||
|
});
|
||||||
|
await mutate({
|
||||||
|
variables: {
|
||||||
|
connections: [connectionId],
|
||||||
|
input: {
|
||||||
|
measureId: measureId,
|
||||||
|
file: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
uploadables: {
|
||||||
|
"input.file": file,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ref.current?.close();
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<DialogContent padded className="space-y-4">
|
||||||
|
<Field
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
label={__("Name")}
|
||||||
|
placeholder={__("Evidence name")}
|
||||||
|
{...register("name")}
|
||||||
|
error={formState.errors.name?.message}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
required
|
||||||
|
type="url"
|
||||||
|
label={__("URL")}
|
||||||
|
placeholder={__("Evidence URL")}
|
||||||
|
{...register("url")}
|
||||||
|
error={formState.errors.url?.message}
|
||||||
|
help={__("This will create a .uri file with the URL inside")}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
disabled={formState.isSubmitting}
|
||||||
|
icon={formState.isSubmitting ? Spinner : undefined}
|
||||||
|
>
|
||||||
|
{__("Create")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
|
Breadcrumb,
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
IconArrowInbox,
|
IconArrowInbox,
|
||||||
IconFilter,
|
IconWarning,
|
||||||
|
Spinner,
|
||||||
|
useDialogRef,
|
||||||
|
useToast,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
evidence: {
|
evidence: {
|
||||||
@@ -18,27 +23,86 @@ type Props = {
|
|||||||
|
|
||||||
export function EvidencePreviewDialog({ evidence, onClose }: Props) {
|
export function EvidencePreviewDialog({ evidence, onClose }: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const ref = useDialogRef();
|
||||||
return (
|
return (
|
||||||
<Dialog defaultOpen title={evidence.filename} onClose={onClose}>
|
<Dialog
|
||||||
|
ref={ref}
|
||||||
|
defaultOpen
|
||||||
|
title={
|
||||||
|
<Breadcrumb
|
||||||
|
items={[{ label: __("Evidences") }, { label: evidence.filename }]}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClose={onClose}
|
||||||
|
>
|
||||||
<DialogContent padded>
|
<DialogContent padded>
|
||||||
<p>
|
<EvidencePreviewContent
|
||||||
{__(
|
evidence={evidence}
|
||||||
"Preview of the evidence file. You can view or download the file from here."
|
onClose={() => ref.current?.close()}
|
||||||
)}
|
/>
|
||||||
</p>
|
|
||||||
<EvidencePreviewContent evidence={evidence} />
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EvidencePreviewContent({ evidence }: Pick<Props, "evidence">) {
|
const fetchUrlFromUriFile = async (
|
||||||
|
fileUrl: string,
|
||||||
|
options?: { signal?: AbortSignal }
|
||||||
|
): Promise<string> => {
|
||||||
|
const response = await fetch(fileUrl, options);
|
||||||
|
const text = await response.text();
|
||||||
|
// URI files typically have the URL on the first line
|
||||||
|
const firstLine = text.trim().split("\n")[0];
|
||||||
|
if (!firstLine) {
|
||||||
|
throw new Error("No URL found in URI file");
|
||||||
|
}
|
||||||
|
return firstLine;
|
||||||
|
};
|
||||||
|
|
||||||
|
function EvidencePreviewContent({ evidence, onClose }: Props) {
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
|
const { toast } = useToast();
|
||||||
|
const isUriFile =
|
||||||
|
evidence.mimeType === "text/uri-list" || evidence.mimeType === "text/uri";
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isUriFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const abortController = new AbortController();
|
||||||
|
fetchUrlFromUriFile(evidence.fileUrl ?? "", {
|
||||||
|
signal: abortController.signal,
|
||||||
|
})
|
||||||
|
.then((url) => {
|
||||||
|
window.open(url, "_blank");
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
if (e.name === "AbortError") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toast({
|
||||||
|
title: __("Error"),
|
||||||
|
description: e.message ?? __("Failed to extract URL from URI file"),
|
||||||
|
variant: "error",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(onClose);
|
||||||
|
return () => {
|
||||||
|
abortController.abort();
|
||||||
|
};
|
||||||
|
}, [evidence.fileUrl, isUriFile]);
|
||||||
|
|
||||||
if (!evidence.fileUrl) {
|
if (!evidence.fileUrl) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isUriFile) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-2 justify-center">
|
||||||
|
<Spinner size={20} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (evidence.mimeType.startsWith("image/")) {
|
if (evidence.mimeType.startsWith("image/")) {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
@@ -60,10 +124,10 @@ function EvidencePreviewContent({ evidence }: Pick<Props, "evidence">) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center gap-4 justify-center">
|
<div className="flex flex-col items-center gap-2 justify-center">
|
||||||
<IconFilter size={20} />
|
<IconWarning size={20} />
|
||||||
<p className="text-secondary text-center">
|
<p className="text-txt-secondary text-center">
|
||||||
{__("Preview not availeeable for this file type") +
|
{__("Preview not available for this file type") +
|
||||||
" " +
|
" " +
|
||||||
evidence.mimeType}
|
evidence.mimeType}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<dd129ccf57ef541ce24672d4b55d708f>>
|
* @generated SignedSource<<4e4aeb5c756375a47e6a260c6ff9e259>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -14,11 +14,11 @@ export type UploadMeasureEvidenceInput = {
|
|||||||
file: any;
|
file: any;
|
||||||
measureId: string;
|
measureId: string;
|
||||||
};
|
};
|
||||||
export type MeasureEvidencesTabUploadMutation$variables = {
|
export type CreateEvidenceDialogUploadMutation$variables = {
|
||||||
connections: ReadonlyArray<string>;
|
connections: ReadonlyArray<string>;
|
||||||
input: UploadMeasureEvidenceInput;
|
input: UploadMeasureEvidenceInput;
|
||||||
};
|
};
|
||||||
export type MeasureEvidencesTabUploadMutation$data = {
|
export type CreateEvidenceDialogUploadMutation$data = {
|
||||||
readonly uploadMeasureEvidence: {
|
readonly uploadMeasureEvidence: {
|
||||||
readonly evidenceEdge: {
|
readonly evidenceEdge: {
|
||||||
readonly node: {
|
readonly node: {
|
||||||
@@ -28,9 +28,9 @@ export type MeasureEvidencesTabUploadMutation$data = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type MeasureEvidencesTabUploadMutation = {
|
export type CreateEvidenceDialogUploadMutation = {
|
||||||
response: MeasureEvidencesTabUploadMutation$data;
|
response: CreateEvidenceDialogUploadMutation$data;
|
||||||
variables: MeasureEvidencesTabUploadMutation$variables;
|
variables: CreateEvidenceDialogUploadMutation$variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
const node: ConcreteRequest = (function(){
|
||||||
@@ -66,7 +66,7 @@ return {
|
|||||||
],
|
],
|
||||||
"kind": "Fragment",
|
"kind": "Fragment",
|
||||||
"metadata": null,
|
"metadata": null,
|
||||||
"name": "MeasureEvidencesTabUploadMutation",
|
"name": "CreateEvidenceDialogUploadMutation",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
@@ -118,7 +118,7 @@ return {
|
|||||||
(v0/*: any*/)
|
(v0/*: any*/)
|
||||||
],
|
],
|
||||||
"kind": "Operation",
|
"kind": "Operation",
|
||||||
"name": "MeasureEvidencesTabUploadMutation",
|
"name": "CreateEvidenceDialogUploadMutation",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
@@ -215,16 +215,16 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "facbff238f3ee10c220a5c80883836da",
|
"cacheID": "7070386f52341969556753a27d42e226",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "MeasureEvidencesTabUploadMutation",
|
"name": "CreateEvidenceDialogUploadMutation",
|
||||||
"operationKind": "mutation",
|
"operationKind": "mutation",
|
||||||
"text": "mutation MeasureEvidencesTabUploadMutation(\n $input: UploadMeasureEvidenceInput!\n) {\n uploadMeasureEvidence(input: $input) {\n evidenceEdge {\n node {\n id\n ...MeasureEvidencesTabFragment_evidence\n }\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment_evidence on Evidence {\n id\n filename\n size\n type\n createdAt\n fileUrl\n mimeType\n}\n"
|
"text": "mutation CreateEvidenceDialogUploadMutation(\n $input: UploadMeasureEvidenceInput!\n) {\n uploadMeasureEvidence(input: $input) {\n evidenceEdge {\n node {\n id\n ...MeasureEvidencesTabFragment_evidence\n }\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment_evidence on Evidence {\n id\n filename\n size\n type\n createdAt\n fileUrl\n mimeType\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "37283c7d6f25ed8c768ba9e0f93ebafb";
|
(node as any).hash = "719cce21cdff705c55dda3de31997a53";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
@@ -5,23 +5,26 @@ import { usePageTitle } from "@probo/hooks";
|
|||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
Dropzone,
|
|
||||||
IconArrowInbox,
|
IconArrowInbox,
|
||||||
|
IconPlusLarge,
|
||||||
IconTrashCan,
|
IconTrashCan,
|
||||||
Tbody,
|
Tbody,
|
||||||
Td,
|
Td,
|
||||||
Th,
|
Th,
|
||||||
Thead,
|
Thead,
|
||||||
Tr,
|
Tr,
|
||||||
|
TrButton,
|
||||||
useConfirm,
|
useConfirm,
|
||||||
|
useDialogRef,
|
||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import { useFragment, useMutation, useRefetchableFragment } from "react-relay";
|
import { useFragment, useMutation, usePaginationFragment } from "react-relay";
|
||||||
import { SortableTable } from "/components/SortableTable";
|
import { SortableTable } from "/components/SortableTable";
|
||||||
import type { MeasureEvidencesTabFragment_evidence$key } from "./__generated__/MeasureEvidencesTabFragment_evidence.graphql";
|
import type { MeasureEvidencesTabFragment_evidence$key } from "./__generated__/MeasureEvidencesTabFragment_evidence.graphql";
|
||||||
import { fileSize, fileType, promisifyMutation, sprintf } from "@probo/helpers";
|
import { fileSize, fileType, promisifyMutation, sprintf } from "@probo/helpers";
|
||||||
import { EvidencePreviewDialog } from "../dialog/EvidencePreviewDialog";
|
import { EvidencePreviewDialog } from "../dialog/EvidencePreviewDialog";
|
||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import { CreateEvidenceDialog } from "../dialog/CreateEvidenceDialog";
|
||||||
|
|
||||||
export const evidencesFragment = graphql`
|
export const evidencesFragment = graphql`
|
||||||
fragment MeasureEvidencesTabFragment on Measure
|
fragment MeasureEvidencesTabFragment on Measure
|
||||||
@@ -67,22 +70,6 @@ export const evidenceFragment = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const uploadEvidenceMutation = graphql`
|
|
||||||
mutation MeasureEvidencesTabUploadMutation(
|
|
||||||
$input: UploadMeasureEvidenceInput!
|
|
||||||
$connections: [ID!]!
|
|
||||||
) {
|
|
||||||
uploadMeasureEvidence(input: $input) {
|
|
||||||
evidenceEdge @appendEdge(connections: $connections) {
|
|
||||||
node {
|
|
||||||
id
|
|
||||||
...MeasureEvidencesTabFragment_evidence
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const deleteEvidenceMutation = graphql`
|
const deleteEvidenceMutation = graphql`
|
||||||
mutation MeasureEvidencesTabDeleteMutation(
|
mutation MeasureEvidencesTabDeleteMutation(
|
||||||
$input: DeleteEvidenceInput!
|
$input: DeleteEvidenceInput!
|
||||||
@@ -99,58 +86,21 @@ export default function MeasureEvidencesTab() {
|
|||||||
measure: MeasureEvidencesTabFragment$key & { id: string; name: string };
|
measure: MeasureEvidencesTabFragment$key & { id: string; name: string };
|
||||||
}>();
|
}>();
|
||||||
const { evidenceId } = useParams<{ evidenceId: string }>();
|
const { evidenceId } = useParams<{ evidenceId: string }>();
|
||||||
const [data, refetch] = useRefetchableFragment(evidencesFragment, measure);
|
const pagination = usePaginationFragment(evidencesFragment, measure);
|
||||||
const connectionId = data.evidences.__id;
|
const connectionId = pagination.data.evidences.__id;
|
||||||
const evidences = data.evidences?.edges?.map((edge) => edge.node) ?? [];
|
const evidences =
|
||||||
|
pagination.data.evidences?.edges?.map((edge) => edge.node) ?? [];
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const [mutate, isUpdating] = useMutation(uploadEvidenceMutation);
|
|
||||||
const evidence = evidences.find((e) => e.id === evidenceId);
|
const evidence = evidences.find((e) => e.id === evidenceId);
|
||||||
const organizationId = useOrganizationId();
|
const organizationId = useOrganizationId();
|
||||||
|
const dialogRef = useDialogRef();
|
||||||
|
|
||||||
usePageTitle(measure.name + " - " + __("Evidences"));
|
usePageTitle(measure.name + " - " + __("Evidences"));
|
||||||
|
|
||||||
const handleDrop = (files: File[]) => {
|
|
||||||
for (const file of files) {
|
|
||||||
mutate({
|
|
||||||
variables: {
|
|
||||||
connections: [connectionId],
|
|
||||||
input: {
|
|
||||||
measureId: measure.id,
|
|
||||||
file: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
uploadables: {
|
|
||||||
"input.file": file,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<Dropzone
|
<SortableTable {...pagination}>
|
||||||
description={__(
|
|
||||||
"Only PDF, DOCX, XLSX, PPTX, JPG, PNG, WEBP files up to 10MB are allowed",
|
|
||||||
)}
|
|
||||||
isUploading={isUpdating}
|
|
||||||
onDrop={handleDrop}
|
|
||||||
accept={{
|
|
||||||
"application/pdf": [".pdf"],
|
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
|
||||||
[".docx"],
|
|
||||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [
|
|
||||||
".xlsx",
|
|
||||||
],
|
|
||||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation":
|
|
||||||
[".pptx"],
|
|
||||||
"image/jpeg": [".jpg", ".jpeg"],
|
|
||||||
"image/png": [".png"],
|
|
||||||
"image/webp": [".webp"],
|
|
||||||
}}
|
|
||||||
maxSize={10}
|
|
||||||
/>
|
|
||||||
<SortableTable refetch={refetch}>
|
|
||||||
<Thead>
|
<Thead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>{__("Evidence name")}</Th>
|
<Th>{__("Evidence name")}</Th>
|
||||||
@@ -170,6 +120,13 @@ export default function MeasureEvidencesTab() {
|
|||||||
connectionId={connectionId}
|
connectionId={connectionId}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
<TrButton
|
||||||
|
colspan={5}
|
||||||
|
onClick={() => dialogRef.current?.open()}
|
||||||
|
icon={IconPlusLarge}
|
||||||
|
>
|
||||||
|
{__("Add evidence")}
|
||||||
|
</TrButton>
|
||||||
</Tbody>
|
</Tbody>
|
||||||
</SortableTable>
|
</SortableTable>
|
||||||
{evidence && (
|
{evidence && (
|
||||||
@@ -183,6 +140,11 @@ export default function MeasureEvidencesTab() {
|
|||||||
evidence={evidence}
|
evidence={evidence}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<CreateEvidenceDialog
|
||||||
|
ref={dialogRef}
|
||||||
|
measureId={measure.id}
|
||||||
|
connectionId={connectionId}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function TabItem({
|
|||||||
asChild,
|
asChild,
|
||||||
active,
|
active,
|
||||||
...props
|
...props
|
||||||
}: AsChildProps<{ active?: boolean }>) {
|
}: AsChildProps<{ active?: boolean; onClick?: () => void }>) {
|
||||||
const Component = asChild ? Slot : "div";
|
const Component = asChild ? Slot : "div";
|
||||||
return <Component {...props} className={cls.item({ active })} />;
|
return <Component {...props} className={cls.item({ active })} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type Props =
|
|||||||
| BaseProps<never, ComponentProps<typeof Input>>
|
| BaseProps<never, ComponentProps<typeof Input>>
|
||||||
| BaseProps<"text", ComponentProps<typeof Input>>
|
| BaseProps<"text", ComponentProps<typeof Input>>
|
||||||
| BaseProps<"email", ComponentProps<typeof Input>>
|
| BaseProps<"email", ComponentProps<typeof Input>>
|
||||||
|
| BaseProps<"url", ComponentProps<typeof Input>>
|
||||||
| BaseProps<"password", ComponentProps<typeof Input>>
|
| BaseProps<"password", ComponentProps<typeof Input>>
|
||||||
| BaseProps<"textarea", ComponentProps<typeof Textarea>>
|
| BaseProps<"textarea", ComponentProps<typeof Textarea>>
|
||||||
| BaseProps<"number", ComponentProps<typeof Input>>
|
| BaseProps<"number", ComponentProps<typeof Input>>
|
||||||
|
|||||||
Reference in New Issue
Block a user