@servicetitan/marketing-ui 1.10.0 → 1.13.0
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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/ads-texts.d.ts +1 -1
- package/dist/utils/ads-texts.d.ts.map +1 -1
- package/dist/utils/ads-texts.js +1 -0
- package/dist/utils/ads-texts.js.map +1 -1
- package/dist/utils/format-big-numbers.d.ts +2 -0
- package/dist/utils/format-big-numbers.d.ts.map +1 -0
- package/dist/utils/format-big-numbers.js +17 -0
- package/dist/utils/format-big-numbers.js.map +1 -0
- package/dist/utils/helpers.d.ts +15 -0
- package/dist/utils/helpers.d.ts.map +1 -1
- package/dist/utils/helpers.js +41 -1
- package/dist/utils/helpers.js.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +1 -0
- package/src/utils/__tests__/format-big-numbers.test.ts +17 -0
- package/src/utils/__tests__/helpers.test.ts +31 -0
- package/src/utils/ads-texts.tsx +3 -0
- package/src/utils/format-big-numbers.ts +15 -0
- package/src/utils/helpers.ts +43 -0
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sCAAsC,CAAC;AACrD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0CAA0C,CAAC;AAEzD,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,sCAAsC,CAAC;AACrD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kCAAkC,CAAC;AACjD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0CAA0C,CAAC;AAEzD,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC;AAChC,cAAc,4BAA4B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -21,4 +21,5 @@ __exportStar(require("./utils/formatters"), exports);
|
|
|
21
21
|
__exportStar(require("./utils/string-case"), exports);
|
|
22
22
|
__exportStar(require("./utils/use-client-rect"), exports);
|
|
23
23
|
__exportStar(require("./utils/helpers"), exports);
|
|
24
|
+
__exportStar(require("./utils/format-big-numbers"), exports);
|
|
24
25
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8DAA4C;AAC5C,uEAAqD;AACrD,4DAA0C;AAC1C,mEAAiD;AACjD,4DAA0C;AAC1C,2EAAyD;AAEzD,oDAAkC;AAClC,qDAAmC;AACnC,sDAAoC;AACpC,0DAAwC;AACxC,kDAAgC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,8DAA4C;AAC5C,uEAAqD;AACrD,4DAA0C;AAC1C,mEAAiD;AACjD,4DAA0C;AAC1C,2EAAyD;AAEzD,oDAAkC;AAClC,qDAAmC;AACnC,sDAAoC;AACpC,0DAAwC;AACxC,kDAAgC;AAChC,6DAA2C"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare type AdsStatType = 'impressions' | 'views' | 'clicks' | 'clickRate' | 'conversionRate' | 'closeRate' | 'sessions' | 'conversions' | 'transactions' | 'bookedJobs' | 'ranJobs' | 'ranRate' | 'leads' | 'leadRate' | 'completedJobs' | 'soldJobs' | 'revenue' | 'marketingRevenue' | 'expenseToIncomeRatio' | 'costPerBookedJob' | 'costPerLead' | 'costPerRanJob' | 'costPerSoldJob' | 'costPerConversion' | 'cost' | 'averageCostPerClick' | 'averageJobValue' | 'roi' | 'bookingRate' | 'opportunityRate' | 'newCustomerLeads' | 'newCustomerLeadRate' | 'ranJobsFromMarketing' | 'existingCustomerLeads';
|
|
1
|
+
export declare type AdsStatType = 'impressions' | 'views' | 'clicks' | 'clickRate' | 'conversionRate' | 'closeRate' | 'sessions' | 'conversions' | 'allConversions' | 'transactions' | 'bookedJobs' | 'ranJobs' | 'ranRate' | 'leads' | 'leadRate' | 'completedJobs' | 'soldJobs' | 'revenue' | 'marketingRevenue' | 'expenseToIncomeRatio' | 'costPerBookedJob' | 'costPerLead' | 'costPerRanJob' | 'costPerSoldJob' | 'costPerConversion' | 'cost' | 'averageCostPerClick' | 'averageJobValue' | 'roi' | 'bookingRate' | 'opportunityRate' | 'newCustomerLeads' | 'newCustomerLeadRate' | 'ranJobsFromMarketing' | 'existingCustomerLeads';
|
|
2
2
|
export declare const adsStatDescriptions: Record<AdsStatType, string>;
|
|
3
3
|
//# sourceMappingURL=ads-texts.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ads-texts.d.ts","sourceRoot":"","sources":["../../src/utils/ads-texts.tsx"],"names":[],"mappings":"AAAA,oBAAY,WAAW,GACjB,aAAa,GACb,OAAO,GACP,QAAQ,GACR,WAAW,GACX,gBAAgB,GAChB,WAAW,GACX,UAAU,GACV,aAAa,GACb,cAAc,GACd,YAAY,GACZ,SAAS,GACT,SAAS,GACT,OAAO,GACP,UAAU,GACV,eAAe,GACf,UAAU,GACV,SAAS,GACT,kBAAkB,GAClB,sBAAsB,GACtB,kBAAkB,GAClB,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,mBAAmB,GACnB,MAAM,GACN,qBAAqB,GACrB,iBAAiB,GACjB,KAAK,GACL,aAAa,GACb,iBAAiB,GACjB,kBAAkB,GAClB,qBAAqB,GACrB,sBAAsB,GACtB,uBAAuB,CAAC;AAE9B,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"ads-texts.d.ts","sourceRoot":"","sources":["../../src/utils/ads-texts.tsx"],"names":[],"mappings":"AAAA,oBAAY,WAAW,GACjB,aAAa,GACb,OAAO,GACP,QAAQ,GACR,WAAW,GACX,gBAAgB,GAChB,WAAW,GACX,UAAU,GACV,aAAa,GACb,gBAAgB,GAChB,cAAc,GACd,YAAY,GACZ,SAAS,GACT,SAAS,GACT,OAAO,GACP,UAAU,GACV,eAAe,GACf,UAAU,GACV,SAAS,GACT,kBAAkB,GAClB,sBAAsB,GACtB,kBAAkB,GAClB,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,mBAAmB,GACnB,MAAM,GACN,qBAAqB,GACrB,iBAAiB,GACjB,KAAK,GACL,aAAa,GACb,iBAAiB,GACjB,kBAAkB,GAClB,qBAAqB,GACrB,sBAAsB,GACtB,uBAAuB,CAAC;AAE9B,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAqD3D,CAAC"}
|
package/dist/utils/ads-texts.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.adsStatDescriptions = {
|
|
|
7
7
|
clicks: 'The number of times an ad was clicked, taking a searcher to a landing page',
|
|
8
8
|
sessions: "The number of times your website registered a visitor. This can differ from clicks because clicks can include clicks on extensions on the ad that don't lead to the website, like call extensions. Also, some clickers immediately leave the website before the website can register a session",
|
|
9
9
|
conversions: 'The number of people who took a predesignated action resulting from a marketing campaign. This typically includes Phone Calls, Form Submissions, Appointments Booked, and Live Chats',
|
|
10
|
+
allConversions: 'Every conversion reported by Google Ads, including those that are marked to NOT be included in the Conversions column',
|
|
10
11
|
transactions: 'The number of completed jobs where revenue was generated',
|
|
11
12
|
leads: 'Phone Calls that are longer than 60 seconds and not dismissed, plus online bookings and manual jobs',
|
|
12
13
|
ranJobs: 'A booking where a truck was dispatched. On the marketing dashboard, we show Ran Jobs that have marketing attribution',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ads-texts.js","sourceRoot":"","sources":["../../src/utils/ads-texts.tsx"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"ads-texts.js","sourceRoot":"","sources":["../../src/utils/ads-texts.tsx"],"names":[],"mappings":";;;AAqCa,QAAA,mBAAmB,GAAgC;IAC5D,WAAW,EAAE,oCAAoC;IACjD,KAAK,EAAE,2HAA2H;IAClI,MAAM,EAAE,4EAA4E;IACpF,QAAQ,EACJ,gSAAgS;IACpS,WAAW,EACP,sLAAsL;IAC1L,cAAc,EACV,uHAAuH;IAC3H,YAAY,EAAE,0DAA0D;IACxE,KAAK,EAAE,qGAAqG;IAC5G,OAAO,EACH,sHAAsH;IAC1H,OAAO,EAAE,mDAAmD;IAC5D,UAAU,EAAE,4CAA4C;IACxD,aAAa,EAAE,6CAA6C;IAC5D,QAAQ,EACJ,kIAAkI;IACtI,OAAO,EAAE,uEAAuE;IAChF,gBAAgB,EACZ,wFAAwF;IAC5F,oBAAoB,EAChB,2HAA2H;IAC/H,WAAW,EAAE,0CAA0C;IACvD,gBAAgB,EAAE,gDAAgD;IAClE,aAAa,EACT,iGAAiG;IACrG,cAAc,EAAE,mDAAmD;IACnE,iBAAiB,EAAE,0DAA0D;IAC7E,mBAAmB,EACf,8JAA8J;IAClK,IAAI,EAAE,2DAA2D;IAEjE,SAAS,EAAE,kEAAkE;IAC7E,cAAc,EACV,wLAAwL;IAC5L,SAAS,EACL,2LAA2L;IAC/L,QAAQ,EACJ,iJAAiJ;IACrJ,eAAe,EAAE,mDAAmD;IACpE,GAAG,EAAE,+JAA+J;IACpK,WAAW,EAAE,qDAAqD;IAClE,eAAe,EAAE,4DAA4D;IAC7E,gBAAgB,EACZ,8GAA8G;IAClH,mBAAmB,EACf,8KAA8K;IAClL,qBAAqB,EACjB,mGAAmG;IACvG,oBAAoB,EAChB,2FAA2F;CAClG,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-big-numbers.d.ts","sourceRoot":"","sources":["../../src/utils/format-big-numbers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,QAAS,MAAM,yBAAyB,MAAM,WAczE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatBigNumber = void 0;
|
|
4
|
+
const formatBigNumber = (num, maximumFractionDigits) => {
|
|
5
|
+
let formatedNum = num;
|
|
6
|
+
const ranks = ['', 'K', 'M'];
|
|
7
|
+
let rankIndex = 0;
|
|
8
|
+
while (formatedNum > 10000 && rankIndex < ranks.length - 1) {
|
|
9
|
+
formatedNum /= 1000;
|
|
10
|
+
rankIndex++;
|
|
11
|
+
}
|
|
12
|
+
return (formatedNum.toLocaleString(undefined, {
|
|
13
|
+
maximumFractionDigits,
|
|
14
|
+
}) + ranks[rankIndex]);
|
|
15
|
+
};
|
|
16
|
+
exports.formatBigNumber = formatBigNumber;
|
|
17
|
+
//# sourceMappingURL=format-big-numbers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-big-numbers.js","sourceRoot":"","sources":["../../src/utils/format-big-numbers.ts"],"names":[],"mappings":";;;AAAO,MAAM,eAAe,GAAG,CAAC,GAAW,EAAE,qBAA6B,EAAE,EAAE;IAC1E,IAAI,WAAW,GAAG,GAAG,CAAC;IACtB,MAAM,KAAK,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAC7B,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,OAAO,WAAW,GAAG,KAAK,IAAI,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;QACxD,WAAW,IAAI,IAAI,CAAC;QACpB,SAAS,EAAE,CAAC;KACf;IAED,OAAO,CACH,WAAW,CAAC,cAAc,CAAC,SAAS,EAAE;QAClC,qBAAqB;KACxB,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,CACxB,CAAC;AACN,CAAC,CAAC;AAdW,QAAA,eAAe,mBAc1B"}
|
package/dist/utils/helpers.d.ts
CHANGED
|
@@ -1,2 +1,17 @@
|
|
|
1
1
|
export declare const keys: <T extends Record<string, any>>(obj: T) => (keyof T)[];
|
|
2
|
+
/**
|
|
3
|
+
* Searches for words (divided by white space) in the string,
|
|
4
|
+
* i.e `findSubstrIndexes('91203 - Glendale, CA', 'glen 912')`
|
|
5
|
+
* returns `[
|
|
6
|
+
* { start: 0, end: 3 },
|
|
7
|
+
* { start: 8, end: 12 },
|
|
8
|
+
* ]`
|
|
9
|
+
* @param str - String to search in
|
|
10
|
+
* @param substr - Searched words, i.e 'hello world'
|
|
11
|
+
* @returns Array of start and end indexes of the words
|
|
12
|
+
*/
|
|
13
|
+
export declare const findSubstrIndexes: (str: string, substr: string) => {
|
|
14
|
+
start: number;
|
|
15
|
+
end: number;
|
|
16
|
+
}[];
|
|
2
17
|
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/utils/helpers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,wDAA2E,CAAC"}
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/utils/helpers.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,wDAA2E,CAAC;AAE7F;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,QAAS,MAAM,UAAU,MAAM;;;GA8B5D,CAAC"}
|
package/dist/utils/helpers.js
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.keys = void 0;
|
|
3
|
+
exports.findSubstrIndexes = exports.keys = void 0;
|
|
4
4
|
const keys = (obj) => Object.keys(obj);
|
|
5
5
|
exports.keys = keys;
|
|
6
|
+
/**
|
|
7
|
+
* Searches for words (divided by white space) in the string,
|
|
8
|
+
* i.e `findSubstrIndexes('91203 - Glendale, CA', 'glen 912')`
|
|
9
|
+
* returns `[
|
|
10
|
+
* { start: 0, end: 3 },
|
|
11
|
+
* { start: 8, end: 12 },
|
|
12
|
+
* ]`
|
|
13
|
+
* @param str - String to search in
|
|
14
|
+
* @param substr - Searched words, i.e 'hello world'
|
|
15
|
+
* @returns Array of start and end indexes of the words
|
|
16
|
+
*/
|
|
17
|
+
const findSubstrIndexes = (str, substr) => {
|
|
18
|
+
const searchWords = substr.split(' ');
|
|
19
|
+
const realSearchWords = searchWords.filter(word => word !== '');
|
|
20
|
+
const indexes = realSearchWords.map(word => {
|
|
21
|
+
const index = str.toLowerCase().indexOf(word.toLowerCase());
|
|
22
|
+
return {
|
|
23
|
+
start: index,
|
|
24
|
+
end: index + word.length,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
const realIndexes = indexes
|
|
28
|
+
.filter(index => index.start !== -1)
|
|
29
|
+
.sort((indexA, indexB) => indexA.start - indexB.start);
|
|
30
|
+
const realRanges = [];
|
|
31
|
+
if (realIndexes.length >= 1) {
|
|
32
|
+
realRanges.push(realIndexes[0]);
|
|
33
|
+
for (let i = 1; i < realIndexes.length; i++) {
|
|
34
|
+
const lastRealRange = realRanges[realRanges.length - 1];
|
|
35
|
+
if (realIndexes[i].start < lastRealRange.end) {
|
|
36
|
+
lastRealRange.end = realIndexes[i].end;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
realRanges.push(realIndexes[i]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return realRanges;
|
|
44
|
+
};
|
|
45
|
+
exports.findSubstrIndexes = findSubstrIndexes;
|
|
6
46
|
//# sourceMappingURL=helpers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/utils/helpers.ts"],"names":[],"mappings":";;;AAAO,MAAM,IAAI,GAAG,CAAgC,GAAM,EAAe,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAAhF,QAAA,IAAI,QAA4E"}
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/utils/helpers.ts"],"names":[],"mappings":";;;AAAO,MAAM,IAAI,GAAG,CAAgC,GAAM,EAAe,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAAhF,QAAA,IAAI,QAA4E;AAE7F;;;;;;;;;;GAUG;AACI,MAAM,iBAAiB,GAAG,CAAC,GAAW,EAAE,MAAc,EAAE,EAAE;IAC7D,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtC,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;IAChE,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;QACvC,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAC5D,OAAO;YACH,KAAK,EAAE,KAAK;YACZ,GAAG,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM;SAC3B,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,OAAO;SACtB,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC;SACnC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE3D,MAAM,UAAU,GAAG,EAAE,CAAC;IAEtB,IAAI,WAAW,CAAC,MAAM,IAAI,CAAC,EAAE;QACzB,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;QAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACzC,MAAM,aAAa,GAAG,UAAU,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACxD,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,aAAa,CAAC,GAAG,EAAE;gBAC1C,aAAa,CAAC,GAAG,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;aAC1C;iBAAM;gBACH,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;aACnC;SACJ;KACJ;IAED,OAAO,UAAU,CAAC;AACtB,CAAC,CAAC;AA9BW,QAAA,iBAAiB,qBA8B5B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/marketing-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Marketing UI component and utils",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"main": "./dist/index.js",
|
|
11
11
|
"typings": "./dist/index.d.ts",
|
|
12
|
+
"sideEffects": false,
|
|
12
13
|
"files": [
|
|
13
14
|
"dist",
|
|
14
15
|
"src"
|
|
@@ -50,5 +51,5 @@
|
|
|
50
51
|
"less": true,
|
|
51
52
|
"webpack": false
|
|
52
53
|
},
|
|
53
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "b4fe44f5c84b2009399239f3973473fffd0e9346"
|
|
54
55
|
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { formatBigNumber } from '../format-big-numbers';
|
|
2
|
+
|
|
3
|
+
test('formatBigNumber', () => {
|
|
4
|
+
expect(formatBigNumber(12.333333, 0)).toEqual('12');
|
|
5
|
+
expect(formatBigNumber(12000.333333, 0)).toEqual('12K');
|
|
6
|
+
expect(formatBigNumber(12000000.333333, 0)).toEqual('12M');
|
|
7
|
+
expect(formatBigNumber(12.333333, 2)).toEqual(
|
|
8
|
+
/*
|
|
9
|
+
* I know it's a bad practice to compare like this
|
|
10
|
+
* but I don't want to use preset culture that I get from backend side
|
|
11
|
+
* I want to define culture on the user side
|
|
12
|
+
*/
|
|
13
|
+
(12.333333).toLocaleString(undefined, {
|
|
14
|
+
maximumFractionDigits: 2,
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { findSubstrIndexes } from '../helpers';
|
|
2
|
+
|
|
3
|
+
describe('[Marketing] helpers', () => {
|
|
4
|
+
test('findSubstrIndexes works well', () => {
|
|
5
|
+
expect(findSubstrIndexes('91203 - Glendale, CA', '912')).toEqual([{ start: 0, end: 3 }]);
|
|
6
|
+
expect(findSubstrIndexes('91203 - Glendale, CA', '912 ')).toEqual([{ start: 0, end: 3 }]);
|
|
7
|
+
expect(findSubstrIndexes('91203 - Glendale, CA', ' 912 ')).toEqual([{ start: 0, end: 3 }]);
|
|
8
|
+
expect(findSubstrIndexes('91203 - Glendale, CA', '912 glen')).toEqual([
|
|
9
|
+
{ start: 0, end: 3 },
|
|
10
|
+
{ start: 8, end: 12 },
|
|
11
|
+
]);
|
|
12
|
+
expect(findSubstrIndexes('91203 - Glendale, CA', 'glen 912')).toEqual([
|
|
13
|
+
{ start: 0, end: 3 },
|
|
14
|
+
{ start: 8, end: 12 },
|
|
15
|
+
]);
|
|
16
|
+
expect(findSubstrIndexes('91203 - Glendale, CA', 'glen lend 912')).toEqual([
|
|
17
|
+
{ start: 0, end: 3 },
|
|
18
|
+
{ start: 8, end: 13 },
|
|
19
|
+
]);
|
|
20
|
+
expect(findSubstrIndexes('91203 - Glendale, CA', 'lend glen 912')).toEqual([
|
|
21
|
+
{ start: 0, end: 3 },
|
|
22
|
+
{ start: 8, end: 13 },
|
|
23
|
+
]);
|
|
24
|
+
expect(findSubstrIndexes('91203 - Glendale, CA', 'glen glen 912')).toEqual([
|
|
25
|
+
{ start: 0, end: 3 },
|
|
26
|
+
{ start: 8, end: 12 },
|
|
27
|
+
]);
|
|
28
|
+
expect(findSubstrIndexes('91203 - Glendale, CA', '')).toEqual([]);
|
|
29
|
+
expect(findSubstrIndexes('91203 - Glendale, CA', 'blabla')).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
});
|
package/src/utils/ads-texts.tsx
CHANGED
|
@@ -7,6 +7,7 @@ export type AdsStatType =
|
|
|
7
7
|
| 'closeRate'
|
|
8
8
|
| 'sessions'
|
|
9
9
|
| 'conversions'
|
|
10
|
+
| 'allConversions'
|
|
10
11
|
| 'transactions'
|
|
11
12
|
| 'bookedJobs'
|
|
12
13
|
| 'ranJobs'
|
|
@@ -42,6 +43,8 @@ export const adsStatDescriptions: Record<AdsStatType, string> = {
|
|
|
42
43
|
"The number of times your website registered a visitor. This can differ from clicks because clicks can include clicks on extensions on the ad that don't lead to the website, like call extensions. Also, some clickers immediately leave the website before the website can register a session",
|
|
43
44
|
conversions:
|
|
44
45
|
'The number of people who took a predesignated action resulting from a marketing campaign. This typically includes Phone Calls, Form Submissions, Appointments Booked, and Live Chats',
|
|
46
|
+
allConversions:
|
|
47
|
+
'Every conversion reported by Google Ads, including those that are marked to NOT be included in the Conversions column',
|
|
45
48
|
transactions: 'The number of completed jobs where revenue was generated',
|
|
46
49
|
leads: 'Phone Calls that are longer than 60 seconds and not dismissed, plus online bookings and manual jobs',
|
|
47
50
|
ranJobs:
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const formatBigNumber = (num: number, maximumFractionDigits: number) => {
|
|
2
|
+
let formatedNum = num;
|
|
3
|
+
const ranks = ['', 'K', 'M'];
|
|
4
|
+
let rankIndex = 0;
|
|
5
|
+
while (formatedNum > 10000 && rankIndex < ranks.length - 1) {
|
|
6
|
+
formatedNum /= 1000;
|
|
7
|
+
rankIndex++;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
formatedNum.toLocaleString(undefined, {
|
|
12
|
+
maximumFractionDigits,
|
|
13
|
+
}) + ranks[rankIndex]
|
|
14
|
+
);
|
|
15
|
+
};
|
package/src/utils/helpers.ts
CHANGED
|
@@ -1 +1,44 @@
|
|
|
1
1
|
export const keys = <T extends Record<string, any>>(obj: T): (keyof T)[] => Object.keys(obj);
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Searches for words (divided by white space) in the string,
|
|
5
|
+
* i.e `findSubstrIndexes('91203 - Glendale, CA', 'glen 912')`
|
|
6
|
+
* returns `[
|
|
7
|
+
* { start: 0, end: 3 },
|
|
8
|
+
* { start: 8, end: 12 },
|
|
9
|
+
* ]`
|
|
10
|
+
* @param str - String to search in
|
|
11
|
+
* @param substr - Searched words, i.e 'hello world'
|
|
12
|
+
* @returns Array of start and end indexes of the words
|
|
13
|
+
*/
|
|
14
|
+
export const findSubstrIndexes = (str: string, substr: string) => {
|
|
15
|
+
const searchWords = substr.split(' ');
|
|
16
|
+
const realSearchWords = searchWords.filter(word => word !== '');
|
|
17
|
+
const indexes = realSearchWords.map(word => {
|
|
18
|
+
const index = str.toLowerCase().indexOf(word.toLowerCase());
|
|
19
|
+
return {
|
|
20
|
+
start: index,
|
|
21
|
+
end: index + word.length,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const realIndexes = indexes
|
|
26
|
+
.filter(index => index.start !== -1)
|
|
27
|
+
.sort((indexA, indexB) => indexA.start - indexB.start);
|
|
28
|
+
|
|
29
|
+
const realRanges = [];
|
|
30
|
+
|
|
31
|
+
if (realIndexes.length >= 1) {
|
|
32
|
+
realRanges.push(realIndexes[0]);
|
|
33
|
+
for (let i = 1; i < realIndexes.length; i++) {
|
|
34
|
+
const lastRealRange = realRanges[realRanges.length - 1];
|
|
35
|
+
if (realIndexes[i].start < lastRealRange.end) {
|
|
36
|
+
lastRealRange.end = realIndexes[i].end;
|
|
37
|
+
} else {
|
|
38
|
+
realRanges.push(realIndexes[i]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return realRanges;
|
|
44
|
+
};
|