diff --git a/.changeset/mean-points-glow.md b/.changeset/mean-points-glow.md new file mode 100644 index 00000000000..e8d60633972 --- /dev/null +++ b/.changeset/mean-points-glow.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +You can now edit note in order details. Notes in order details now show id of note, id of related note and type of note "added" or "updated" diff --git a/.changeset/new-monkeys-talk.md b/.changeset/new-monkeys-talk.md new file mode 100644 index 00000000000..eac6d6a20df --- /dev/null +++ b/.changeset/new-monkeys-talk.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +You can now open datagrid list item in new tab using cmd/ctrl button diff --git a/.changeset/selfish-swans-turn.md b/.changeset/selfish-swans-turn.md new file mode 100644 index 00000000000..b2c0f2027ec --- /dev/null +++ b/.changeset/selfish-swans-turn.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +After creating a new collection, you should see a list of assigned channels diff --git a/.changeset/violet-doors-bathe.md b/.changeset/violet-doors-bathe.md new file mode 100644 index 00000000000..0ee192c480a --- /dev/null +++ b/.changeset/violet-doors-bathe.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Activates list items on the welcome page no longer implies that they are clickable \ No newline at end of file diff --git a/.changeset/wild-poems-approve.md b/.changeset/wild-poems-approve.md new file mode 100644 index 00000000000..9eb8f631c5b --- /dev/null +++ b/.changeset/wild-poems-approve.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Editor.js no more cause error during saving diff --git a/locale/defaultMessages.json b/locale/defaultMessages.json index 4c55a0a5dad..1f9035102e6 100644 --- a/locale/defaultMessages.json +++ b/locale/defaultMessages.json @@ -309,6 +309,9 @@ "context": "channels alphabetically title", "string": "Channels from A to Z" }, + "/n+NRO": { + "string": "Note id" + }, "/nZy6A": { "context": "page subtitle", "string": "Issue refund" @@ -1711,6 +1714,9 @@ "context": "order number label", "string": "Order number" }, + "9Th87u": { + "string": "Note successfully updated" + }, "9Tl/bT": { "context": "export items as spreadsheet", "string": "Spreadsheet for Excel, Numbers etc." @@ -6496,6 +6502,9 @@ "context": "edit tracking button", "string": "Edit tracking" }, + "dVSBW6": { + "string": "Related note id" + }, "dVk241": { "string": "product configurations" }, @@ -6699,6 +6708,9 @@ "context": "gift card history message", "string": "Gift card expiry date was updated" }, + "fM7xZh": { + "string": "updated" + }, "fNFEkh": { "string": "No of Rows:" }, @@ -7057,6 +7069,9 @@ "context": "docs link label", "string": "Learn more..." }, + "hniz8Z": { + "string": "here" + }, "ho75Lr": { "context": "status label deactivated", "string": "Deactivated" @@ -8313,6 +8328,9 @@ "context": "header", "string": "Add Value to Authorization Field" }, + "qD1kvF": { + "string": "The timeline below shows the history of all events related to this order. Each entry represents a single event along with its content or readable description. For more information regarding order events, you can find {link}." + }, "qDfaDI": { "string": "No app or plugin is configured to handle requested transaction action" }, @@ -9367,6 +9385,9 @@ "context": "gift card settings header", "string": "Gift Cards Settings" }, + "xJEaxW": { + "string": "added" + }, "xJQX5t": { "string": "No staff members found" }, diff --git a/src/collections/views/CollectionCreate.tsx b/src/collections/views/CollectionCreate.tsx index 4cc8fc4dfe3..a79db066b10 100644 --- a/src/collections/views/CollectionCreate.tsx +++ b/src/collections/views/CollectionCreate.tsx @@ -63,28 +63,7 @@ export const CollectionCreate: React.FC = ({ params }) => { closeModal, openModal }, { formId: COLLECTION_CREATE_FORM_ID }, ); - const [createCollection, createCollectionOpts] = useCreateCollectionMutation({ - onCompleted: data => { - if (data.collectionCreate.errors.length === 0) { - notify({ - status: "success", - text: intl.formatMessage(commonMessages.savedChanges), - }); - navigate(collectionUrl(data.collectionCreate.collection.id)); - } else { - const backgroundImageError = data.collectionCreate.errors.find( - error => error.field === ("backgroundImage" as keyof CollectionCreateInput), - ); - - if (backgroundImageError) { - notify({ - status: "error", - text: intl.formatMessage(commonMessages.somethingWentWrong), - }); - } - } - }, - }); + const [createCollection, createCollectionOpts] = useCreateCollectionMutation({}); const handleCreate = async (formData: CollectionCreateData) => { const result = await createCollection({ variables: { @@ -103,7 +82,7 @@ export const CollectionCreate: React.FC = ({ params }) => const id = result.data?.collectionCreate.collection?.id || null; if (id) { - updateChannels({ + await updateChannels({ variables: { id, input: { @@ -118,6 +97,25 @@ export const CollectionCreate: React.FC = ({ params }) => }); } + if (result.data.collectionCreate.errors.length === 0) { + notify({ + status: "success", + text: intl.formatMessage(commonMessages.savedChanges), + }); + navigate(collectionUrl(id)); + } else { + const backgroundImageError = result.data.collectionCreate.errors.find( + error => error.field === ("backgroundImage" as keyof CollectionCreateInput), + ); + + if (backgroundImageError) { + notify({ + status: "error", + text: intl.formatMessage(commonMessages.somethingWentWrong), + }); + } + } + return { id, errors: getMutationErrors(result) }; }; const handleSubmit = createMetadataCreateHandler( diff --git a/src/components/Datagrid/Datagrid.tsx b/src/components/Datagrid/Datagrid.tsx index cad7e807ccb..d4f4659b25a 100644 --- a/src/components/Datagrid/Datagrid.tsx +++ b/src/components/Datagrid/Datagrid.tsx @@ -533,7 +533,13 @@ export const Datagrid: React.FC = ({ e.preventDefault(); if (e.currentTarget.dataset.reactRouterPath) { - navigate(e.currentTarget.dataset.reactRouterPath, navigatorOpts); + const url = e.currentTarget.dataset.reactRouterPath; + + if (e.metaKey || e.ctrlKey) { + window.open(url, "_blank"); + } else { + navigate(url, navigatorOpts); + } } }} /> diff --git a/src/components/RichTextEditor/ReactEditorJS.tsx b/src/components/RichTextEditor/ReactEditorJS.tsx index 9e35a63834a..0ae96d99807 100644 --- a/src/components/RichTextEditor/ReactEditorJS.tsx +++ b/src/components/RichTextEditor/ReactEditorJS.tsx @@ -32,6 +32,8 @@ class ClientEditorCore implements EditorCore { } public async save() { + await this._editorJS.isReady; + return this._editorJS.save(); } diff --git a/src/components/Timeline/TimelineNote.test.tsx b/src/components/Timeline/TimelineNote.test.tsx index 7c712bb56f9..8c088a55b85 100644 --- a/src/components/Timeline/TimelineNote.test.tsx +++ b/src/components/Timeline/TimelineNote.test.tsx @@ -1,6 +1,7 @@ import { OrderEventFragment } from "@dashboard/graphql/types.generated"; import Wrapper from "@test/wrapper"; -import { render, screen } from "@testing-library/react"; +import { act, render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import React from "react"; import TimelineNote from "./TimelineNote"; @@ -142,4 +143,85 @@ describe("TimelineNote", () => { expect(initials).toBeNull(); expect(avatar).toBeInTheDocument(); }); + + it("renders note id and refer id", () => { + // Arrange + const noteId = "T3JkZXJFdmVudDozNDM3"; + const noteRelatedId = "T3JkZXJFdmVudDozNDQx"; + const mockedUser = { + avatar: null, + id: "1", + email: "test@test.com", + firstName: "Test", + lastName: "User", + __typename: "User", + } satisfies OrderEventFragment["user"]; + + // Act + render( + , + { wrapper: Wrapper }, + ); + + // Assert + expect(screen.getByText("Test User")).toBeInTheDocument(); + expect(screen.getByText("Note")).toBeInTheDocument(); + expect(screen.getByText("TU")).toBeInTheDocument(); + expect(screen.getByText("a few seconds ago")).toBeInTheDocument(); + expect(screen.getByText(`Note id: ${noteId}`)).toBeInTheDocument(); + expect(screen.getByText(new RegExp(noteRelatedId))).toBeInTheDocument(); + }); + + it("should edit note", async () => { + // Arrange + const noteId = "T3JkZXJFdmVudDozNDM3"; + const noteRelatedId = "T3JkZXJFdmVudDozNDQx"; + const onNoteUpdate = jest.fn(); + const onNoteUpdateLoading = false; + const mockedUser = { + avatar: null, + id: "1", + email: "test@test.com", + firstName: "Test", + lastName: "User", + __typename: "User", + } satisfies OrderEventFragment["user"]; + + render( + , + { wrapper: Wrapper }, + ); + + // Act + await act(async () => { + await userEvent.click(screen.getByTestId("edit-note")); + }); + + await act(async () => { + await userEvent.clear(screen.getByRole("textbox")); + await userEvent.type(screen.getByRole("textbox"), "New note"); + await userEvent.click(screen.getByRole("button", { name: /save/i })); + }); + + // Assert + expect(onNoteUpdate).toHaveBeenCalledWith(noteId, "New note"); + }); }); diff --git a/src/components/Timeline/TimelineNote.tsx b/src/components/Timeline/TimelineNote.tsx index 2fc6eb760e2..7beac14dc77 100644 --- a/src/components/Timeline/TimelineNote.tsx +++ b/src/components/Timeline/TimelineNote.tsx @@ -1,35 +1,13 @@ import { GiftCardEventsQuery, OrderEventFragment } from "@dashboard/graphql"; import { getUserInitials, getUserName } from "@dashboard/misc"; -import { makeStyles } from "@saleor/macaw-ui"; -import { Text } from "@saleor/macaw-ui-next"; -import React from "react"; +import { Box, Button, EditIcon, Text } from "@saleor/macaw-ui-next"; +import React, { useState } from "react"; +import { FormattedMessage } from "react-intl"; import { DashboardCard } from "../Card"; import { DateTime } from "../Date"; import { UserAvatar } from "../UserAvatar"; - -const useStyles = makeStyles( - theme => ({ - avatar: { - left: -40, - position: "absolute", - top: 0, - }, - root: { - position: "relative", - }, - title: { - "& p": { - fontSize: "14px", - }, - alignItems: "center", - display: "flex", - justifyContent: "space-between", - marginBottom: theme.spacing(), - }, - }), - { name: "TimelineNote" }, -); +import { TimelineNoteEdit } from "./TimelineNoteEdit"; type TimelineAppType = | NonNullable["events"][0]["app"] @@ -41,6 +19,10 @@ interface TimelineNoteProps { user: OrderEventFragment["user"]; app: TimelineAppType; hasPlainDate?: boolean; + id?: string; + relatedId?: string; + onNoteUpdate?: (id: string, message: string) => Promise; + onNoteUpdateLoading?: boolean; } interface NoteMessageProps { @@ -66,7 +48,7 @@ const TimelineAvatar = ({ }: { user: OrderEventFragment["user"]; app: TimelineAppType; - className: string; + className?: string; }) => { if (user) { return ( @@ -93,38 +75,84 @@ export const TimelineNote: React.FC = ({ message, hasPlainDate, app, + id, + relatedId, + onNoteUpdate, + onNoteUpdateLoading, }) => { - const classes = useStyles(); - const userDisplayName = getUserName(user, true) ?? app?.name; + const [showEdit, setShowEdit] = useState(false); return ( -
- -
+ + + + + {userDisplayName} - + + {relatedId ? ( + + ) : ( + + )} -
- - - - - -
+ + + {showEdit && id ? ( + setShowEdit(false)} + /> + ) : ( + <> + + + + {onNoteUpdate && ( +