@burdenoff/microfe-tours 2026.513.2 → 2026.516.1
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/generated/global-operations.d.ts +49 -0
- package/dist/generated/global-operations.d.ts.map +1 -1
- package/dist/generated/global-operations.js +84 -62
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/tours/hooks/useTourBuilder.d.ts.map +1 -1
- package/dist/tours/hooks/useTourBuilder.js +22 -20
- package/dist/tours/hooks/useTourBuilder.js.map +1 -1
- package/dist/tours/hooks/useTourLifecycle.d.ts.map +1 -1
- package/dist/tours/hooks/useTourLifecycle.js +31 -18
- package/dist/tours/hooks/useTourLifecycle.js.map +1 -1
- package/dist/tours/pages/TourBuilderPage.d.ts.map +1 -1
- package/dist/tours/pages/TourBuilderPage.js +556 -182
- package/dist/tours/pages/TourBuilderPage.js.map +1 -1
- package/dist/tours/pages/TourPreviewPage.js +3 -3
- package/dist/tours/pages/ToursListPage.d.ts.map +1 -1
- package/dist/tours/pages/ToursListPage.js +156 -155
- package/dist/tours/pages/ToursListPage.js.map +1 -1
- package/dist/tours/store/toursStore.d.ts +2 -0
- package/dist/tours/store/toursStore.d.ts.map +1 -1
- package/dist/tours/store/toursStore.js +2 -0
- package/dist/tours/store/toursStore.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,287 +1,288 @@
|
|
|
1
1
|
import { useToursContext as e } from "../providers/ToursProvider.js";
|
|
2
2
|
import { useDeleteTourMutation as t } from "../../generated/global-operations.js";
|
|
3
3
|
import { useToursList as n } from "../hooks/useToursList.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { useToursStore as r } from "../store/toursStore.js";
|
|
5
|
+
import { useTourLifecycle as i } from "../hooks/useTourLifecycle.js";
|
|
6
|
+
import { useToursPermissionAccess as a } from "../hooks/useToursPermissionAccess.js";
|
|
7
7
|
import { TOUR_PERMISSIONS as o } from "../../constants/permissions.js";
|
|
8
8
|
import { useToursI18n as s } from "../i18n.js";
|
|
9
9
|
import { isNative as c, nativeConfirm as l, nativeImpact as u, nativeNotify as d, nativeToast as f } from "../../utils/nativeBridge.js";
|
|
10
10
|
import { useEffect as p, useRef as m, useState as h } from "react";
|
|
11
11
|
import { PermissionGate as g } from "@burdenoff/fe-libs/shared/components";
|
|
12
|
-
import { Eye as _, Grid as
|
|
13
|
-
import { jsx as
|
|
14
|
-
import { useNavigate as
|
|
12
|
+
import { Eye as _, Grid as v, List as y, ListOrdered as ee, MoreVertical as te, Plus as ne, Search as b, Settings as x } from "lucide-react";
|
|
13
|
+
import { jsx as S, jsxs as C } from "react/jsx-runtime";
|
|
14
|
+
import { useNavigate as w } from "react-router-dom";
|
|
15
15
|
//#region src/tours/pages/ToursListPage.tsx
|
|
16
|
-
function
|
|
17
|
-
let { tr:
|
|
16
|
+
function T() {
|
|
17
|
+
let { tr: T } = s(), E = w(), { basePath: D = "/" } = e(), { viewMode: O, tourFilter: k, toursListRevision: A, setViewMode: j, updateTourFilter: M, notifyToursListChanged: N } = r(), { hasPermission: P, isLoading: F } = a(), I = P(o.TOUR_UPDATE), L = P(o.TOUR_STEP_UPDATE), R = P(o.TOUR_DELETE), z = P(o.TOUR_PUBLISH), B = P(o.TOUR_ARCHIVE), V = P(o.TOUR_DUPLICATE), { tours: H, loading: U, fetchTours: W } = n(), { publishTour: G, unpublishTour: K, archiveTour: re, duplicateTour: q } = i(), [J] = t(), [Y, X] = h(""), [ie, ae] = h("all"), [Z, Q] = h(null), $ = m(null);
|
|
18
18
|
p(() => {
|
|
19
|
-
|
|
19
|
+
F || W({ ...k });
|
|
20
20
|
}, [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
W,
|
|
22
|
+
k,
|
|
23
|
+
A,
|
|
24
|
+
F
|
|
24
25
|
]);
|
|
25
|
-
let
|
|
26
|
-
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
},
|
|
26
|
+
let oe = (e) => {
|
|
27
|
+
X(e), M({ search: e });
|
|
28
|
+
}, se = (e) => {
|
|
29
|
+
ae(e), M({ status: e });
|
|
30
|
+
}, ce = () => {
|
|
31
|
+
E(D.endsWith("/") ? `${D}new` : `${D}/new`);
|
|
32
|
+
}, le = (e) => {
|
|
32
33
|
switch (e) {
|
|
33
|
-
case "DRAFT": return
|
|
34
|
-
case "PUBLISHED": return
|
|
35
|
-
case "ARCHIVED": return
|
|
36
|
-
case "SCHEDULED": return
|
|
37
|
-
}
|
|
38
|
-
}, le = async (e) => {
|
|
39
|
-
Z(null), u("light");
|
|
40
|
-
try {
|
|
41
|
-
await H(e), d("success"), $();
|
|
42
|
-
} catch {
|
|
43
|
-
d("error"), c() ? f(w("toursList.errorPublish")) : alert(w("toursList.errorPublish"));
|
|
34
|
+
case "DRAFT": return T("toursList.statusDraft");
|
|
35
|
+
case "PUBLISHED": return T("toursList.statusPublished");
|
|
36
|
+
case "ARCHIVED": return T("toursList.statusArchived");
|
|
37
|
+
case "SCHEDULED": return T("toursList.statusScheduled");
|
|
44
38
|
}
|
|
45
39
|
}, ue = async (e) => {
|
|
46
|
-
|
|
40
|
+
Q(null), u("light");
|
|
47
41
|
try {
|
|
48
|
-
await
|
|
42
|
+
await G(e), d("success");
|
|
49
43
|
} catch {
|
|
50
|
-
d("error"), c() ? f(
|
|
44
|
+
d("error"), c() ? f(T("toursList.errorPublish")) : alert(T("toursList.errorPublish"));
|
|
51
45
|
}
|
|
52
46
|
}, de = async (e) => {
|
|
53
|
-
|
|
47
|
+
Q(null), u("light");
|
|
54
48
|
try {
|
|
55
|
-
await
|
|
49
|
+
await K(e), d("success");
|
|
56
50
|
} catch {
|
|
57
|
-
d("error"), c() ? f(
|
|
51
|
+
d("error"), c() ? f(T("toursList.errorUnpublish")) : alert(T("toursList.errorUnpublish"));
|
|
58
52
|
}
|
|
59
53
|
}, fe = async (e) => {
|
|
60
|
-
|
|
54
|
+
Q(null), u("light");
|
|
61
55
|
try {
|
|
62
|
-
await
|
|
56
|
+
await re(e), d("success");
|
|
63
57
|
} catch {
|
|
64
|
-
d("error"), c() ? f(
|
|
58
|
+
d("error"), c() ? f(T("toursList.errorArchive")) : alert(T("toursList.errorArchive"));
|
|
65
59
|
}
|
|
66
60
|
}, pe = async (e) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
61
|
+
Q(null), u("light");
|
|
62
|
+
try {
|
|
63
|
+
await q(e), d("success");
|
|
64
|
+
} catch {
|
|
65
|
+
d("error"), c() ? f(T("toursList.errorDuplicate")) : alert(T("toursList.errorDuplicate"));
|
|
66
|
+
}
|
|
67
|
+
}, me = async (e) => {
|
|
68
|
+
Q(null), u("medium");
|
|
69
|
+
let t = T("toursList.confirmDelete"), n = await l({
|
|
70
|
+
title: T("toursList.actionDelete"),
|
|
70
71
|
message: t,
|
|
71
|
-
okButtonTitle:
|
|
72
|
+
okButtonTitle: T("toursList.actionDelete")
|
|
72
73
|
});
|
|
73
74
|
if (n === null ? window.confirm(t) : n) try {
|
|
74
|
-
await
|
|
75
|
+
await J({ variables: { id: e } }), N(), d("success");
|
|
75
76
|
} catch {
|
|
76
|
-
d("error"), c() ? f(
|
|
77
|
+
d("error"), c() ? f(T("toursList.errorDelete")) : alert(T("toursList.errorDelete"));
|
|
77
78
|
}
|
|
78
79
|
};
|
|
79
80
|
return p(() => {
|
|
80
81
|
let e = (e) => {
|
|
81
|
-
|
|
82
|
+
$.current && !$.current.contains(e.target) && Q(null);
|
|
82
83
|
};
|
|
83
|
-
return
|
|
84
|
-
}, [
|
|
84
|
+
return Z && document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
|
|
85
|
+
}, [Z]), /* @__PURE__ */ C("div", {
|
|
85
86
|
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
86
87
|
children: [
|
|
87
|
-
/* @__PURE__ */
|
|
88
|
+
/* @__PURE__ */ C("div", {
|
|
88
89
|
className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3",
|
|
89
|
-
children: [/* @__PURE__ */
|
|
90
|
+
children: [/* @__PURE__ */ C("div", {
|
|
90
91
|
className: "flex-1 min-w-0",
|
|
91
|
-
children: [/* @__PURE__ */
|
|
92
|
+
children: [/* @__PURE__ */ S("h1", {
|
|
92
93
|
className: "text-xl sm:text-2xl font-bold text-foreground",
|
|
93
|
-
children:
|
|
94
|
-
}), /* @__PURE__ */
|
|
94
|
+
children: T("toursList.title")
|
|
95
|
+
}), /* @__PURE__ */ S("p", {
|
|
95
96
|
className: "text-sm sm:text-base text-muted-foreground",
|
|
96
|
-
children:
|
|
97
|
+
children: T("toursList.subtitle")
|
|
97
98
|
})]
|
|
98
|
-
}), /* @__PURE__ */
|
|
99
|
+
}), /* @__PURE__ */ S(g, {
|
|
99
100
|
permission: o.TOUR_CREATE,
|
|
100
|
-
children: /* @__PURE__ */
|
|
101
|
-
onClick:
|
|
101
|
+
children: /* @__PURE__ */ C("button", {
|
|
102
|
+
onClick: ce,
|
|
102
103
|
className: "w-full sm:w-auto px-4 py-2 min-h-[44px] bg-primary text-primary-foreground rounded-md hover:bg-primary/90 active:bg-primary/80 flex items-center justify-center gap-2",
|
|
103
|
-
children: [/* @__PURE__ */
|
|
104
|
+
children: [/* @__PURE__ */ S(ne, { size: 20 }), T("toursList.createTour")]
|
|
104
105
|
})
|
|
105
106
|
})]
|
|
106
107
|
}),
|
|
107
|
-
/* @__PURE__ */
|
|
108
|
+
/* @__PURE__ */ C("div", {
|
|
108
109
|
className: "flex flex-col sm:flex-row gap-3 sm:gap-4",
|
|
109
|
-
children: [/* @__PURE__ */
|
|
110
|
+
children: [/* @__PURE__ */ C("div", {
|
|
110
111
|
className: "flex-1 relative",
|
|
111
|
-
children: [/* @__PURE__ */
|
|
112
|
+
children: [/* @__PURE__ */ S(b, {
|
|
112
113
|
className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground",
|
|
113
114
|
size: 20
|
|
114
|
-
}), /* @__PURE__ */
|
|
115
|
+
}), /* @__PURE__ */ S("input", {
|
|
115
116
|
type: "text",
|
|
116
|
-
placeholder:
|
|
117
|
-
value:
|
|
118
|
-
onChange: (e) =>
|
|
117
|
+
placeholder: T("toursList.searchPlaceholder"),
|
|
118
|
+
value: Y,
|
|
119
|
+
onChange: (e) => oe(e.target.value),
|
|
119
120
|
className: "w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
|
120
121
|
})]
|
|
121
|
-
}), /* @__PURE__ */
|
|
122
|
+
}), /* @__PURE__ */ C("div", {
|
|
122
123
|
className: "flex gap-2",
|
|
123
|
-
children: [/* @__PURE__ */
|
|
124
|
-
value:
|
|
125
|
-
onChange: (e) =>
|
|
124
|
+
children: [/* @__PURE__ */ C("select", {
|
|
125
|
+
value: ie,
|
|
126
|
+
onChange: (e) => se(e.target.value),
|
|
126
127
|
className: "px-4 py-2 min-h-[44px] border border-border rounded-md bg-background text-foreground",
|
|
127
128
|
children: [
|
|
128
|
-
/* @__PURE__ */
|
|
129
|
+
/* @__PURE__ */ S("option", {
|
|
129
130
|
value: "all",
|
|
130
|
-
children:
|
|
131
|
+
children: T("toursList.statusAll")
|
|
131
132
|
}),
|
|
132
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ S("option", {
|
|
133
134
|
value: "DRAFT",
|
|
134
|
-
children:
|
|
135
|
+
children: T("toursList.statusDraft")
|
|
135
136
|
}),
|
|
136
|
-
/* @__PURE__ */
|
|
137
|
+
/* @__PURE__ */ S("option", {
|
|
137
138
|
value: "PUBLISHED",
|
|
138
|
-
children:
|
|
139
|
+
children: T("toursList.statusPublished")
|
|
139
140
|
}),
|
|
140
|
-
/* @__PURE__ */
|
|
141
|
+
/* @__PURE__ */ S("option", {
|
|
141
142
|
value: "ARCHIVED",
|
|
142
|
-
children:
|
|
143
|
+
children: T("toursList.statusArchived")
|
|
143
144
|
}),
|
|
144
|
-
/* @__PURE__ */
|
|
145
|
+
/* @__PURE__ */ S("option", {
|
|
145
146
|
value: "SCHEDULED",
|
|
146
|
-
children:
|
|
147
|
+
children: T("toursList.statusScheduled")
|
|
147
148
|
})
|
|
148
149
|
]
|
|
149
|
-
}), /* @__PURE__ */
|
|
150
|
+
}), /* @__PURE__ */ C("div", {
|
|
150
151
|
className: "flex border border-border rounded-md overflow-hidden",
|
|
151
|
-
children: [/* @__PURE__ */
|
|
152
|
-
onClick: () =>
|
|
153
|
-
className: `px-3 py-2 min-h-[44px] ${
|
|
154
|
-
"aria-label":
|
|
155
|
-
title:
|
|
156
|
-
children: /* @__PURE__ */
|
|
157
|
-
}), /* @__PURE__ */
|
|
158
|
-
onClick: () =>
|
|
159
|
-
className: `px-3 py-2 min-h-[44px] ${
|
|
160
|
-
"aria-label":
|
|
161
|
-
title:
|
|
162
|
-
children: /* @__PURE__ */
|
|
152
|
+
children: [/* @__PURE__ */ S("button", {
|
|
153
|
+
onClick: () => j("grid"),
|
|
154
|
+
className: `px-3 py-2 min-h-[44px] ${O === "grid" ? "bg-primary text-primary-foreground" : "bg-background text-foreground hover:bg-accent"}`,
|
|
155
|
+
"aria-label": T("toursList.gridView"),
|
|
156
|
+
title: T("toursList.gridView"),
|
|
157
|
+
children: /* @__PURE__ */ S(v, { size: 20 })
|
|
158
|
+
}), /* @__PURE__ */ S("button", {
|
|
159
|
+
onClick: () => j("list"),
|
|
160
|
+
className: `px-3 py-2 min-h-[44px] ${O === "list" ? "bg-primary text-primary-foreground" : "bg-background text-foreground hover:bg-accent"}`,
|
|
161
|
+
"aria-label": T("toursList.listView"),
|
|
162
|
+
title: T("toursList.listView"),
|
|
163
|
+
children: /* @__PURE__ */ S(y, { size: 20 })
|
|
163
164
|
})]
|
|
164
165
|
})]
|
|
165
166
|
})]
|
|
166
167
|
}),
|
|
167
|
-
|
|
168
|
+
U ? /* @__PURE__ */ S("div", {
|
|
168
169
|
className: "text-center py-12",
|
|
169
|
-
children: /* @__PURE__ */
|
|
170
|
+
children: /* @__PURE__ */ S("p", {
|
|
170
171
|
className: "text-sm sm:text-base text-muted-foreground",
|
|
171
|
-
children:
|
|
172
|
+
children: T("toursList.loading")
|
|
172
173
|
})
|
|
173
|
-
}) :
|
|
174
|
-
className:
|
|
175
|
-
children:
|
|
176
|
-
let t = e.status, n = t === "DRAFT" &&
|
|
177
|
-
return /* @__PURE__ */
|
|
174
|
+
}) : H.length > 0 ? /* @__PURE__ */ S("div", {
|
|
175
|
+
className: O === "grid" ? "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4" : "space-y-3",
|
|
176
|
+
children: H.map((e) => {
|
|
177
|
+
let t = e.status, n = t === "DRAFT" && z, r = t === "PUBLISHED" && z, i = t !== "ARCHIVED" && B, a = n || r || V || i || R;
|
|
178
|
+
return /* @__PURE__ */ C("div", {
|
|
178
179
|
className: "border border-border rounded-md p-4 bg-card hover:shadow-md transition-shadow",
|
|
179
180
|
children: [
|
|
180
|
-
/* @__PURE__ */
|
|
181
|
+
/* @__PURE__ */ C("div", {
|
|
181
182
|
className: "flex items-start justify-between mb-2",
|
|
182
|
-
children: [/* @__PURE__ */
|
|
183
|
-
className: `font-semibold text-foreground ${
|
|
183
|
+
children: [/* @__PURE__ */ S("h3", {
|
|
184
|
+
className: `font-semibold text-foreground ${I ? "cursor-pointer hover:text-primary" : ""}`,
|
|
184
185
|
onClick: () => {
|
|
185
|
-
|
|
186
|
+
I && E(`${D}/${e.id}/edit`);
|
|
186
187
|
},
|
|
187
188
|
children: e.name
|
|
188
|
-
}), /* @__PURE__ */
|
|
189
|
+
}), /* @__PURE__ */ S("span", {
|
|
189
190
|
className: `px-2 py-1 text-xs rounded-full ${e.status === "PUBLISHED" ? "bg-success/10 text-success" : e.status === "DRAFT" ? "bg-muted text-muted-foreground" : "bg-warning/10 text-warning"}`,
|
|
190
|
-
children:
|
|
191
|
+
children: le(t)
|
|
191
192
|
})]
|
|
192
193
|
}),
|
|
193
|
-
e.description && /* @__PURE__ */
|
|
194
|
+
e.description && /* @__PURE__ */ S("p", {
|
|
194
195
|
className: "text-sm text-muted-foreground mb-3",
|
|
195
196
|
children: e.description
|
|
196
197
|
}),
|
|
197
|
-
/* @__PURE__ */
|
|
198
|
+
/* @__PURE__ */ C("div", {
|
|
198
199
|
className: "flex items-center justify-between text-xs text-muted-foreground mb-3",
|
|
199
|
-
children: [/* @__PURE__ */
|
|
200
|
+
children: [/* @__PURE__ */ C("span", { children: [
|
|
200
201
|
e.steps?.length || 0,
|
|
201
202
|
" ",
|
|
202
|
-
|
|
203
|
-
] }), /* @__PURE__ */
|
|
203
|
+
T("toursList.steps")
|
|
204
|
+
] }), /* @__PURE__ */ C("span", { children: [
|
|
204
205
|
e.viewCount,
|
|
205
206
|
" ",
|
|
206
|
-
|
|
207
|
+
T("toursList.views")
|
|
207
208
|
] })]
|
|
208
209
|
}),
|
|
209
|
-
/* @__PURE__ */
|
|
210
|
+
/* @__PURE__ */ C("div", {
|
|
210
211
|
className: "flex items-center gap-2 pt-3 border-t border-border",
|
|
211
212
|
children: [
|
|
212
|
-
|
|
213
|
+
L && /* @__PURE__ */ C("button", {
|
|
213
214
|
onClick: (t) => {
|
|
214
|
-
t.stopPropagation(),
|
|
215
|
+
t.stopPropagation(), E(`${D}/${e.id}/steps`);
|
|
215
216
|
},
|
|
216
217
|
className: "flex-1 px-3 py-1.5 text-xs bg-secondary text-secondary-foreground rounded hover:bg-secondary/80 flex items-center justify-center gap-1.5",
|
|
217
|
-
title:
|
|
218
|
-
children: [/* @__PURE__ */
|
|
218
|
+
title: T("toursList.editStepsTitle"),
|
|
219
|
+
children: [/* @__PURE__ */ S(ee, { size: 14 }), T("toursList.stepsButton")]
|
|
219
220
|
}),
|
|
220
|
-
/* @__PURE__ */
|
|
221
|
+
/* @__PURE__ */ C("button", {
|
|
221
222
|
onClick: (t) => {
|
|
222
|
-
t.stopPropagation(),
|
|
223
|
+
t.stopPropagation(), E(`${D}/${e.id}/preview`);
|
|
223
224
|
},
|
|
224
225
|
className: "flex-1 px-3 py-1.5 text-xs border border-border rounded hover:bg-accent flex items-center justify-center gap-1.5",
|
|
225
|
-
title:
|
|
226
|
-
children: [/* @__PURE__ */
|
|
226
|
+
title: T("toursList.previewTitle"),
|
|
227
|
+
children: [/* @__PURE__ */ S(_, { size: 14 }), T("toursList.previewButton")]
|
|
227
228
|
}),
|
|
228
|
-
|
|
229
|
+
I && /* @__PURE__ */ S("button", {
|
|
229
230
|
onClick: (t) => {
|
|
230
|
-
t.stopPropagation(),
|
|
231
|
+
t.stopPropagation(), E(`${D}/${e.id}/edit`);
|
|
231
232
|
},
|
|
232
233
|
className: "px-2 py-1.5 text-xs border border-border rounded hover:bg-accent",
|
|
233
|
-
title:
|
|
234
|
-
"aria-label":
|
|
235
|
-
children: /* @__PURE__ */ x
|
|
234
|
+
title: T("toursList.settingsTitle"),
|
|
235
|
+
"aria-label": T("toursList.settingsTitle"),
|
|
236
|
+
children: /* @__PURE__ */ S(x, { size: 14 })
|
|
236
237
|
}),
|
|
237
|
-
a && /* @__PURE__ */
|
|
238
|
+
a && /* @__PURE__ */ C("div", {
|
|
238
239
|
className: "relative",
|
|
239
|
-
ref:
|
|
240
|
-
children: [/* @__PURE__ */
|
|
240
|
+
ref: Z === e.id ? $ : void 0,
|
|
241
|
+
children: [/* @__PURE__ */ S("button", {
|
|
241
242
|
onClick: (t) => {
|
|
242
|
-
t.stopPropagation(), Z
|
|
243
|
+
t.stopPropagation(), Q(Z === e.id ? null : e.id);
|
|
243
244
|
},
|
|
244
245
|
className: "px-2 py-1.5 text-xs border border-border rounded hover:bg-accent",
|
|
245
|
-
title:
|
|
246
|
-
"aria-label":
|
|
247
|
-
children: /* @__PURE__ */
|
|
248
|
-
}),
|
|
246
|
+
title: T("toursList.moreActionsTitle"),
|
|
247
|
+
"aria-label": T("toursList.moreActionsTitle"),
|
|
248
|
+
children: /* @__PURE__ */ S(te, { size: 14 })
|
|
249
|
+
}), Z === e.id && /* @__PURE__ */ C("div", {
|
|
249
250
|
className: "absolute right-0 top-full mt-1 w-40 bg-card border border-border rounded-md shadow-lg z-10",
|
|
250
251
|
children: [
|
|
251
|
-
n && /* @__PURE__ */
|
|
252
|
+
n && /* @__PURE__ */ S("button", {
|
|
252
253
|
onClick: (t) => {
|
|
253
|
-
t.stopPropagation(),
|
|
254
|
+
t.stopPropagation(), ue(e.id);
|
|
254
255
|
},
|
|
255
256
|
className: "w-full text-left px-3 py-2 text-xs hover:bg-accent",
|
|
256
|
-
children:
|
|
257
|
+
children: T("toursList.actionPublish")
|
|
257
258
|
}),
|
|
258
|
-
r && /* @__PURE__ */
|
|
259
|
+
r && /* @__PURE__ */ S("button", {
|
|
259
260
|
onClick: (t) => {
|
|
260
|
-
t.stopPropagation(),
|
|
261
|
+
t.stopPropagation(), de(e.id);
|
|
261
262
|
},
|
|
262
263
|
className: "w-full text-left px-3 py-2 text-xs hover:bg-accent",
|
|
263
|
-
children:
|
|
264
|
+
children: T("toursList.actionUnpublish")
|
|
264
265
|
}),
|
|
265
|
-
|
|
266
|
+
V && /* @__PURE__ */ S("button", {
|
|
266
267
|
onClick: (t) => {
|
|
267
|
-
t.stopPropagation(),
|
|
268
|
+
t.stopPropagation(), pe(e.id);
|
|
268
269
|
},
|
|
269
270
|
className: "w-full text-left px-3 py-2 text-xs hover:bg-accent",
|
|
270
|
-
children:
|
|
271
|
+
children: T("toursList.actionDuplicate")
|
|
271
272
|
}),
|
|
272
|
-
i && /* @__PURE__ */
|
|
273
|
+
i && /* @__PURE__ */ S("button", {
|
|
273
274
|
onClick: (t) => {
|
|
274
|
-
t.stopPropagation(),
|
|
275
|
+
t.stopPropagation(), fe(e.id);
|
|
275
276
|
},
|
|
276
277
|
className: "w-full text-left px-3 py-2 text-xs hover:bg-accent",
|
|
277
|
-
children:
|
|
278
|
+
children: T("toursList.actionArchive")
|
|
278
279
|
}),
|
|
279
|
-
|
|
280
|
+
R && /* @__PURE__ */ S("button", {
|
|
280
281
|
onClick: (t) => {
|
|
281
|
-
t.stopPropagation(),
|
|
282
|
+
t.stopPropagation(), me(e.id);
|
|
282
283
|
},
|
|
283
284
|
className: "w-full text-left px-3 py-2 text-xs text-destructive hover:bg-destructive/10",
|
|
284
|
-
children:
|
|
285
|
+
children: T("toursList.actionDelete")
|
|
285
286
|
})
|
|
286
287
|
]
|
|
287
288
|
})]
|
|
@@ -291,20 +292,20 @@ function w() {
|
|
|
291
292
|
]
|
|
292
293
|
}, e.id);
|
|
293
294
|
})
|
|
294
|
-
}) : /* @__PURE__ */
|
|
295
|
+
}) : /* @__PURE__ */ C("div", {
|
|
295
296
|
className: "text-center py-12 border border-dashed border-border rounded-md",
|
|
296
|
-
children: [/* @__PURE__ */
|
|
297
|
+
children: [/* @__PURE__ */ S("p", {
|
|
297
298
|
className: "text-sm sm:text-base text-muted-foreground mb-2",
|
|
298
|
-
children:
|
|
299
|
-
}), /* @__PURE__ */
|
|
299
|
+
children: T("toursList.emptyTitle")
|
|
300
|
+
}), /* @__PURE__ */ S("p", {
|
|
300
301
|
className: "text-xs sm:text-sm text-muted-foreground",
|
|
301
|
-
children:
|
|
302
|
+
children: T("toursList.emptySubtitle")
|
|
302
303
|
})]
|
|
303
304
|
})
|
|
304
305
|
]
|
|
305
306
|
});
|
|
306
307
|
}
|
|
307
308
|
//#endregion
|
|
308
|
-
export {
|
|
309
|
+
export { T as ToursListPage };
|
|
309
310
|
|
|
310
311
|
//# sourceMappingURL=ToursListPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToursListPage.js","names":[],"sources":["../../../src/tours/pages/ToursListPage.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Plus,\n Search,\n Grid,\n List as ListIcon,\n ListOrdered,\n Eye,\n Settings,\n MoreVertical,\n} from 'lucide-react';\nimport { PermissionGate } from '@burdenoff/fe-libs/shared/components';\nimport { useToursList } from '../hooks/useToursList';\nimport { useTourLifecycle } from '../hooks/useTourLifecycle';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport { useToursStore } from '../store/toursStore';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useDeleteTourMutation } from '../../generated/global-operations';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport type { TourStatus } from '../types';\nimport type { Tour } from '../../generated/global-types';\nimport { useToursI18n } from '../i18n';\nimport {\n isNative,\n nativeConfirm,\n nativeImpact,\n nativeNotify,\n nativeToast,\n} from '../../utils/nativeBridge';\n\n/**\n * Tours list page\n * Displays all tours with filtering and search\n */\nexport function ToursListPage() {\n const { tr } = useToursI18n();\n const navigate = useNavigate();\n const { basePath = '/' } = useToursContext();\n const { viewMode, tourFilter, setViewMode, updateTourFilter } = useToursStore();\n const { hasPermission, isLoading: permissionsLoading } = useToursPermissionAccess();\n\n const canUpdateTour = hasPermission(TOUR_PERMISSIONS.TOUR_UPDATE);\n const canUpdateTourSteps = hasPermission(TOUR_PERMISSIONS.TOUR_STEP_UPDATE);\n const canDeleteTour = hasPermission(TOUR_PERMISSIONS.TOUR_DELETE);\n const canPublishTour = hasPermission(TOUR_PERMISSIONS.TOUR_PUBLISH);\n const canArchiveTour = hasPermission(TOUR_PERMISSIONS.TOUR_ARCHIVE);\n const canDuplicateTour = hasPermission(TOUR_PERMISSIONS.TOUR_DUPLICATE);\n\n // Fetch tours (includes both workspace-specific and global tours)\n const { tours, loading, fetchTours } = useToursList();\n\n const { publishTour, unpublishTour, archiveTour, duplicateTour } = useTourLifecycle();\n const [deleteTourMutation] = useDeleteTourMutation();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<TourStatus | 'all'>('all');\n const [openMenuId, setOpenMenuId] = useState<string | null>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n // Fetch tours when filter changes. The list page is also the user-facing\n // tour browser, so backend permissions remain authoritative for actions while\n // normal users can still see published tours returned by the API.\n useEffect(() => {\n if (permissionsLoading) return;\n fetchTours({ ...tourFilter });\n }, [fetchTours, tourFilter, permissionsLoading]);\n\n const handleSearch = (query: string) => {\n setSearchQuery(query);\n updateTourFilter({ search: query });\n };\n\n const handleStatusFilter = (status: TourStatus | 'all') => {\n setStatusFilter(status);\n updateTourFilter({ status });\n };\n\n const handleCreateTour = () => {\n const newPath = basePath.endsWith('/') ? `${basePath}new` : `${basePath}/new`;\n navigate(newPath);\n };\n\n const refreshTours = () => fetchTours({ ...tourFilter });\n\n const getStatusLabel = (status: TourStatus) => {\n switch (status) {\n case 'DRAFT':\n return tr('toursList.statusDraft');\n case 'PUBLISHED':\n return tr('toursList.statusPublished');\n case 'ARCHIVED':\n return tr('toursList.statusArchived');\n case 'SCHEDULED':\n return tr('toursList.statusScheduled');\n }\n };\n\n const handlePublish = async (id: string) => {\n setOpenMenuId(null);\n nativeImpact('light');\n try {\n await publishTour(id);\n void nativeNotify('success');\n refreshTours();\n } catch {\n void nativeNotify('error');\n if (isNative()) void nativeToast(tr('toursList.errorPublish'));\n else alert(tr('toursList.errorPublish'));\n }\n };\n\n const handleUnpublish = async (id: string) => {\n setOpenMenuId(null);\n nativeImpact('light');\n try {\n await unpublishTour(id);\n void nativeNotify('success');\n refreshTours();\n } catch {\n void nativeNotify('error');\n if (isNative()) void nativeToast(tr('toursList.errorUnpublish'));\n else alert(tr('toursList.errorUnpublish'));\n }\n };\n\n const handleArchive = async (id: string) => {\n setOpenMenuId(null);\n nativeImpact('light');\n try {\n await archiveTour(id);\n void nativeNotify('success');\n refreshTours();\n } catch {\n void nativeNotify('error');\n if (isNative()) void nativeToast(tr('toursList.errorArchive'));\n else alert(tr('toursList.errorArchive'));\n }\n };\n\n const handleDuplicate = async (id: string) => {\n setOpenMenuId(null);\n nativeImpact('light');\n try {\n await duplicateTour(id);\n void nativeNotify('success');\n refreshTours();\n } catch {\n void nativeNotify('error');\n if (isNative()) void nativeToast(tr('toursList.errorDuplicate'));\n else alert(tr('toursList.errorDuplicate'));\n }\n };\n\n const handleDelete = async (id: string) => {\n setOpenMenuId(null);\n nativeImpact('medium');\n const confirmMsg = tr('toursList.confirmDelete');\n const nativeResult = await nativeConfirm({\n title: tr('toursList.actionDelete'),\n message: confirmMsg,\n okButtonTitle: tr('toursList.actionDelete'),\n });\n const confirmed = nativeResult === null ? window.confirm(confirmMsg) : nativeResult;\n if (!confirmed) return;\n try {\n await deleteTourMutation({ variables: { id } });\n void nativeNotify('success');\n refreshTours();\n } catch {\n void nativeNotify('error');\n if (isNative()) void nativeToast(tr('toursList.errorDelete'));\n else alert(tr('toursList.errorDelete'));\n }\n };\n\n // Close dropdown when clicking outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (menuRef.current && !menuRef.current.contains(event.target as Node)) {\n setOpenMenuId(null);\n }\n };\n if (openMenuId) {\n document.addEventListener('mousedown', handleClickOutside);\n }\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, [openMenuId]);\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n <div className=\"flex-1 min-w-0\">\n <h1 className=\"text-xl sm:text-2xl font-bold text-foreground\">{tr('toursList.title')}</h1>\n <p className=\"text-sm sm:text-base text-muted-foreground\">{tr('toursList.subtitle')}</p>\n </div>\n <PermissionGate permission={TOUR_PERMISSIONS.TOUR_CREATE}>\n <button\n onClick={handleCreateTour}\n className=\"w-full sm:w-auto px-4 py-2 min-h-[44px] bg-primary text-primary-foreground rounded-md hover:bg-primary/90 active:bg-primary/80 flex items-center justify-center gap-2\"\n >\n <Plus size={20} />\n {tr('toursList.createTour')}\n </button>\n </PermissionGate>\n </div>\n\n {/* Filters and Search */}\n <div className=\"flex flex-col sm:flex-row gap-3 sm:gap-4\">\n {/* Search */}\n <div className=\"flex-1 relative\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n size={20}\n />\n <input\n type=\"text\"\n placeholder={tr('toursList.searchPlaceholder')}\n value={searchQuery}\n onChange={(e) => handleSearch(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n />\n </div>\n\n {/* Status Filter */}\n <div className=\"flex gap-2\">\n <select\n value={statusFilter}\n onChange={(e) => handleStatusFilter(e.target.value as TourStatus | 'all')}\n className=\"px-4 py-2 min-h-[44px] border border-border rounded-md bg-background text-foreground\"\n >\n <option value=\"all\">{tr('toursList.statusAll')}</option>\n <option value=\"DRAFT\">{tr('toursList.statusDraft')}</option>\n <option value=\"PUBLISHED\">{tr('toursList.statusPublished')}</option>\n <option value=\"ARCHIVED\">{tr('toursList.statusArchived')}</option>\n <option value=\"SCHEDULED\">{tr('toursList.statusScheduled')}</option>\n </select>\n\n {/* View Mode Toggle */}\n <div className=\"flex border border-border rounded-md overflow-hidden\">\n <button\n onClick={() => setViewMode('grid')}\n className={`px-3 py-2 min-h-[44px] ${\n viewMode === 'grid'\n ? 'bg-primary text-primary-foreground'\n : 'bg-background text-foreground hover:bg-accent'\n }`}\n aria-label={tr('toursList.gridView')}\n title={tr('toursList.gridView')}\n >\n <Grid size={20} />\n </button>\n <button\n onClick={() => setViewMode('list')}\n className={`px-3 py-2 min-h-[44px] ${\n viewMode === 'list'\n ? 'bg-primary text-primary-foreground'\n : 'bg-background text-foreground hover:bg-accent'\n }`}\n aria-label={tr('toursList.listView')}\n title={tr('toursList.listView')}\n >\n <ListIcon size={20} />\n </button>\n </div>\n </div>\n </div>\n\n {/* Tours List */}\n {loading ? (\n <div className=\"text-center py-12\">\n <p className=\"text-sm sm:text-base text-muted-foreground\">{tr('toursList.loading')}</p>\n </div>\n ) : tours.length > 0 ? (\n <div\n className={\n viewMode === 'grid'\n ? 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4'\n : 'space-y-3'\n }\n >\n {tours.map((tour: Tour) => {\n const status = tour.status as TourStatus;\n const canShowPublishAction = status === 'DRAFT' && canPublishTour;\n const canShowUnpublishAction = status === 'PUBLISHED' && canPublishTour;\n const canShowArchiveAction = status !== 'ARCHIVED' && canArchiveTour;\n const hasMoreActions =\n canShowPublishAction ||\n canShowUnpublishAction ||\n canDuplicateTour ||\n canShowArchiveAction ||\n canDeleteTour;\n\n return (\n <div\n key={tour.id}\n className=\"border border-border rounded-md p-4 bg-card hover:shadow-md transition-shadow\"\n >\n <div className=\"flex items-start justify-between mb-2\">\n <h3\n className={`font-semibold text-foreground ${\n canUpdateTour ? 'cursor-pointer hover:text-primary' : ''\n }`}\n onClick={() => {\n if (canUpdateTour) navigate(`${basePath}/${tour.id}/edit`);\n }}\n >\n {tour.name}\n </h3>\n <span\n className={`px-2 py-1 text-xs rounded-full ${\n tour.status === 'PUBLISHED'\n ? 'bg-success/10 text-success'\n : tour.status === 'DRAFT'\n ? 'bg-muted text-muted-foreground'\n : 'bg-warning/10 text-warning'\n }`}\n >\n {getStatusLabel(status)}\n </span>\n </div>\n {tour.description && (\n <p className=\"text-sm text-muted-foreground mb-3\">{tour.description}</p>\n )}\n <div className=\"flex items-center justify-between text-xs text-muted-foreground mb-3\">\n <span>\n {tour.steps?.length || 0} {tr('toursList.steps')}\n </span>\n <span>\n {tour.viewCount} {tr('toursList.views')}\n </span>\n </div>\n {/* Quick Actions */}\n <div className=\"flex items-center gap-2 pt-3 border-t border-border\">\n {canUpdateTourSteps && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`${basePath}/${tour.id}/steps`);\n }}\n className=\"flex-1 px-3 py-1.5 text-xs bg-secondary text-secondary-foreground rounded hover:bg-secondary/80 flex items-center justify-center gap-1.5\"\n title={tr('toursList.editStepsTitle')}\n >\n <ListOrdered size={14} />\n {tr('toursList.stepsButton')}\n </button>\n )}\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`${basePath}/${tour.id}/preview`);\n }}\n className=\"flex-1 px-3 py-1.5 text-xs border border-border rounded hover:bg-accent flex items-center justify-center gap-1.5\"\n title={tr('toursList.previewTitle')}\n >\n <Eye size={14} />\n {tr('toursList.previewButton')}\n </button>\n {canUpdateTour && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`${basePath}/${tour.id}/edit`);\n }}\n className=\"px-2 py-1.5 text-xs border border-border rounded hover:bg-accent\"\n title={tr('toursList.settingsTitle')}\n aria-label={tr('toursList.settingsTitle')}\n >\n <Settings size={14} />\n </button>\n )}\n {/* More Actions Dropdown */}\n {hasMoreActions && (\n <div className=\"relative\" ref={openMenuId === tour.id ? menuRef : undefined}>\n <button\n onClick={(e) => {\n e.stopPropagation();\n setOpenMenuId(openMenuId === tour.id ? null : tour.id);\n }}\n className=\"px-2 py-1.5 text-xs border border-border rounded hover:bg-accent\"\n title={tr('toursList.moreActionsTitle')}\n aria-label={tr('toursList.moreActionsTitle')}\n >\n <MoreVertical size={14} />\n </button>\n {openMenuId === tour.id && (\n <div className=\"absolute right-0 top-full mt-1 w-40 bg-card border border-border rounded-md shadow-lg z-10\">\n {canShowPublishAction && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n handlePublish(tour.id);\n }}\n className=\"w-full text-left px-3 py-2 text-xs hover:bg-accent\"\n >\n {tr('toursList.actionPublish')}\n </button>\n )}\n {canShowUnpublishAction && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n handleUnpublish(tour.id);\n }}\n className=\"w-full text-left px-3 py-2 text-xs hover:bg-accent\"\n >\n {tr('toursList.actionUnpublish')}\n </button>\n )}\n {canDuplicateTour && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n handleDuplicate(tour.id);\n }}\n className=\"w-full text-left px-3 py-2 text-xs hover:bg-accent\"\n >\n {tr('toursList.actionDuplicate')}\n </button>\n )}\n {canShowArchiveAction && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n handleArchive(tour.id);\n }}\n className=\"w-full text-left px-3 py-2 text-xs hover:bg-accent\"\n >\n {tr('toursList.actionArchive')}\n </button>\n )}\n {canDeleteTour && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n handleDelete(tour.id);\n }}\n className=\"w-full text-left px-3 py-2 text-xs text-destructive hover:bg-destructive/10\"\n >\n {tr('toursList.actionDelete')}\n </button>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n );\n })}\n </div>\n ) : (\n <div className=\"text-center py-12 border border-dashed border-border rounded-md\">\n <p className=\"text-sm sm:text-base text-muted-foreground mb-2\">\n {tr('toursList.emptyTitle')}\n </p>\n <p className=\"text-xs sm:text-sm text-muted-foreground\">\n {tr('toursList.emptySubtitle')}\n </p>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ToursListPage.js","names":[],"sources":["../../../src/tours/pages/ToursListPage.tsx"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Plus,\n Search,\n Grid,\n List as ListIcon,\n ListOrdered,\n Eye,\n Settings,\n MoreVertical,\n} from 'lucide-react';\nimport { PermissionGate } from '@burdenoff/fe-libs/shared/components';\nimport { useToursList } from '../hooks/useToursList';\nimport { useTourLifecycle } from '../hooks/useTourLifecycle';\nimport { useToursPermissionAccess } from '../hooks/useToursPermissionAccess';\nimport { useToursStore } from '../store/toursStore';\nimport { useToursContext } from '../providers/ToursProvider';\nimport { useDeleteTourMutation } from '../../generated/global-operations';\nimport { TOUR_PERMISSIONS } from '../../constants/permissions';\nimport type { TourStatus } from '../types';\nimport type { Tour } from '../../generated/global-types';\nimport { useToursI18n } from '../i18n';\nimport {\n isNative,\n nativeConfirm,\n nativeImpact,\n nativeNotify,\n nativeToast,\n} from '../../utils/nativeBridge';\n\n/**\n * Tours list page\n * Displays all tours with filtering and search\n */\nexport function ToursListPage() {\n const { tr } = useToursI18n();\n const navigate = useNavigate();\n const { basePath = '/' } = useToursContext();\n const {\n viewMode,\n tourFilter,\n toursListRevision,\n setViewMode,\n updateTourFilter,\n notifyToursListChanged,\n } = useToursStore();\n const { hasPermission, isLoading: permissionsLoading } = useToursPermissionAccess();\n\n const canUpdateTour = hasPermission(TOUR_PERMISSIONS.TOUR_UPDATE);\n const canUpdateTourSteps = hasPermission(TOUR_PERMISSIONS.TOUR_STEP_UPDATE);\n const canDeleteTour = hasPermission(TOUR_PERMISSIONS.TOUR_DELETE);\n const canPublishTour = hasPermission(TOUR_PERMISSIONS.TOUR_PUBLISH);\n const canArchiveTour = hasPermission(TOUR_PERMISSIONS.TOUR_ARCHIVE);\n const canDuplicateTour = hasPermission(TOUR_PERMISSIONS.TOUR_DUPLICATE);\n\n // Fetch tours (includes both workspace-specific and global tours)\n const { tours, loading, fetchTours } = useToursList();\n\n const { publishTour, unpublishTour, archiveTour, duplicateTour } = useTourLifecycle();\n const [deleteTourMutation] = useDeleteTourMutation();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<TourStatus | 'all'>('all');\n const [openMenuId, setOpenMenuId] = useState<string | null>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n // Fetch tours when filter changes. The list page is also the user-facing\n // tour browser, so backend permissions remain authoritative for actions while\n // normal users can still see published tours returned by the API.\n useEffect(() => {\n if (permissionsLoading) return;\n fetchTours({ ...tourFilter });\n }, [fetchTours, tourFilter, toursListRevision, permissionsLoading]);\n\n const handleSearch = (query: string) => {\n setSearchQuery(query);\n updateTourFilter({ search: query });\n };\n\n const handleStatusFilter = (status: TourStatus | 'all') => {\n setStatusFilter(status);\n updateTourFilter({ status });\n };\n\n const handleCreateTour = () => {\n const newPath = basePath.endsWith('/') ? `${basePath}new` : `${basePath}/new`;\n navigate(newPath);\n };\n\n const getStatusLabel = (status: TourStatus) => {\n switch (status) {\n case 'DRAFT':\n return tr('toursList.statusDraft');\n case 'PUBLISHED':\n return tr('toursList.statusPublished');\n case 'ARCHIVED':\n return tr('toursList.statusArchived');\n case 'SCHEDULED':\n return tr('toursList.statusScheduled');\n }\n };\n\n const handlePublish = async (id: string) => {\n setOpenMenuId(null);\n nativeImpact('light');\n try {\n await publishTour(id);\n void nativeNotify('success');\n } catch {\n void nativeNotify('error');\n if (isNative()) void nativeToast(tr('toursList.errorPublish'));\n else alert(tr('toursList.errorPublish'));\n }\n };\n\n const handleUnpublish = async (id: string) => {\n setOpenMenuId(null);\n nativeImpact('light');\n try {\n await unpublishTour(id);\n void nativeNotify('success');\n } catch {\n void nativeNotify('error');\n if (isNative()) void nativeToast(tr('toursList.errorUnpublish'));\n else alert(tr('toursList.errorUnpublish'));\n }\n };\n\n const handleArchive = async (id: string) => {\n setOpenMenuId(null);\n nativeImpact('light');\n try {\n await archiveTour(id);\n void nativeNotify('success');\n } catch {\n void nativeNotify('error');\n if (isNative()) void nativeToast(tr('toursList.errorArchive'));\n else alert(tr('toursList.errorArchive'));\n }\n };\n\n const handleDuplicate = async (id: string) => {\n setOpenMenuId(null);\n nativeImpact('light');\n try {\n await duplicateTour(id);\n void nativeNotify('success');\n } catch {\n void nativeNotify('error');\n if (isNative()) void nativeToast(tr('toursList.errorDuplicate'));\n else alert(tr('toursList.errorDuplicate'));\n }\n };\n\n const handleDelete = async (id: string) => {\n setOpenMenuId(null);\n nativeImpact('medium');\n const confirmMsg = tr('toursList.confirmDelete');\n const nativeResult = await nativeConfirm({\n title: tr('toursList.actionDelete'),\n message: confirmMsg,\n okButtonTitle: tr('toursList.actionDelete'),\n });\n const confirmed = nativeResult === null ? window.confirm(confirmMsg) : nativeResult;\n if (!confirmed) return;\n try {\n await deleteTourMutation({ variables: { id } });\n notifyToursListChanged();\n void nativeNotify('success');\n } catch {\n void nativeNotify('error');\n if (isNative()) void nativeToast(tr('toursList.errorDelete'));\n else alert(tr('toursList.errorDelete'));\n }\n };\n\n // Close dropdown when clicking outside\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (menuRef.current && !menuRef.current.contains(event.target as Node)) {\n setOpenMenuId(null);\n }\n };\n if (openMenuId) {\n document.addEventListener('mousedown', handleClickOutside);\n }\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, [openMenuId]);\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n <div className=\"flex-1 min-w-0\">\n <h1 className=\"text-xl sm:text-2xl font-bold text-foreground\">{tr('toursList.title')}</h1>\n <p className=\"text-sm sm:text-base text-muted-foreground\">{tr('toursList.subtitle')}</p>\n </div>\n <PermissionGate permission={TOUR_PERMISSIONS.TOUR_CREATE}>\n <button\n onClick={handleCreateTour}\n className=\"w-full sm:w-auto px-4 py-2 min-h-[44px] bg-primary text-primary-foreground rounded-md hover:bg-primary/90 active:bg-primary/80 flex items-center justify-center gap-2\"\n >\n <Plus size={20} />\n {tr('toursList.createTour')}\n </button>\n </PermissionGate>\n </div>\n\n {/* Filters and Search */}\n <div className=\"flex flex-col sm:flex-row gap-3 sm:gap-4\">\n {/* Search */}\n <div className=\"flex-1 relative\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n size={20}\n />\n <input\n type=\"text\"\n placeholder={tr('toursList.searchPlaceholder')}\n value={searchQuery}\n onChange={(e) => handleSearch(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 min-h-[44px] border border-border rounded-md bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring\"\n />\n </div>\n\n {/* Status Filter */}\n <div className=\"flex gap-2\">\n <select\n value={statusFilter}\n onChange={(e) => handleStatusFilter(e.target.value as TourStatus | 'all')}\n className=\"px-4 py-2 min-h-[44px] border border-border rounded-md bg-background text-foreground\"\n >\n <option value=\"all\">{tr('toursList.statusAll')}</option>\n <option value=\"DRAFT\">{tr('toursList.statusDraft')}</option>\n <option value=\"PUBLISHED\">{tr('toursList.statusPublished')}</option>\n <option value=\"ARCHIVED\">{tr('toursList.statusArchived')}</option>\n <option value=\"SCHEDULED\">{tr('toursList.statusScheduled')}</option>\n </select>\n\n {/* View Mode Toggle */}\n <div className=\"flex border border-border rounded-md overflow-hidden\">\n <button\n onClick={() => setViewMode('grid')}\n className={`px-3 py-2 min-h-[44px] ${\n viewMode === 'grid'\n ? 'bg-primary text-primary-foreground'\n : 'bg-background text-foreground hover:bg-accent'\n }`}\n aria-label={tr('toursList.gridView')}\n title={tr('toursList.gridView')}\n >\n <Grid size={20} />\n </button>\n <button\n onClick={() => setViewMode('list')}\n className={`px-3 py-2 min-h-[44px] ${\n viewMode === 'list'\n ? 'bg-primary text-primary-foreground'\n : 'bg-background text-foreground hover:bg-accent'\n }`}\n aria-label={tr('toursList.listView')}\n title={tr('toursList.listView')}\n >\n <ListIcon size={20} />\n </button>\n </div>\n </div>\n </div>\n\n {/* Tours List */}\n {loading ? (\n <div className=\"text-center py-12\">\n <p className=\"text-sm sm:text-base text-muted-foreground\">{tr('toursList.loading')}</p>\n </div>\n ) : tours.length > 0 ? (\n <div\n className={\n viewMode === 'grid'\n ? 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4'\n : 'space-y-3'\n }\n >\n {tours.map((tour: Tour) => {\n const status = tour.status as TourStatus;\n const canShowPublishAction = status === 'DRAFT' && canPublishTour;\n const canShowUnpublishAction = status === 'PUBLISHED' && canPublishTour;\n const canShowArchiveAction = status !== 'ARCHIVED' && canArchiveTour;\n const hasMoreActions =\n canShowPublishAction ||\n canShowUnpublishAction ||\n canDuplicateTour ||\n canShowArchiveAction ||\n canDeleteTour;\n\n return (\n <div\n key={tour.id}\n className=\"border border-border rounded-md p-4 bg-card hover:shadow-md transition-shadow\"\n >\n <div className=\"flex items-start justify-between mb-2\">\n <h3\n className={`font-semibold text-foreground ${\n canUpdateTour ? 'cursor-pointer hover:text-primary' : ''\n }`}\n onClick={() => {\n if (canUpdateTour) navigate(`${basePath}/${tour.id}/edit`);\n }}\n >\n {tour.name}\n </h3>\n <span\n className={`px-2 py-1 text-xs rounded-full ${\n tour.status === 'PUBLISHED'\n ? 'bg-success/10 text-success'\n : tour.status === 'DRAFT'\n ? 'bg-muted text-muted-foreground'\n : 'bg-warning/10 text-warning'\n }`}\n >\n {getStatusLabel(status)}\n </span>\n </div>\n {tour.description && (\n <p className=\"text-sm text-muted-foreground mb-3\">{tour.description}</p>\n )}\n <div className=\"flex items-center justify-between text-xs text-muted-foreground mb-3\">\n <span>\n {tour.steps?.length || 0} {tr('toursList.steps')}\n </span>\n <span>\n {tour.viewCount} {tr('toursList.views')}\n </span>\n </div>\n {/* Quick Actions */}\n <div className=\"flex items-center gap-2 pt-3 border-t border-border\">\n {canUpdateTourSteps && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`${basePath}/${tour.id}/steps`);\n }}\n className=\"flex-1 px-3 py-1.5 text-xs bg-secondary text-secondary-foreground rounded hover:bg-secondary/80 flex items-center justify-center gap-1.5\"\n title={tr('toursList.editStepsTitle')}\n >\n <ListOrdered size={14} />\n {tr('toursList.stepsButton')}\n </button>\n )}\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`${basePath}/${tour.id}/preview`);\n }}\n className=\"flex-1 px-3 py-1.5 text-xs border border-border rounded hover:bg-accent flex items-center justify-center gap-1.5\"\n title={tr('toursList.previewTitle')}\n >\n <Eye size={14} />\n {tr('toursList.previewButton')}\n </button>\n {canUpdateTour && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n navigate(`${basePath}/${tour.id}/edit`);\n }}\n className=\"px-2 py-1.5 text-xs border border-border rounded hover:bg-accent\"\n title={tr('toursList.settingsTitle')}\n aria-label={tr('toursList.settingsTitle')}\n >\n <Settings size={14} />\n </button>\n )}\n {/* More Actions Dropdown */}\n {hasMoreActions && (\n <div className=\"relative\" ref={openMenuId === tour.id ? menuRef : undefined}>\n <button\n onClick={(e) => {\n e.stopPropagation();\n setOpenMenuId(openMenuId === tour.id ? null : tour.id);\n }}\n className=\"px-2 py-1.5 text-xs border border-border rounded hover:bg-accent\"\n title={tr('toursList.moreActionsTitle')}\n aria-label={tr('toursList.moreActionsTitle')}\n >\n <MoreVertical size={14} />\n </button>\n {openMenuId === tour.id && (\n <div className=\"absolute right-0 top-full mt-1 w-40 bg-card border border-border rounded-md shadow-lg z-10\">\n {canShowPublishAction && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n handlePublish(tour.id);\n }}\n className=\"w-full text-left px-3 py-2 text-xs hover:bg-accent\"\n >\n {tr('toursList.actionPublish')}\n </button>\n )}\n {canShowUnpublishAction && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n handleUnpublish(tour.id);\n }}\n className=\"w-full text-left px-3 py-2 text-xs hover:bg-accent\"\n >\n {tr('toursList.actionUnpublish')}\n </button>\n )}\n {canDuplicateTour && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n handleDuplicate(tour.id);\n }}\n className=\"w-full text-left px-3 py-2 text-xs hover:bg-accent\"\n >\n {tr('toursList.actionDuplicate')}\n </button>\n )}\n {canShowArchiveAction && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n handleArchive(tour.id);\n }}\n className=\"w-full text-left px-3 py-2 text-xs hover:bg-accent\"\n >\n {tr('toursList.actionArchive')}\n </button>\n )}\n {canDeleteTour && (\n <button\n onClick={(e) => {\n e.stopPropagation();\n handleDelete(tour.id);\n }}\n className=\"w-full text-left px-3 py-2 text-xs text-destructive hover:bg-destructive/10\"\n >\n {tr('toursList.actionDelete')}\n </button>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n );\n })}\n </div>\n ) : (\n <div className=\"text-center py-12 border border-dashed border-border rounded-md\">\n <p className=\"text-sm sm:text-base text-muted-foreground mb-2\">\n {tr('toursList.emptyTitle')}\n </p>\n <p className=\"text-xs sm:text-sm text-muted-foreground\">\n {tr('toursList.emptySubtitle')}\n </p>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -6,6 +6,7 @@ interface ToursState {
|
|
|
6
6
|
viewMode: ToursViewMode;
|
|
7
7
|
devicePreview: DeviceType;
|
|
8
8
|
tourFilter: TourFilters;
|
|
9
|
+
toursListRevision: number;
|
|
9
10
|
stepSequence: TourStep[];
|
|
10
11
|
isDirty: boolean;
|
|
11
12
|
validationErrors: ValidationError[];
|
|
@@ -20,6 +21,7 @@ interface ToursState {
|
|
|
20
21
|
setSidebarOpen: (open: boolean) => void;
|
|
21
22
|
updateTourFilter: (filter: Partial<TourFilters>) => void;
|
|
22
23
|
resetTourFilter: () => void;
|
|
24
|
+
notifyToursListChanged: () => void;
|
|
23
25
|
updateStepSequence: (steps: TourStep[]) => void;
|
|
24
26
|
reorderSteps: (startIndex: number, endIndex: number) => void;
|
|
25
27
|
addStep: (step: Partial<TourStep>) => void;
|