@deenruv/reviews-plugin 1.0.17-dev.30 → 1.0.17-dev.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugin-ui/components/ReviewCustomer.js +4 -2
- package/dist/plugin-ui/components/ReviewOrder.d.ts +2 -0
- package/dist/plugin-ui/components/ReviewOrder.js +15 -3
- package/dist/plugin-ui/index.js +2 -1
- package/dist/plugin-ui/pages/Review.js +5 -1
- package/dist/plugin-ui/pages/Reviews.js +2 -1
- package/dist/plugin-ui/translations.spec.d.ts +1 -0
- package/dist/plugin-ui/translations.spec.js +44 -0
- package/package.json +5 -5
|
@@ -6,7 +6,7 @@ import { REVIEWS_ROUTES } from "../index.js";
|
|
|
6
6
|
import { Check, ReplaceAllIcon, ReplaceIcon } from "lucide-react";
|
|
7
7
|
import { UniversalSelectDialog } from "./UniversalSelectDialog";
|
|
8
8
|
export const ReviewCustomer = () => {
|
|
9
|
-
const { t } = useTranslation(TRANSLATION_NAMESPACE);
|
|
9
|
+
const { t, i18n } = useTranslation(TRANSLATION_NAMESPACE);
|
|
10
10
|
const [fetch] = useLazyQuery(ListReviewQuery);
|
|
11
11
|
const [changeReviewState] = useMutation(ChangeReviewStateMutation);
|
|
12
12
|
const [changeReviewsState] = useMutation(ChangeReviewsStateMutation);
|
|
@@ -136,7 +136,9 @@ export const ReviewCustomer = () => {
|
|
|
136
136
|
const responseCreatedAt = row.original.responseCreatedAt;
|
|
137
137
|
return (React.createElement("div", null, responseCreatedAt
|
|
138
138
|
? t("list.responseCreatedAtValue", {
|
|
139
|
-
date: formatDate(responseCreatedAt
|
|
139
|
+
date: formatDate(responseCreatedAt, {
|
|
140
|
+
locale: i18n.resolvedLanguage ?? i18n.language,
|
|
141
|
+
}),
|
|
140
142
|
})
|
|
141
143
|
: t("list.noResponse")));
|
|
142
144
|
},
|
|
@@ -23,8 +23,18 @@ const getStatusColor = (status) => {
|
|
|
23
23
|
return "bg-gray-100 text-gray-800 border-gray-200";
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
|
+
export const getReviewStateLabel = (state, translate) => {
|
|
27
|
+
switch (state) {
|
|
28
|
+
case "PENDING" /* ReviewState.PENDING */:
|
|
29
|
+
return translate("state.pending");
|
|
30
|
+
case "ACCEPTED" /* ReviewState.ACCEPTED */:
|
|
31
|
+
return translate("state.accepted");
|
|
32
|
+
case "DECLINED" /* ReviewState.DECLINED */:
|
|
33
|
+
return translate("state.declined");
|
|
34
|
+
}
|
|
35
|
+
};
|
|
26
36
|
export const ReviewOrder = () => {
|
|
27
|
-
const { t } = useTranslation(TRANSLATION_NAMESPACE);
|
|
37
|
+
const { t, i18n } = useTranslation(TRANSLATION_NAMESPACE);
|
|
28
38
|
const { order } = useOrder();
|
|
29
39
|
const [getReviewForOrder, { data }] = useLazyQuery(GetReviewForOrderQuery);
|
|
30
40
|
useEffect(() => {
|
|
@@ -41,7 +51,7 @@ export const ReviewOrder = () => {
|
|
|
41
51
|
React.createElement(CardTitle, { className: "flex items-center gap-2" },
|
|
42
52
|
React.createElement(MessageSquare, { className: "h-5 w-5 text-orange-500" }),
|
|
43
53
|
t("reviewOrder.titleWithId", { orderId: order.id }))),
|
|
44
|
-
React.createElement(Badge, { className: getStatusColor(data.getReviewForOrder.state) }, data.getReviewForOrder.state))),
|
|
54
|
+
React.createElement(Badge, { className: getStatusColor(data.getReviewForOrder.state) }, getReviewStateLabel(data.getReviewForOrder.state, t)))),
|
|
45
55
|
React.createElement(CardContent, { className: "space-y-4 p-0" },
|
|
46
56
|
React.createElement("div", { className: "space-y-4" },
|
|
47
57
|
React.createElement("div", { className: "flex items-center justify-between" },
|
|
@@ -50,5 +60,7 @@ export const ReviewOrder = () => {
|
|
|
50
60
|
React.createElement("p", { className: "text-sm leading-relaxed text-foreground" }, data.getReviewForOrder.body)),
|
|
51
61
|
React.createElement("div", { className: "flex items-center gap-4 pt-2 border-t text-xs text-muted-foreground" }, data.getReviewForOrder.createdAt && (React.createElement("div", { className: "flex items-center gap-1" },
|
|
52
62
|
React.createElement(Calendar, { className: "h-3 w-3" }),
|
|
53
|
-
formatDate(data.getReviewForOrder.createdAt
|
|
63
|
+
formatDate(data.getReviewForOrder.createdAt, {
|
|
64
|
+
locale: i18n.resolvedLanguage ?? i18n.language,
|
|
65
|
+
}))))))));
|
|
54
66
|
};
|
package/dist/plugin-ui/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { createDeenruvUIPlugin, DEENRUV_UI_VERSION } from "@deenruv/react-ui-devkit";
|
|
2
|
+
import { createDeenruvUIPlugin, DEENRUV_UI_VERSION, } from "@deenruv/react-ui-devkit";
|
|
3
3
|
import { Reviews } from "./pages/Reviews";
|
|
4
4
|
import { ListIcon } from "lucide-react";
|
|
5
5
|
import { Review } from "./pages/Review";
|
|
@@ -46,6 +46,7 @@ export const ReviewsUIPlugin = createDeenruvUIPlugin({
|
|
|
46
46
|
id: "customers-detail-view",
|
|
47
47
|
name: "reviews",
|
|
48
48
|
label: "Reviews",
|
|
49
|
+
labelId: "nav.reviews",
|
|
49
50
|
component: React.createElement(ReviewCustomer, null),
|
|
50
51
|
hideSidebar: true,
|
|
51
52
|
},
|
|
@@ -9,7 +9,7 @@ import { TRANSLATION_NAMESPACE } from "../constants";
|
|
|
9
9
|
import { toast } from "sonner";
|
|
10
10
|
import { ReviewStateChange } from "../components/ReviewStateChange";
|
|
11
11
|
export const Review = () => {
|
|
12
|
-
const { t } = useTranslation(TRANSLATION_NAMESPACE);
|
|
12
|
+
const { t, i18n } = useTranslation(TRANSLATION_NAMESPACE);
|
|
13
13
|
const { id } = useParams();
|
|
14
14
|
const { data: config } = useQuery(GetReviewsConfigQuery);
|
|
15
15
|
const [get] = useLazyQuery(GetReviewQuery);
|
|
@@ -178,6 +178,7 @@ export const Review = () => {
|
|
|
178
178
|
React.createElement("div", { className: "flex items-center gap-2 text-sm text-gray-600" },
|
|
179
179
|
React.createElement(Calendar, { className: "w-4 h-4" }),
|
|
180
180
|
formatDate(review.createdAt, {
|
|
181
|
+
locale: i18n.language,
|
|
181
182
|
dateStyle: "medium",
|
|
182
183
|
timeStyle: "short",
|
|
183
184
|
})),
|
|
@@ -198,6 +199,7 @@ export const Review = () => {
|
|
|
198
199
|
React.createElement("div", { className: "flex items-center gap-2 mb-2" },
|
|
199
200
|
React.createElement(Label, { className: "text-base font-semibold text-secondary" }, t("detail.businessResponse")),
|
|
200
201
|
React.createElement(Badge, { variant: "outline", className: "text-xs text-secondary" }, formatDate(review.responseCreatedAt, {
|
|
202
|
+
locale: i18n.language,
|
|
201
203
|
dateStyle: "medium",
|
|
202
204
|
timeStyle: "short",
|
|
203
205
|
}))),
|
|
@@ -209,6 +211,7 @@ export const Review = () => {
|
|
|
209
211
|
":"),
|
|
210
212
|
" ",
|
|
211
213
|
formatDate(review.createdAt, {
|
|
214
|
+
locale: i18n.language,
|
|
212
215
|
dateStyle: "medium",
|
|
213
216
|
timeStyle: "short",
|
|
214
217
|
})),
|
|
@@ -218,6 +221,7 @@ export const Review = () => {
|
|
|
218
221
|
":"),
|
|
219
222
|
" ",
|
|
220
223
|
formatDate(review.updatedAt, {
|
|
224
|
+
locale: i18n.language,
|
|
221
225
|
dateStyle: "medium",
|
|
222
226
|
timeStyle: "short",
|
|
223
227
|
}))))) : (React.createElement("div", { className: "text-center py-8" },
|
|
@@ -7,7 +7,7 @@ import { Check, ReplaceIcon, ReplaceAllIcon } from "lucide-react";
|
|
|
7
7
|
import { UniversalSelectDialog } from "../components/UniversalSelectDialog.js";
|
|
8
8
|
import { ChangeReviewsStateMutation, ChangeReviewStateMutation, } from "../graphql/mutations.js";
|
|
9
9
|
export const Reviews = () => {
|
|
10
|
-
const { t } = useTranslation(TRANSLATION_NAMESPACE);
|
|
10
|
+
const { t, i18n } = useTranslation(TRANSLATION_NAMESPACE);
|
|
11
11
|
const [fetch] = useLazyQuery(ListReviewQuery);
|
|
12
12
|
const [changeReviewState] = useMutation(ChangeReviewStateMutation);
|
|
13
13
|
const [changeReviewsState] = useMutation(ChangeReviewsStateMutation);
|
|
@@ -142,6 +142,7 @@ export const Reviews = () => {
|
|
|
142
142
|
return (React.createElement("div", null, responseCreatedAt
|
|
143
143
|
? t("list.responseCreatedAtValue", {
|
|
144
144
|
date: formatDate(responseCreatedAt, {
|
|
145
|
+
locale: i18n.resolvedLanguage ?? i18n.language,
|
|
145
146
|
dateStyle: "medium",
|
|
146
147
|
timeStyle: "short",
|
|
147
148
|
}),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import en from "./locales/en/reviews.json";
|
|
3
|
+
import pl from "./locales/pl/reviews.json";
|
|
4
|
+
import { getReviewStateLabel } from "./components/ReviewOrder";
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { fileURLToPath, URL } from "node:url";
|
|
7
|
+
const leafKeys = (value, prefix = "") => Object.entries(value).flatMap(([key, child]) => {
|
|
8
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
9
|
+
return child && typeof child === "object"
|
|
10
|
+
? leafKeys(child, path)
|
|
11
|
+
: [path];
|
|
12
|
+
});
|
|
13
|
+
describe("Reviews admin translations", () => {
|
|
14
|
+
it("keeps English and Polish locale keys in parity", () => {
|
|
15
|
+
expect(leafKeys(en).sort()).toEqual(leafKeys(pl).sort());
|
|
16
|
+
});
|
|
17
|
+
it("localizes every backend review state without changing enum values", () => {
|
|
18
|
+
const translate = (key) => `translated:${key}`;
|
|
19
|
+
expect(getReviewStateLabel("PENDING" /* ReviewState.PENDING */, translate)).toBe("translated:state.pending");
|
|
20
|
+
expect(getReviewStateLabel("ACCEPTED" /* ReviewState.ACCEPTED */, translate)).toBe("translated:state.accepted");
|
|
21
|
+
expect(getReviewStateLabel("DECLINED" /* ReviewState.DECLINED */, translate)).toBe("translated:state.declined");
|
|
22
|
+
});
|
|
23
|
+
it("declares an SSR-safe customer tab translation key", async () => {
|
|
24
|
+
const { ReviewsUIPlugin } = await import("./index");
|
|
25
|
+
expect(ReviewsUIPlugin.tabs?.[0]).toMatchObject({
|
|
26
|
+
name: "reviews",
|
|
27
|
+
label: "Reviews",
|
|
28
|
+
labelId: "nav.reviews",
|
|
29
|
+
});
|
|
30
|
+
expect(globalThis.window).toBeUndefined();
|
|
31
|
+
});
|
|
32
|
+
it("formats every visible review date in the active UI language", () => {
|
|
33
|
+
const sources = [
|
|
34
|
+
"./pages/Review.tsx",
|
|
35
|
+
"./pages/Reviews.tsx",
|
|
36
|
+
"./components/ReviewCustomer.tsx",
|
|
37
|
+
"./components/ReviewOrder.tsx",
|
|
38
|
+
].map((path) => readFileSync(fileURLToPath(new URL(path, import.meta.url)), "utf8"));
|
|
39
|
+
expect(sources.reduce((count, source) => count + (source.match(/formatDate\(/g)?.length ?? 0), 0)).toBe(7);
|
|
40
|
+
for (const source of sources) {
|
|
41
|
+
expect(source.match(/locale: i18n\.(?:resolvedLanguage|language)/g)).toHaveLength(source.match(/formatDate\(/g)?.length ?? 0);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deenruv/reviews-plugin",
|
|
3
|
-
"version": "1.0.17-dev.
|
|
3
|
+
"version": "1.0.17-dev.32",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"react-router": "^7.13.0",
|
|
31
31
|
"sonner": "^1.4.41",
|
|
32
32
|
"recharts": "^2.12.7",
|
|
33
|
-
"@deenruv/admin-types": "^1.0.17-dev.
|
|
34
|
-
"@deenruv/
|
|
35
|
-
"@deenruv/
|
|
33
|
+
"@deenruv/admin-types": "^1.0.17-dev.32",
|
|
34
|
+
"@deenruv/common": "^1.0.17-dev.32",
|
|
35
|
+
"@deenruv/react-ui-devkit": "^1.0.17-dev.32"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@graphql-typed-document-node/core": "3.2.0",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"rimraf": "^5.0.10",
|
|
45
45
|
"tslib": "^2.6.2",
|
|
46
46
|
"typescript": "5.3.3",
|
|
47
|
-
"@deenruv/core": "^1.0.17-dev.
|
|
47
|
+
"@deenruv/core": "^1.0.17-dev.32"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@deenruv/core": "^1.0.0 || ^1.0.17-dev.0"
|