Skip to content

Commit 40ed981

Browse files
committed
fully implement adding tags to all selected accounts
1 parent 504a999 commit 40ed981

File tree

4 files changed

+180
-4
lines changed

4 files changed

+180
-4
lines changed

src/packages/frontend/frame-editors/course-editor/course-panel-wrapper.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ so there is no common containing react component that we control...
1212
*/
1313

1414
import { Map } from "immutable";
15-
1615
import {
1716
AppRedux,
1817
React,

src/packages/frontend/frame-editors/crm-editor/views/view-menu/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export default function ViewMenu({
5858
title={title}
5959
primaryKey={primaryKey}
6060
columns={columns}
61+
refresh={refresh}
6162
/>
6263
<HideFieldsMenu
6364
columns={allColumns}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { useState } from "react";
2+
import { Alert, Button, Input, Modal, Progress, Space, Spin } from "antd";
3+
import { Icon } from "@cocalc/frontend/components/icon";
4+
import { webapp_client } from "@cocalc/frontend/webapp-client";
5+
import type { ColumnsType } from "../../fields";
6+
import { Set } from "immutable";
7+
import { plural } from "@cocalc/util/misc";
8+
import ShowError from "@cocalc/frontend/components/error";
9+
import { map as awaitMap } from "awaiting";
10+
const MAX_PARALLEL_TASKS = 15;
11+
12+
interface Props {
13+
title: string;
14+
onClose: () => void;
15+
selected: Set<any> | undefined;
16+
data: { account_id: string; tags?: string[] }[];
17+
primaryKey: string;
18+
columns: ColumnsType[];
19+
refresh: Function;
20+
}
21+
22+
export default function TagAccounts({
23+
title,
24+
onClose,
25+
selected,
26+
data,
27+
primaryKey,
28+
columns,
29+
refresh,
30+
}: Props) {
31+
console.log({ title, selected, data, primaryKey, columns });
32+
const [value, setValue] = useState<string>("");
33+
const [loading, setLoading] = useState<boolean>(false);
34+
const [progress, setProgress] = useState<number>(0);
35+
const [error, setError] = useState<string>("");
36+
37+
if (selected == null) {
38+
return null;
39+
}
40+
41+
const save = async () => {
42+
const tags = value
43+
.split(",")
44+
.map((x) => x.trim())
45+
.filter((x) => !!x);
46+
if (tags.length == 0 || selected == null || selected.size == 0) {
47+
setValue("");
48+
return;
49+
}
50+
const errors: string[] = [];
51+
const tagsByAccount: { [account_id: string]: string[] } = {};
52+
for (const { account_id, tags } of data) {
53+
tagsByAccount[account_id] = tags ?? [];
54+
}
55+
try {
56+
setProgress(0);
57+
setLoading(true);
58+
let done = 0;
59+
let goal = selected.size;
60+
const task = async (account_id) => {
61+
try {
62+
await webapp_client.async_query({
63+
query: {
64+
crm_accounts: {
65+
account_id,
66+
tags: tagsByAccount[account_id].concat(tags),
67+
},
68+
},
69+
});
70+
} catch (err) {
71+
errors.push(`${err}`);
72+
}
73+
done += 1;
74+
setProgress(Math.round((done * 100) / goal));
75+
};
76+
await awaitMap(Array.from(selected), MAX_PARALLEL_TASKS, task);
77+
setValue("");
78+
} finally {
79+
refresh();
80+
if (errors.length > 0) {
81+
setError(errors.join(" \n"));
82+
}
83+
setProgress(100);
84+
setLoading(false);
85+
if (errors.length == 0) {
86+
onClose();
87+
}
88+
}
89+
};
90+
91+
return (
92+
<Modal
93+
open
94+
footer={null}
95+
title={
96+
<div style={{ margin: "0 15px" }}>
97+
<Icon name="tags-outlined" /> Tag {selected.size} Selected{" "}
98+
{plural(selected.size, "Account")}
99+
</div>
100+
}
101+
onCancel={onClose}
102+
>
103+
<div style={{ margin: "30px 15px" }}>
104+
<Space.Compact style={{ width: "100%" }}>
105+
<Input
106+
allowClear
107+
value={value}
108+
onChange={(e) => setValue(e.target.value)}
109+
placeholder="Tag or tag1,tag2,..."
110+
size="large"
111+
/>
112+
<Button
113+
size="large"
114+
style={{ height: "41px" /* hack around antd bug?*/ }}
115+
disabled={!value.trim() || loading}
116+
onClick={() => {
117+
save();
118+
}}
119+
>
120+
Tag {plural(selected.size, "Account")} {loading && <Spin />}
121+
</Button>
122+
</Space.Compact>
123+
<Alert
124+
style={{ margin: "15px 0" }}
125+
type="info"
126+
message={
127+
<>The above tags will be applied to each selected account.</>
128+
}
129+
/>
130+
{loading && (
131+
<div>
132+
<Progress percent={progress} />
133+
</div>
134+
)}
135+
{error && <hr />}
136+
<ShowError error={error} setError={setError} />
137+
</div>
138+
</Modal>
139+
);
140+
}

src/packages/frontend/frame-editors/crm-editor/views/view-menu/top.tsx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useSelected } from "../use-selection";
66
import { plural } from "@cocalc/util/misc";
77
import { useState } from "react";
88
import Export from "./export";
9+
import TagAccounts from "./tag-accounts";
910
import { capitalize } from "@cocalc/util/misc";
1011

1112
export default function TopMenu({
@@ -19,9 +20,10 @@ export default function TopMenu({
1920
data,
2021
title,
2122
primaryKey,
23+
refresh,
2224
}) {
2325
const [modal, setModal] = useState<
24-
"csv-export" | "json-export" | "not-implemented" | null
26+
"csv-export" | "json-export" | "not-implemented" | "tag-accounts" | null
2527
>(null);
2628
const selected = useSelected({ id });
2729
const numSelected = selected?.size ?? 0;
@@ -117,7 +119,7 @@ export default function TopMenu({
117119
? "Export CSV (select some records)"
118120
: `Export ${selected?.size ?? 0} ${plural(
119121
numSelected,
120-
"record"
122+
"record",
121123
)} to CSV...`,
122124
key: "csv-export",
123125
},
@@ -129,7 +131,7 @@ export default function TopMenu({
129131
? "Export JSON (select some records)"
130132
: `Export ${selected?.size ?? 0} ${plural(
131133
numSelected,
132-
"record"
134+
"record",
133135
)} to JSON...`,
134136
key: "json-export",
135137
},
@@ -138,6 +140,28 @@ export default function TopMenu({
138140
],
139141
},
140142
];
143+
if (dbtable == "crm_accounts") {
144+
items[0].children.push({
145+
type: "group",
146+
label: (
147+
<Divider>
148+
<Icon name="tags-outlined" style={{ marginRight: "10px" }} /> Tag
149+
Accounts
150+
</Divider>
151+
),
152+
children: [
153+
{
154+
icon: <Icon name="tags-outlined" />,
155+
disabled: primaryKey != null && numSelected == 0,
156+
label:
157+
numSelected == 0
158+
? "Tag Accounts (select some records)"
159+
: `Tag ${selected?.size ?? 0} ${plural(numSelected, "Account")}...`,
160+
key: "tag-accounts",
161+
},
162+
],
163+
});
164+
}
141165
return (
142166
<>
143167
{modal == "csv-export" && (primaryKey == null || selected) && (
@@ -162,6 +186,17 @@ export default function TopMenu({
162186
primaryKey={primaryKey}
163187
/>
164188
)}
189+
{modal == "tag-accounts" && (primaryKey == null || selected) && (
190+
<TagAccounts
191+
title={title}
192+
onClose={() => setModal(null)}
193+
selected={selected}
194+
data={data}
195+
columns={columns}
196+
primaryKey={primaryKey}
197+
refresh={refresh}
198+
/>
199+
)}
165200
{modal == "not-implemented" && (
166201
<Modal
167202
open
@@ -183,6 +218,7 @@ export default function TopMenu({
183218
switch (key) {
184219
case "csv-export":
185220
case "json-export":
221+
case "tag-accounts":
186222
setModal(key);
187223
break;
188224
default:

0 commit comments

Comments
 (0)