diff --git a/assets/tasksManager.js b/assets/tasksManager.js index 964cbe9..95d2918 100644 --- a/assets/tasksManager.js +++ b/assets/tasksManager.js @@ -1,9 +1,17 @@ "use strict"; - +//#region Variables const msPerH = 3600000; const msPerD = msPerH * 24; const boardId = "3478645467"; const mondayApiUrl = "https://api.monday.com/v2"; +let headers = { + 'Content-Type': 'application/json', + 'Referer': '', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"Windows"', + 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' +}; const weekday = [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ]; @@ -12,18 +20,86 @@ const columnRenames = { "estado": "status", "label": "frequency", "label9": "house", - "numbers": "duration", + "numbers": "dur", "people": "assigned", - "status_1": "category", + "status_1": "cat", "subitems": "subitems", "text": "comments" }; +function createTreeMap(dataCategoriesAndValues, width, height, customColors) { + const treeMap = { + "name": "tasks", + "children": dataCategoriesAndValues + }; + // @ts-ignore + const root = d3.treemap().tile(d3.treemapSquarify) + .size([width, height]) + .padding(1) + .round(true) + // @ts-ignore + (d3.hierarchy(treeMap) + .sum(d => d.value) + .sort((a, b) => b.value - a.value)); + + // @ts-ignore Create the SVG container. + const svg = d3.create("svg") + .attr("viewBox", [0, 0, width, height]) + .attr("width", width) + .attr("height", height) + .attr("style", "font: bold 14px sans-serif; height: auto; max-width: 100%;"); + // Add a cell for each leaf of the hierarchy. + const leaf = svg.selectAll("g") + .data(root.leaves()) + .join("g") + .attr("transform", d => `translate(${d.x0},${d.y0})`); + + // @ts-ignore Append a tooltip. + const format = d3.format(",d"); + leaf.append("title") + .text(d => `${d.ancestors().reverse().map(d => d.data.name).join(".")}\n${format(d.value)}`); + + // Append a color rectangle. + leaf.append("rect") + .attr("id", (d, dIdx) => d.leafUid = `leaf${dIdx}`) + .attr("fill", d => { + while (d.depth > 1) d = d.parent; + return customColors[parseInt(d.data.name.substring(0, 1), 10)]; + }) + .attr("fill-opacity", 0.6) + .attr("width", d => d.x1 - d.x0) + .attr("height", d => d.y1 - d.y0 + 10); + + // Append a clipPath to ensure text does not overflow. + leaf.append("clipPath") + .attr("id", (d, dIdx) => d.clipUid = `clip${dIdx}`) + .append("use") + .attr("xlink:href", d => d.leafUid.href); + + // Append multiline text. The last line shows the value and has a specific formatting. + leaf.append("text") + .attr("clip-path", d => d.clipUid) + .selectAll("tspan") + .data(d => d.data.name.split(/(?=[A-Z][a-z])|\s+/g).concat(format(d.value))) + .join("tspan") + .attr("x", 3) + // @ts-ignore + // @ts-ignore + .attr("y", (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`) + // @ts-ignore + .attr("fill-opacity", (d, i, nodes) => i === nodes.length - 1 ? 0.7 : null) + .text(d => d); + const treeMapSvgObj = Object.assign(svg.node()); + return treeMapSvgObj; +} +//#endregion // @ts-ignore class tasksManager extends React.Component { + //#region Constructor and functions constructor(props) { super(props); this.state = { + dayOffsetValue: 1, getDatedMondayItemsToJson: true, lastRefreshDateTime: "undefined", lastUpdatedItem: false, @@ -36,7 +112,6 @@ class tasksManager extends React.Component { nextVI: "undefined", }; }; - addMondayMeta = (mondayTasksCols) => { const currentDate = new Date(); const aYearFromNowDt = new Date(); @@ -49,7 +124,7 @@ class tasksManager extends React.Component { item["h_diff"] = +( (new Date(item["datetime"]).valueOf() - currentDate.valueOf()) / msPerH ).toFixed(2); - item["duration"] = +(parseFloat(item["duration"]).toFixed(1)); + item["dur"] = +(parseFloat(item["dur"]).toFixed(1)); item["date"] = item["datetime"].substring(0, 10); const notes = `${item["comments"]} ${item["subitems"]}`; item["notes"] = notes; @@ -58,14 +133,15 @@ class tasksManager extends React.Component { const mondayItemsJsonPayload = mondayTasksCols.map( (t) => { return { - "category": t["category"], + "cat": t["cat"], "task_name": t["task_name"], "datetime": t["datetime"], "wd": weekday[new Date(t["date"]).getDay()], - "duration": t["duration"], + "dur": t["dur"], "h_diff": t["h_diff"], "actions": t["notes"], - "task_id": t["task_id"] + "task_id": t["task_id"], + "gr": t["group"] } } ) @@ -73,12 +149,122 @@ class tasksManager extends React.Component { (a, b) => ("" + a["datetime"]).localeCompare(b["datetime"]) ).map( (t, i) => { - t["index"] = i + 1; + t["#"] = i + 1; return t; } ); }; + aggrTasksByCategory = (sortedMondayItemsJson) => { + //#region Prepare data and setState + const mondayTasksByCatDict = sortedMondayItemsJson.reduce( + (accumulator, item) => { + if (!accumulator[item["cat"]]) { + accumulator[item["cat"]] = 0; + } + accumulator[item["cat"]] += item["dur"] + return accumulator + }, {} + ); + const dataCategoriesAndValues = Object.keys(mondayTasksByCatDict).map( + (k) => { + const duration = +(mondayTasksByCatDict[k].toFixed(1)); + return { + "name": k, + "value": duration + } + } + ); + const mondayTasksDurationSum = dataCategoriesAndValues.map(t => t.value).filter(dur => dur > 0).reduce( + (accumulator, currentValue) => accumulator + currentValue, 0 + ).toFixed(1); + // @ts-ignore + this.setState({ + mondayTasksDurationSum: mondayTasksDurationSum + }); + const [width, height] = [350, 350]; + const customColors = [ + "#e15759", + "#59a14f", // ๐Ÿ  + "#9c755f", // ๐Ÿ’ฐ + "#edc949", // ๐Ÿ + "#f28e2c", // ๐Ÿšฉ๐Ÿ‡ฉ๐Ÿ‡ฐ + "#ff9da7", // ๐Ÿ”ฌ + "#af7aa1", // ๐Ÿ“บ + "#4e79a7", // ๐ŸŽฎ + "#76b7b2", // ๐ŸŒ + "#bab0ab", // โž• + "" + ]; // d3.scaleOrdinal(treeMapChildren.map(d => d.name), d3.schemeTableau10); // alternative + const color = d3.scaleOrdinal(customColors); // for bubbleChart + const bubbleChart = dataCategoriesAndValues.map(nv => { + return { + "id": `tc.${nv.name}`, + "value": 1 + nv.value + } + }); + //#endregion + //#region Plot Bubble Chart + const margin = 1; // to avoid clipping the root circle stroke + const name = d => d.id.split(".").pop(); // "Strings" of "flare.util.Strings" + const group = d => d.id.split(".")[1]; // "util" of "flare.util.Strings" + const names = d => name(d).split(/(?=[A-Z][a-z])|\s+/g); // ["Legend", "Item"] of "flare.vis.legend.LegendItems" + + // Number format for values + const format = d3.format(",d"); + + // Create layout + const pack = d3.pack() + .size([width - margin * 2, height - margin * 2]) + .padding(3); + + // Compute the hierarchy from the (flat) bubbleChart data + const root = pack(d3.hierarchy({ children: bubbleChart }) + .sum(d => d.value)); + + const svg = d3.create("svg") + .attr("width", width) + .attr("height", height) + .attr("viewBox", [-margin, -margin, width, height]) + .attr("style", "max-width: 100%; height: auto; font: 1.4em sans-serif;") + .attr("text-anchor", "middle"); + + // Place each (leaf) node according to the layout's x and y values + const node = svg.append("g") + .selectAll() + .data(root.leaves()) + .join("g") + .attr("transform", d => `translate(${d.x},${d.y})`); + node.append("title") + .text(d => `${d.data.id}\n${format(d.value)}`); + + node.append("circle") + .attr("fill-opacity", 0.7) + .attr("fill", d => customColors[group(d.data)]) + .attr("r", d => d.r); + + // Add the labels + const text = node.append("text") + .attr("clip-path", d => `circle(${d.r})`); + + // Add a tspan for each CamelCase-separated word. + text.selectAll() + .data(d => names(d.data)) + .join("tspan") + .attr("x", 0) + .attr("y", (d, i, nodes) => `${i - nodes.length / 2 + 0.35}em`) + .text(d => d); + + // Add a tspan for the node's value. + text.append("tspan") + .attr("x", 0) + .attr("y", d => `${names(d.data).length / 2 + 0.35}em`) + .attr("fill-opacity", 0.7) + .text(d => format(d.value)); + + return Object.assign(svg.node(), { scales: { color } }); + //#endregion + }; aggrTasksByDay = (sortedMondayItemsJson) => { const [ nextClimbingDay, nextVI, nextVF @@ -93,12 +279,12 @@ class tasksManager extends React.Component { t => { return { "date": t["datetime"].substring(0, 10), - "duration": t["duration"] + "dur": t["dur"] } } ); const arrNext21D = Array.from({ length: 21 }, (_, n) => n).map((n) => { - return { "date": this.offsetNDay(n), "duration": 0 } + return { "date": this.offsetNDay(n), "dur": 0 } }); sortedMondayItemsJsonWithEmptyDates = sortedMondayItemsJsonWithEmptyDates .concat(arrNext21D).sort( @@ -109,7 +295,7 @@ class tasksManager extends React.Component { if (!accumulator[item["date"]]) { accumulator[item["date"]] = 0; } - accumulator[item["date"]] += item["duration"] + accumulator[item["date"]] += item["dur"] return accumulator }, {} ); @@ -132,129 +318,26 @@ class tasksManager extends React.Component { const durStr = setDurStrAsV ? "v".repeat(totV) : `${"|".repeat(usedTime)}${".".repeat(unUsedTime)}`; + const hDiff = Math.round((( + // @ts-ignore + new Date(k) - + // @ts-ignore + new Date((new Date().toISOString().substring(0, 10))) + ) + 3.6e6) / 3.6e5) / 10 return { "date": k, "wd": wd, "dur_offs": durOffs, - "dur_str": durStr - } - } - ); - }; - - aggrTasksByCategory = (sortedMondayItemsJson) => { - const mondayTasksByCatDict = sortedMondayItemsJson.reduce( - (accumulator, item) => { - if (!accumulator[item["category"]]) { - accumulator[item["category"]] = 0; - } - accumulator[item["category"]] += item["duration"] - return accumulator - }, {} - ); - const treeMapChildren = Object.keys(mondayTasksByCatDict).map( - (k) => { - const duration = +(mondayTasksByCatDict[k].toFixed(1)); - return { - "name": k, - "value": duration + "dur_str": durStr, + "h_diff": hDiff } } ); - const mondayTasksDurationSum = treeMapChildren.map(t => t.value).filter(dur => dur > 0).reduce( - (accumulator, currentValue) => accumulator + currentValue, 0 - ).toFixed(1); - // @ts-ignore - this.setState({ - mondayTasksDurationSum: mondayTasksDurationSum - }); - const [width, height] = [350, 350]; - const treeMap = { - "name": "tasks", - "children": treeMapChildren - } - const color = [ - "#e15759", - "#59a14f", // ๐Ÿ  - "#9c755f", // ๐Ÿ’ฐ - "#edc949", // ๐Ÿ - "#f28e2c", // ๐Ÿšฉ๐Ÿ‡ฉ๐Ÿ‡ฐ - "#ff9da7", // ๐Ÿ”ฌ - "#af7aa1", // ๐Ÿ“บ - "#4e79a7", // ๐ŸŽฎ - "#76b7b2", // ๐ŸŒ - "#bab0ab", // โž• - "" - ]; // d3.scaleOrdinal(treeMapChildren.map(d => d.name), d3.schemeTableau10); - // @ts-ignore - const root = d3.treemap().tile(d3.treemapSquarify) - .size([width, height]) - .padding(1) - .round(true) - // @ts-ignore - (d3.hierarchy(treeMap) - .sum(d => d.value) - .sort((a, b) => b.value - a.value)); - - // @ts-ignore Create the SVG container. - const svg = d3.create("svg") - .attr("viewBox", [0, 0, width, height]) - .attr("width", width) - .attr("height", height) - .attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;"); - - // Add a cell for each leaf of the hierarchy. - const leaf = svg.selectAll("g") - .data(root.leaves()) - .join("g") - .attr("transform", d => `translate(${d.x0},${d.y0})`); - - // @ts-ignore Append a tooltip. - const format = d3.format(",d"); - leaf.append("title") - .text(d => `${d.ancestors().reverse().map(d => d.data.name).join(".")}\n${format(d.value)}`); - - // Append a color rectangle. - leaf.append("rect") - .attr("id", (d, dIdx) => d.leafUid = `leaf${dIdx}`) - .attr("fill", d => { - while (d.depth > 1) d = d.parent; - return color[parseInt(d.data.name.substring(0, 1), 10)]; - }) - .attr("fill-opacity", 0.6) - .attr("width", d => d.x1 - d.x0) - .attr("height", d => d.y1 - d.y0); - - // Append a clipPath to ensure text does not overflow. - leaf.append("clipPath") - .attr("id", (d, dIdx) => d.clipUid = `clip${dIdx}`) - .append("use") - .attr("xlink:href", d => d.leafUid.href); - - // Append multiline text. The last line shows the value and has a specific formatting. - leaf.append("text") - .attr("clip-path", d => d.clipUid) - .selectAll("tspan") - .data(d => d.data.name.split(/(?=[A-Z][a-z])|\s+/g).concat(format(d.value))) - .join("tspan") - .attr("x", 3) - // @ts-ignore - // @ts-ignore - .attr("y", (d, i, nodes) => `${(i === nodes.length - 1) * 0.3 + 1.1 + i * 0.9}em`) - // @ts-ignore - .attr("fill-opacity", (d, i, nodes) => i === nodes.length - 1 ? 0.7 : null) - .text(d => d); - const treeMapSvgObj = Object.assign(svg.node()); - return treeMapSvgObj; }; - getDatedMondayTasksToMultipleJson = async ( mondayKey, boardId, columnRenames ) => { - const headers = { - "Authorization": mondayKey, - "Content-Type": "application/json", - }; + headers["Authorization"] = mondayKey; const query = "boards (ids: " + boardId + ") { " + "items_page (limit: 500) { items { " + "group { title id } id name column_values { column { id } text value } " + @@ -278,7 +361,9 @@ class tasksManager extends React.Component { mondayItemsRawJson["data"]["boards"][0]["items_page"]["items"].map( (rawItem, _rawItemIdx) => { const taskIds = { - "task_id": rawItem["id"], "task_name": rawItem["name"] + "task_id": rawItem["id"], + "task_name": rawItem["name"], + "group": rawItem["group"]["title"] }; mondayTasksCols.push(taskIds); rawItemIdx = _rawItemIdx; @@ -302,7 +387,6 @@ class tasksManager extends React.Component { }); return sortedMondayItemsJson; }; - offsetNDay = (n, dateToOffset, precision = "day") => { const dateToOffsetAsValue = dateToOffset ? new Date(dateToOffset).valueOf() : @@ -317,20 +401,16 @@ class tasksManager extends React.Component { 19 // sec ); }; - putMondayDateItem = async ( mondayKey, boardId, itemId, dateTimeToSet ) => { + headers["Authorization"] = mondayKey; const query = `mutation { change_column_value ( ${"" }board_id: ${boardId}, item_id: ${itemId}, column_id: "date", value: "{${"" }\\"date\\":\\"${dateTimeToSet.substring(0, 10)}\\", ${"" }\\"time\\":\\"${dateTimeToSet.substring(11)}\\", ${"" }\\"changed_at\\":\\"${new Date().toISOString().substring(0, 19)}\\"${"" }}") { name } }`; - const headers = { - "Authorization": mondayKey, - "Content-Type": "application/json", - }; const body = JSON.stringify({ "query": query }); const mondayPutResponsePremise = await fetch( mondayApiUrl, @@ -350,21 +430,22 @@ class tasksManager extends React.Component { this.setState({ lastUpdatedItem: lastUpdatedItem }); } }; - setBgBasedOnHDiff = (taskRow) => { const hToNextDay = new Date().getHours(); - const hToNextWeek = (7 - (new Date().getDay() % 7)) * 24 + hToNextDay; + const hToNextWeek = ((8 - (new Date().getDay() % 7)) * 24) - hToNextDay; const hDiff = parseFloat(taskRow["h_diff"]); const bgRanges = [ - { "bgRange": -9e3, "bgColor": "#C669" }, // passed - { "bgRange": 0, "bgColor": "#C666" }, // now - { "bgRange": 24 - hToNextDay, "bgColor": "#C663" }, // today - { "bgRange": 48 - hToNextDay, "bgColor": "#C863" }, // tomorrow - { "bgRange": Math.max(49 - hToNextDay, hToNextWeek), "bgColor": "#CA63" }, // this week - { "bgRange": 168 + hToNextWeek, "bgColor": "#CC62" }, // next week - { "bgRange": 720 - hToNextDay, "bgColor": "#CE61" }, // this month - { "bgRange": 8760, "bgColor": "#9F61" }, // this year - { "bgRange": 9e9, "bgColor": "#BF60" } + { "bgRange": -9e3, "bgColor": "#C66D" }, // passed + { "bgRange": 0, "bgColor": "#C669" }, // now + { "bgRange": 24 - hToNextDay, "bgColor": "#C667" }, // today + { "bgRange": 48 - hToNextDay, "bgColor": "#C665" }, // tomorrow + { "bgRange": 72 - hToNextDay, "bgColor": "#C865" }, // in 2d + { "bgRange": 96 - hToNextDay, "bgColor": "#C965" }, // in 3d + { "bgRange": Math.max(96 - hToNextDay, hToNextWeek), "bgColor": "#CA65" }, // this week + { "bgRange": 168 + hToNextWeek, "bgColor": "#CC63" }, // next week + { "bgRange": 720 - hToNextDay, "bgColor": "#CE62" }, // this month + { "bgRange": 8760, "bgColor": "#9F62" }, // this year + { "bgRange": 9e9, "bgColor": "#BBFF6606" } ]; let bgColor = "#0000"; bgRanges.filter( @@ -374,18 +455,24 @@ class tasksManager extends React.Component { }); return bgColor; }; - + setDayOffsetValue = (k) => { + // @ts-ignore + this.setState({ dayOffsetValue: k }) + }; + //#endregion render() { + //#region State listeners if (this.state.getDatedMondayItemsToJson) { //@ts-ignore this.getDatedMondayTasksToMultipleJson(monday_key, boardId, columnRenames); } if (this.state.mondayTasksByCategory.length) { - const treeMapPlaceholder = document.querySelector("#treeMap"); - if (!treeMapPlaceholder) { return; } - treeMapPlaceholder.innerHTML = ""; - treeMapPlaceholder.appendChild(this.state.mondayTasksByCategory[0]); + const tasksByCategoryPlaceholder = document.querySelector("#tasksByCategory"); + if (!tasksByCategoryPlaceholder) { return; } + tasksByCategoryPlaceholder.innerHTML = ""; + tasksByCategoryPlaceholder.appendChild(this.state.mondayTasksByCategory[0]); } + //#endregion // @ts-ignore return React.createElement( "div", { @@ -407,7 +494,7 @@ class tasksManager extends React.Component { id: "lastRefreshDateTime", style: { paddingLeft: "0.3em" } }, - `Last refresh datetime: ${this.state.lastRefreshDateTime}` + `Last refresh: ${this.state.lastRefreshDateTime}` ), // @ts-ignore React.createElement( @@ -416,7 +503,7 @@ class tasksManager extends React.Component { id: "lastUpdatedItem", style: { paddingLeft: "0.3em" } }, - this.state.lastUpdatedItem && `| ${this.state.lastUpdatedItem + this.state.lastUpdatedItem && `| Last upd. item: ${this.state.lastUpdatedItem }` ), // @ts-ignore @@ -426,19 +513,38 @@ class tasksManager extends React.Component { id: "tasksDurationSum", style: { paddingLeft: "0.3em" } }, - this.state.mondayTasksDurationSum && `| Total tasks duration (h): ${this.state.mondayTasksDurationSum - }` + this.state.mondayTasksDurationSum && `| Total tasks dur: ${this.state.mondayTasksDurationSum + }h/${(this.state.mondayTasksDurationSum / 18).toFixed(1) + }w | Day(s) offset: ` + ), + // @ts-ignore + React.createElement( + "input", + { + id: "seyDayOffset", + value: this.state.dayOffsetValue, + // @ts-ignore + onChange: (e) => this.setState({ dayOffsetValue: e.target.value }), + style: { + backgroundColor: "#FFF6", + fontStyle: "italic", + fontWeight: "bold", + paddingLeft: "0.3em", + width: "3em" + } + }, + null ), // @ts-ignore React.createElement( "div", { id: "mondayTableContainer", - className: "table-container", + className: "first-flex-container-item table-container", style: { - height: "calc(100% - 320px - 4em)", - marginTop: "0.3em", - paddingTop: "0.1em" + paddingTop: "0.1em", + width: "fit-content", + maxWidth: "calc(100% - 0.8em)" } }, (Object.keys(this.state.mondayTasksCols).length && !this.state.getDatedMondayItemsToJson) ? @@ -499,41 +605,39 @@ class tasksManager extends React.Component { } }, // @ts-ignore - taskRow[taskKey], - taskKey === "actions" && [ - // @ts-ignore - React.createElement( - "img", - { - src: "../public/prioritize.png", - alt: "Prioritize", - key: `${taskRow["task_id"]}PrioritizeImg`, - className: "clickable-icon", - onClick: () => this.putMondayDateItem( - //@ts-ignore - monday_key, boardId, - taskRow["task_id"], - this.offsetNDay(-1, taskRow["datetime"], "sec") - ) - } - ), - // @ts-ignore - React.createElement( - "img", - { - src: "../public/snooze.png", - alt: "Snooze", - key: `${taskRow["task_id"]}SnoozeImg`, - className: "clickable-icon", - onClick: () => this.putMondayDateItem( - //@ts-ignore - monday_key, boardId, - taskRow["task_id"], - this.offsetNDay(1, taskRow["datetime"], "sec") - ) - } - ), - ] + React.createElement( + "img", + { + src: "../public/prioritize.png", + alt: "Prioritize", + key: `${taskRow["task_id"]}PrioritizeImg`, + className: "clickable-icon", + onClick: () => this.putMondayDateItem( + //@ts-ignore + monday_key, boardId, + taskRow["task_id"], + this.offsetNDay(-1 * this.state.dayOffsetValue, taskRow["datetime"], "sec") + ) + } + ), + // @ts-ignore + React.createElement( + "img", + { + src: "../public/snooze.png", + alt: "Snooze", + key: `${taskRow["task_id"]}SnoozeImg`, + className: "clickable-icon", + onClick: () => this.putMondayDateItem( + //@ts-ignore + monday_key, boardId, + taskRow["task_id"], + this.offsetNDay(this.state.dayOffsetValue, taskRow["datetime"], "sec") + ) + } + ), + // @ts-ignore + taskRow[taskKey] ) : // @ts-ignore taskRow[taskKey] ) @@ -566,8 +670,9 @@ class tasksManager extends React.Component { id: "mondayTasksByDayTableContainer", className: "table-container", style: { + flexGrow: 1, height: "305px", - margin: "0.3em", + margin: "0.1em", width: "fit-content" } }, @@ -575,7 +680,7 @@ class tasksManager extends React.Component { // @ts-ignore React.createElement( "table", - null, + { style: { width: "100%" } }, // @ts-ignore React.createElement( "thead", @@ -584,12 +689,14 @@ class tasksManager extends React.Component { React.createElement( "tr", null, - // @ts-ignore - Object.keys(this.state.mondayTasksByDay[0]).map(taskKey => React.createElement( - "th", - { key: `${taskKey}HeaderByDay` }, - taskKey - )) + Object.keys(this.state.mondayTasksByDay[0]).map(taskKey => + // @ts-ignore + React.createElement( + "th", + { key: `${taskKey}HeaderByDay` }, + taskKey + ) + ) ) ), // @ts-ignore @@ -599,17 +706,14 @@ class tasksManager extends React.Component { // @ts-ignore this.state.mondayTasksByDay.map((taskRow, idxRow) => React.createElement( "tr", - { key: `TaskRow${idxRow}ByDay` }, + { + key: `TaskRow${idxRow}ByDay`, + style: { backgroundColor: this.setBgBasedOnHDiff(taskRow) } + }, // @ts-ignore Object.keys(this.state.mondayTasksByDay[0]).map(taskKey => React.createElement( "td", - { - key: `${taskKey}${idxRow}TdByDay`, - style: { - fontFamily: "monospace", - fontSize: "0.7rem" - } - }, + { key: `${taskKey}${idxRow}TdByDay` }, taskRow[taskKey] )) )) @@ -626,10 +730,11 @@ class tasksManager extends React.Component { React.createElement( "div", { - id: "treeMap", + id: "tasksByCategory", style: { + // backgroundColor: "#FFF3", only like this for treeMap height: "305px", - margin: "0.3em", + margin: "0.1em", overflow: "hidden", width: "min(50%, 305px)" } @@ -640,9 +745,10 @@ class tasksManager extends React.Component { ) } } - +//#region Append to DOM const domContainer = document.querySelector("#taskManager"); //@ts-ignore const root = ReactDOM.createRoot(domContainer); // @ts-ignore root.render(React.createElement(tasksManager)); +//#endregion \ No newline at end of file diff --git a/data/bubbleChart.json b/data/bubbleChart.json new file mode 100644 index 0000000..a499cb7 --- /dev/null +++ b/data/bubbleChart.json @@ -0,0 +1,882 @@ +[ + { + "name": "flare.analytics.AgglomerativeCluster", + "value": 3938 + }, + { + "name": "flare.analytics.CommunityStructure", + "value": 3812 + }, + { + "name": "flare.analytics.HierarchicalCluster", + "value": 6714 + }, + { + "name": "flare.analytics.MergeEdge", + "value": 743 + }, + { + "name": "flare.analytics.BetweennessCentrality", + "value": 3534 + }, + { + "name": "flare.analytics.LinkDistance", + "value": 5731 + }, + { + "name": "flare.analytics.MaxFlowMinCut", + "value": 7840 + }, + { + "name": "flare.analytics.ShortestPaths", + "value": 5914 + }, + { + "name": "flare.analytics.SpanningTree", + "value": 3416 + }, + { + "name": "flare.analytics.AspectRatioBanker", + "value": 7074 + }, + { + "name": "flare.animate.Easing", + "value": 17010 + }, + { + "name": "flare.animate.FunctionSequence", + "value": 5842 + }, + { + "name": "flare.animate.ArrayInterpolator", + "value": 1983 + }, + { + "name": "flare.animate.ColorInterpolator", + "value": 2047 + }, + { + "name": "flare.animate.DateInterpolator", + "value": 1375 + }, + { + "name": "flare.animate.Interpolator", + "value": 8746 + }, + { + "name": "flare.animate.MatrixInterpolator", + "value": 2202 + }, + { + "name": "flare.animate.NumberInterpolator", + "value": 1382 + }, + { + "name": "flare.animate.ObjectInterpolator", + "value": 1629 + }, + { + "name": "flare.animate.PointInterpolator", + "value": 1675 + }, + { + "name": "flare.animate.RectangleInterpolator", + "value": 2042 + }, + { + "name": "flare.animate.ISchedulable", + "value": 1041 + }, + { + "name": "flare.animate.Parallel", + "value": 5176 + }, + { + "name": "flare.animate.Pause", + "value": 449 + }, + { + "name": "flare.animate.Scheduler", + "value": 5593 + }, + { + "name": "flare.animate.Sequence", + "value": 5534 + }, + { + "name": "flare.animate.Transition", + "value": 9201 + }, + { + "name": "flare.animate.Transitioner", + "value": 19975 + }, + { + "name": "flare.animate.TransitionEvent", + "value": 1116 + }, + { + "name": "flare.animate.Tween", + "value": 6006 + }, + { + "name": "flare.data.Converters", + "value": 721 + }, + { + "name": "flare.data.DelimitedTextConverter", + "value": 4294 + }, + { + "name": "flare.data.GraphMLConverter", + "value": 9800 + }, + { + "name": "flare.data.IDataConverter", + "value": 1314 + }, + { + "name": "flare.data.JSONConverter", + "value": 2220 + }, + { + "name": "flare.data.DataField", + "value": 1759 + }, + { + "name": "flare.data.DataSchema", + "value": 2165 + }, + { + "name": "flare.data.DataSet", + "value": 586 + }, + { + "name": "flare.data.DataSource", + "value": 3331 + }, + { + "name": "flare.data.DataTable", + "value": 772 + }, + { + "name": "flare.data.DataUtil", + "value": 3322 + }, + { + "name": "flare.display.DirtySprite", + "value": 8833 + }, + { + "name": "flare.display.LineSprite", + "value": 1732 + }, + { + "name": "flare.display.RectSprite", + "value": 3623 + }, + { + "name": "flare.display.TextSprite", + "value": 10066 + }, + { + "name": "flare.flex.FlareVis", + "value": 4116 + }, + { + "name": "flare.physics.DragForce", + "value": 1082 + }, + { + "name": "flare.physics.GravityForce", + "value": 1336 + }, + { + "name": "flare.physics.IForce", + "value": 319 + }, + { + "name": "flare.physics.NBodyForce", + "value": 10498 + }, + { + "name": "flare.physics.Particle", + "value": 2822 + }, + { + "name": "flare.physics.Simulation", + "value": 9983 + }, + { + "name": "flare.physics.Spring", + "value": 2213 + }, + { + "name": "flare.physics.SpringForce", + "value": 1681 + }, + { + "name": "flare.query.AggregateExpression", + "value": 1616 + }, + { + "name": "flare.query.And", + "value": 1027 + }, + { + "name": "flare.query.Arithmetic", + "value": 3891 + }, + { + "name": "flare.query.Average", + "value": 891 + }, + { + "name": "flare.query.BinaryExpression", + "value": 2893 + }, + { + "name": "flare.query.Comparison", + "value": 5103 + }, + { + "name": "flare.query.CompositeExpression", + "value": 3677 + }, + { + "name": "flare.query.Count", + "value": 781 + }, + { + "name": "flare.query.DateUtil", + "value": 4141 + }, + { + "name": "flare.query.Distinct", + "value": 933 + }, + { + "name": "flare.query.Expression", + "value": 5130 + }, + { + "name": "flare.query.ExpressionIterator", + "value": 3617 + }, + { + "name": "flare.query.Fn", + "value": 3240 + }, + { + "name": "flare.query.If", + "value": 2732 + }, + { + "name": "flare.query.IsA", + "value": 2039 + }, + { + "name": "flare.query.Literal", + "value": 1214 + }, + { + "name": "flare.query.Match", + "value": 3748 + }, + { + "name": "flare.query.Maximum", + "value": 843 + }, + { + "name": "flare.query.add", + "value": 593 + }, + { + "name": "flare.query.and", + "value": 330 + }, + { + "name": "flare.query.average", + "value": 287 + }, + { + "name": "flare.query.count", + "value": 277 + }, + { + "name": "flare.query.distinct", + "value": 292 + }, + { + "name": "flare.query.div", + "value": 595 + }, + { + "name": "flare.query.eq", + "value": 594 + }, + { + "name": "flare.query.fn", + "value": 460 + }, + { + "name": "flare.query.gt", + "value": 603 + }, + { + "name": "flare.query.gte", + "value": 625 + }, + { + "name": "flare.query.iff", + "value": 748 + }, + { + "name": "flare.query.isa", + "value": 461 + }, + { + "name": "flare.query.lt", + "value": 597 + }, + { + "name": "flare.query.lte", + "value": 619 + }, + { + "name": "flare.query.max", + "value": 283 + }, + { + "name": "flare.query.min", + "value": 283 + }, + { + "name": "flare.query.mod", + "value": 591 + }, + { + "name": "flare.query.mul", + "value": 603 + }, + { + "name": "flare.query.neq", + "value": 599 + }, + { + "name": "flare.query.not", + "value": 386 + }, + { + "name": "flare.query.or", + "value": 323 + }, + { + "name": "flare.query.orderby", + "value": 307 + }, + { + "name": "flare.query.range", + "value": 772 + }, + { + "name": "flare.query.select", + "value": 296 + }, + { + "name": "flare.query.stddev", + "value": 363 + }, + { + "name": "flare.query.sub", + "value": 600 + }, + { + "name": "flare.query.sum", + "value": 280 + }, + { + "name": "flare.query.update", + "value": 307 + }, + { + "name": "flare.query.variance", + "value": 335 + }, + { + "name": "flare.query.where", + "value": 299 + }, + { + "name": "flare.query.xor", + "value": 354 + }, + { + "name": "flare.query._", + "value": 264 + }, + { + "name": "flare.query.Minimum", + "value": 843 + }, + { + "name": "flare.query.Not", + "value": 1554 + }, + { + "name": "flare.query.Or", + "value": 970 + }, + { + "name": "flare.query.Query", + "value": 13896 + }, + { + "name": "flare.query.Range", + "value": 1594 + }, + { + "name": "flare.query.StringUtil", + "value": 4130 + }, + { + "name": "flare.query.Sum", + "value": 791 + }, + { + "name": "flare.query.Variable", + "value": 1124 + }, + { + "name": "flare.query.Variance", + "value": 1876 + }, + { + "name": "flare.query.Xor", + "value": 1101 + }, + { + "name": "flare.scale.IScaleMap", + "value": 2105 + }, + { + "name": "flare.scale.LinearScale", + "value": 1316 + }, + { + "name": "flare.scale.LogScale", + "value": 3151 + }, + { + "name": "flare.scale.OrdinalScale", + "value": 3770 + }, + { + "name": "flare.scale.QuantileScale", + "value": 2435 + }, + { + "name": "flare.scale.QuantitativeScale", + "value": 4839 + }, + { + "name": "flare.scale.RootScale", + "value": 1756 + }, + { + "name": "flare.scale.Scale", + "value": 4268 + }, + { + "name": "flare.scale.ScaleType", + "value": 1821 + }, + { + "name": "flare.scale.TimeScale", + "value": 5833 + }, + { + "name": "flare.util.Arrays", + "value": 8258 + }, + { + "name": "flare.util.Colors", + "value": 10001 + }, + { + "name": "flare.util.Dates", + "value": 8217 + }, + { + "name": "flare.util.Displays", + "value": 12555 + }, + { + "name": "flare.util.Filter", + "value": 2324 + }, + { + "name": "flare.util.Geometry", + "value": 10993 + }, + { + "name": "flare.util.FibonacciHeap", + "value": 9354 + }, + { + "name": "flare.util.HeapNode", + "value": 1233 + }, + { + "name": "flare.util.IEvaluable", + "value": 335 + }, + { + "name": "flare.util.IPredicate", + "value": 383 + }, + { + "name": "flare.util.IValueProxy", + "value": 874 + }, + { + "name": "flare.util.DenseMatrix", + "value": 3165 + }, + { + "name": "flare.util.IMatrix", + "value": 2815 + }, + { + "name": "flare.util.SparseMatrix", + "value": 3366 + }, + { + "name": "flare.util.Maths", + "value": 17705 + }, + { + "name": "flare.util.Orientation", + "value": 1486 + }, + { + "name": "flare.util.ColorPalette", + "value": 6367 + }, + { + "name": "flare.util.Palette", + "value": 1229 + }, + { + "name": "flare.util.ShapePalette", + "value": 2059 + }, + { + "name": "flare.util.SizePalette", + "value": 2291 + }, + { + "name": "flare.util.Property", + "value": 5559 + }, + { + "name": "flare.util.Shapes", + "value": 19118 + }, + { + "name": "flare.util.Sort", + "value": 6887 + }, + { + "name": "flare.util.Stats", + "value": 6557 + }, + { + "name": "flare.util.Strings", + "value": 22026 + }, + { + "name": "flare.vis.Axes", + "value": 1302 + }, + { + "name": "flare.vis.Axis", + "value": 24593 + }, + { + "name": "flare.vis.AxisGridLine", + "value": 652 + }, + { + "name": "flare.vis.AxisLabel", + "value": 636 + }, + { + "name": "flare.vis.CartesianAxes", + "value": 6703 + }, + { + "name": "flare.vis.AnchorControl", + "value": 2138 + }, + { + "name": "flare.vis.ClickControl", + "value": 3824 + }, + { + "name": "flare.vis.Control", + "value": 1353 + }, + { + "name": "flare.vis.ControlList", + "value": 4665 + }, + { + "name": "flare.vis.DragControl", + "value": 2649 + }, + { + "name": "flare.vis.ExpandControl", + "value": 2832 + }, + { + "name": "flare.vis.HoverControl", + "value": 4896 + }, + { + "name": "flare.vis.IControl", + "value": 763 + }, + { + "name": "flare.vis.PanZoomControl", + "value": 5222 + }, + { + "name": "flare.vis.SelectionControl", + "value": 7862 + }, + { + "name": "flare.vis.TooltipControl", + "value": 8435 + }, + { + "name": "flare.vis.Data", + "value": 20544 + }, + { + "name": "flare.vis.DataList", + "value": 19788 + }, + { + "name": "flare.vis.DataSprite", + "value": 10349 + }, + { + "name": "flare.vis.EdgeSprite", + "value": 3301 + }, + { + "name": "flare.vis.NodeSprite", + "value": 19382 + }, + { + "name": "flare.vis.ArrowType", + "value": 698 + }, + { + "name": "flare.vis.EdgeRenderer", + "value": 5569 + }, + { + "name": "flare.vis.IRenderer", + "value": 353 + }, + { + "name": "flare.vis.ShapeRenderer", + "value": 2247 + }, + { + "name": "flare.vis.ScaleBinding", + "value": 11275 + }, + { + "name": "flare.vis.Tree", + "value": 7147 + }, + { + "name": "flare.vis.TreeBuilder", + "value": 9930 + }, + { + "name": "flare.vis.DataEvent", + "value": 2313 + }, + { + "name": "flare.vis.SelectionEvent", + "value": 1880 + }, + { + "name": "flare.vis.TooltipEvent", + "value": 1701 + }, + { + "name": "flare.vis.VisualizationEvent", + "value": 1117 + }, + { + "name": "flare.vis.Legend", + "value": 20859 + }, + { + "name": "flare.vis.LegendItem", + "value": 4614 + }, + { + "name": "flare.vis.LegendRange", + "value": 10530 + }, + { + "name": "flare.vis.BifocalDistortion", + "value": 4461 + }, + { + "name": "flare.vis.Distortion", + "value": 6314 + }, + { + "name": "flare.vis.FisheyeDistortion", + "value": 3444 + }, + { + "name": "flare.vis.ColorEncoder", + "value": 3179 + }, + { + "name": "flare.vis.Encoder", + "value": 4060 + }, + { + "name": "flare.vis.PropertyEncoder", + "value": 4138 + }, + { + "name": "flare.vis.ShapeEncoder", + "value": 1690 + }, + { + "name": "flare.vis.SizeEncoder", + "value": 1830 + }, + { + "name": "flare.vis.FisheyeTreeFilter", + "value": 5219 + }, + { + "name": "flare.vis.GraphDistanceFilter", + "value": 3165 + }, + { + "name": "flare.vis.VisibilityFilter", + "value": 3509 + }, + { + "name": "flare.vis.IOperator", + "value": 1286 + }, + { + "name": "flare.vis.Labeler", + "value": 9956 + }, + { + "name": "flare.vis.RadialLabeler", + "value": 3899 + }, + { + "name": "flare.vis.StackedAreaLabeler", + "value": 3202 + }, + { + "name": "flare.vis.AxisLayout", + "value": 6725 + }, + { + "name": "flare.vis.BundledEdgeRouter", + "value": 3727 + }, + { + "name": "flare.vis.CircleLayout", + "value": 9317 + }, + { + "name": "flare.vis.CirclePackingLayout", + "value": 12003 + }, + { + "name": "flare.vis.DendrogramLayout", + "value": 4853 + }, + { + "name": "flare.vis.ForceDirectedLayout", + "value": 8411 + }, + { + "name": "flare.vis.IcicleTreeLayout", + "value": 4864 + }, + { + "name": "flare.vis.IndentedTreeLayout", + "value": 3174 + }, + { + "name": "flare.vis.Layout", + "value": 7881 + }, + { + "name": "flare.vis.NodeLinkTreeLayout", + "value": 12870 + }, + { + "name": "flare.vis.PieLayout", + "value": 2728 + }, + { + "name": "flare.vis.RadialTreeLayout", + "value": 12348 + }, + { + "name": "flare.vis.RandomLayout", + "value": 870 + }, + { + "name": "flare.vis.StackedAreaLayout", + "value": 9121 + }, + { + "name": "flare.vis.TreeMapLayout", + "value": 9191 + }, + { + "name": "flare.vis.Operator", + "value": 2490 + }, + { + "name": "flare.vis.OperatorList", + "value": 5248 + }, + { + "name": "flare.vis.OperatorSequence", + "value": 4190 + }, + { + "name": "flare.vis.OperatorSwitch", + "value": 2581 + }, + { + "name": "flare.vis.SortOperator", + "value": 2023 + }, + { + "name": "flare.vis.Visualization", + "value": 16540 + } +] \ No newline at end of file diff --git a/data/treeMap.json b/data/treeMap.json new file mode 100644 index 0000000..9cc5832 --- /dev/null +++ b/data/treeMap.json @@ -0,0 +1,38 @@ +[ + { + "name": "1.๐Ÿ ", + "value": 12.3 + }, + { + "name": "3.๐Ÿ", + "value": 8.6 + }, + { + "name": "7.๐ŸŽฎ", + "value": 2 + }, + { + "name": "2.๐Ÿ’ฐ", + "value": 8.2 + }, + { + "name": "8.๐ŸŒ", + "value": 4.5 + }, + { + "name": "9.โž•", + "value": 1.1 + }, + { + "name": "4.๐Ÿšฉ๐Ÿ‡ฉ๐Ÿ‡ฐ", + "value": 1.7 + }, + { + "name": "5.๐Ÿ”ฌ", + "value": 2.8 + }, + { + "name": "6.๐Ÿ“บ", + "value": 0.5 + } +] \ No newline at end of file diff --git a/locale/EN.js b/locale/EN.js index e3edb82..bfc485a 100644 --- a/locale/EN.js +++ b/locale/EN.js @@ -54,16 +54,11 @@ function getEnglishContents() {

About me

- -

- Technical and cloud consultant expert in ETLs, BI, web development and NLP. -

-

- Greatest experience in these clouds: Amazon Web Services (3+ years of experience), -

-

- Azure (3 years of experience) and IBM (1 year of experience within the company). -

+ + Technical and cloud consultant expert in ETLs, BI, web development and NLP.
+ Greatest experience in these clouds:
+ Amazon Web Services (3+ years of experience), + Azure (3 years of experience) and IBM (1 year of experience within the company). @@ -72,7 +67,7 @@ function getEnglishContents() { Tech Stack - + AWS EC2, Amazon Code Commit (git), Amazon Redshift (SQL), AWS Glue (pyspark), Excel (advanced), Power BI (DAX functions, R), Python (boto3, pyspark, numpy, pandas, scikit-learn...), JavaScript (experience on full stack development with React, Angular, jQuery, Leaflet, BootStrap, D3.JS, Ionic, Node.JS, Three.JS, ESLint and TypeScript), CSS(BootStrap, LeafLet, AwesomeFonts), HTML (complex structures and DOM handlers), Docker, Terraform, Regular Expressions (advanced), Azure Data Lake Analytics (U-SQL, C#), Azure Data Factory(ETLs), IBM Watson (AI solutions), Azure App Service, Microsoft LUIS. @@ -86,16 +81,16 @@ function getEnglishContents() {
AETY - (21-11 โ€“ Nowadays)
Full-stack developer, data engineer, and AWS/monday.com/JSM/Power BI technical consultant

+ (21-11 โ€“ Nowadays)
Full-stack developer, data engineer, and AWS/monday.com/JSM/Power BI technical consultant.

23-09 - 23-12
- Project: TDCยดs Smart Build solution, Role: Full-stack developer and UX implementation responsible
- Full-stack developer for TDC NET geographical auctioning Phoenix solution at the AI & Digital team.

+ Project: TDCยดs Smart Build solution, Role: Full-stack developer and UX implementation responsible.
+ Full-stack developer for TDC NET geographical auctioning Phoenix solution at the AI & Digital team.
Main tasks:
  1. E2E representation of TAB geographical data on OpenLayers and Leaflet front-end environments
  2. Front-end development of the UI for Smart Build
  3. Back-end development of the API for the AI model used to improve towers
  4. -

+ Technologies:
  1. Python: fastapi, pandas, numpy, polars, requests
  2. Javascript/Typescript: React, Chakra, OpenLayers, LeafLet, atomic design
  3. @@ -103,8 +98,9 @@ function getEnglishContents() {

22-05 - 22-08
- Project: Jira Ultimate Customizer, Role: Full-stack Developer
- Responsible for Jira's Ultimate Customizer feature to modularize custom JS/CSS and main help desk resolution management resource.

+ Project: Jira Ultimate Customizer
+ Role: Full-stack Developer
+ Responsible for Jira's Ultimate Customizer feature to modularize custom JS/CSS and main help desk resolution management resource.
Main tasks:
  1. Implementation of a feature for customizing different CSS and JS configurations for Ultimate Customizer
  2. Upgrade of @atlaskit/tabs code internals
  3. @@ -117,7 +113,7 @@ function getEnglishContents() { 22-09 - 23-04
    Project: Data automation on Dr. Oetker Home, public webpages content creation.
    - Full-stack software developer, data engineer, and AWS technical consultant
    + Role: Full-stack software developer, data engineer, and AWS technical consultant.
    Project description:
    Dr. Oetker is a producer for baking, frozen pizza, and related supermarket products, whose international webpages have been modernized and adopted a data governance infrastructure and architecture.
    Main tasks:
      @@ -130,12 +126,14 @@ function getEnglishContents() {
    1. Creation of a web-service for Dr. Oetker internal stakeholders for enabling them to modify nginx configuration for different Dr. Oetker websites.

    22-03 - 22-09
    - Project: Jira Ultimate Customizer, Role: Full-stack Developer
    - Responsible for enabling Jira's Ultimate Customizer with its correct UI/UX in signup, approvals, single portals, portals page, users login, profile page, create request, view request, requests page, and for fixing permissions error when editing login screen for the minor version 4.3.0 as well as for resolving and communicating on the resolution for all the Service Desk incidences for this App

    + Project: Jira Ultimate Customizer,
    + Role: Full-stack Developer
    + Responsible for enabling Jira's Ultimate Customizer with its correct UI/UX in signup, approvals, single portals, portals page, users login, profile page, create request, view request, requests page, and for fixing permissions error when editing login screen for the minor version 4.3.0 as well as for resolving and communicating on the resolution for all the Service Desk incidences for this App.

    22-01 - 22-03
    - Project: Integration between monday.com and KortInfo, Role: Software Developer
    - Responsible for developing an automatic real time synchronization solution for a client from monday.com to KortInfo (web map - GIS - provider)
    + Project: Integration between monday.com and KortInfo,
    + Role: Software Developer
    + Responsible for developing an automatic real time synchronization solution for a client from monday.com to KortInfo (web map - GIS - provider).

    Project description:
    The client used two systems holding the same information for different purposes (see projects on the map and manage these projects on a project management tool). They lacked an automatic way to send new projects and updates to the web map environment. The goal of the project was to implement a solution in a "serverless" environment that listens to some critical changes in monday.com.
    Main tasks:
      @@ -146,8 +144,10 @@ function getEnglishContents() {
    1. JavaScript, API REST, GraphQL, Zapier.

    21-12 - 22-01
    - Project: Business intelligence solution, Role: Software Developer, Data engineer / Consultant
    - Responsible for developing a Business Intelligence solution to provide actionable market intelligence for Atlassian's Marketplace Apps.
    + Project: Business intelligence solution,
    + Role: Software Developer, Data engineer / Consultant.
    + Responsible for developing a Business Intelligence solution to provide actionable market intelligence for Atlassian's Marketplace Apps.

    + Project description:
    There is a regular high interest in automatically gathering all public add-ons information from Atlassian Marketplace API to understand our partnership vendors better. Javier used the programming language Python in a serverless architecture for the ETL and an analytical solution for performing SQL queries over the data to identify potential vendors and add-ons.
    Main tasks:
      @@ -167,37 +167,55 @@ function getEnglishContents() {
      T-Systems Iberia (19-11 - 21-11)
      - Full-stack developer, consultant, and Power BI technician

      - - 20-12 - 21-11
      - Technical consultant for full-stack development on Pentaho CDE reports for a Smart City
      Technical consultant whose role was to implement proofs of concept for full-stack - development of personalized reports on Pentaho CDE ctools with postgresql data sources, such as CC charts, Raphaรซl.JS Sankey and network diagrams, Scatterplot and Tile Set Map Components, - and environment test cases.

      + Full-stack developer, consultant, and Power BI technician.

      21-05 - 21-10
      Full-stack developer for Ferrovial's application to handle view, monitor and send real-time traffic alerts, automation of the creation of new road trackings, and user management, all accesible within a React.JS webpage. Docker, Python, JavaScript, SQL, APIs REST, GCP's Directions API, Django, React and UX specialist.

      - 20-12 - 21-05
      + 21-05 - 21-05
      Power BI technical consultant
      Power BI teacher for T-Systems, and all other Deutsche Telekom companies.

      + + 20-12 - 21-04
      + Power BI technical consultant

      + Technical consultant for full-stack development on Pentaho CDE reports for a Smart City.
      + Technical consultant whose role was to implement proofs of concept for full-stack development of personalized reports on Pentaho CDE ctools with postgresql data sources, such as CC charts, Raphaรซl.JS Sankey and network diagrams, Scatterplot and Tile Set Map Components, + and environment test cases.

      + + Project description:
      + A big town hall publicly published its will to implement a "smart city" technology over some years, involving IoT devices around the city.
      + The nourishing of the database needed to be filtered and queried to measure specific KPIs for the local administration to decide.
      + The goal was to query, filter, and display the data to provide insights on the KPIs with Pentaho CDE (open-source web BI platform).

      + + Tasks:
      + Technical validations and proof of concepts for shaping how to achieve the requirements for the client (part-time through 2 years).
      + Being part of the elected development team to make the solution for the city for the 3 years of project duration.
      + Full-stack development of personalized reports on Pentaho CDE.
      + Test case development

      + + Technologies used:
      + Pentaho CDE, PostgreSQL, CC charts, JavaScript, SQL, Leaflet.JS maps, Raphaรซl.JS, Sankey and network diagrams.

      20-10 - 20-12
      Power BI consultant and technician
      Setting up corporate reports for T-Systems to track worker's project dedications, plans, assignments, etc. Using Azure Data Factory and DataFlows.

      20-09 - 20-10
      - Software developer and consultant
      Software developer and consultant for a multi-language automatic translator within a public sector autonomy.

      + Software developer and consultant.
      + Software developer and consultant for a multi-language automatic translator within a public sector autonomy.

      - Jun 20 - Sep 20
      + 20-06 - 20-09
      Power BI consultant and technician
      Setting up multi-client reports for T-Systems based on Azure Log Analytics Exports. Design of the Power Query data sources and of all the tab reports.

      20-05 - 20-06
      - Software Developer and Consultant
      Software developer and consultant for a multi-language automatic translator within a public sector autonomy

      + Software Developer and Consultant
      + Software developer and consultant for a multi-language automatic translator within a public sector autonomy.

      20-03 - 20-04
      Big Data technician
      Big Data technician for setting up the data scientist environment for the biggest insurance company in Spain, using Python, AWS Lambda, AWS Step Functions, AWS S3, AWS Athena, AWS IAM and remote connections.

      19-12 - 20-02
      - Technical consultant of cloud architecture, full-stack developer and big data engineer for a Smart City
      Technical consultant whose role was to implement proofs of concept of front-end developments, python backend + Technical consultant of cloud architecture, full-stack developer and big data engineer for a Smart City.
      + Technical consultant whose role was to implement proofs of concept of front-end developments, python backend connectors, third party's API managements, big data solutions (elasticsearch, BERT, Kibana), AWS Lambda functions, connections and setting up Docker containers for all the components in Docker containers for a big Smart City project.

      @@ -206,7 +224,8 @@ function getEnglishContents() { CSS3, HTML5 and JavaScript native development.

      19-11 - 19-12
      - Azure and Power BI technical consultant
      Power BI, ETL and report designer for an Azure Log Analytics tracking environment in real-state. + Azure and Power BI technical consultant.
      + Power BI, ETL and report designer for an Azure Log Analytics tracking environment in real-state. @@ -214,7 +233,7 @@ function getEnglishContents() { Open Webinars - (Collaboration, 21-10)
      + (Collaboration, 21-10).
      Teacher for the OpenWebinars courses and workshop for JavaScript design patters. @@ -230,22 +249,50 @@ function getEnglishContents() { Google Cloud and SAS. Programming with JavaScript, Node.JS, Python, U-SQL and R.

      19-06 โ€“ 19-10
      - Development, analysis and architecture for an ETL and BI project
      + Development, analysis and architecture for an ETL and BI project.
      Involving extraction of data files from different cloud sources (SAP, Salesforce, Excel documents) to Amazon S3 buckets, transformation of data with Amazon Glue (boto3 and pyspark libraries), and load in Amazon Redshift tables which are taken by Power BI for its data representation.

      - 18-11 - 19-02
      - Development and architecture in an ETL project
      Involving data extraction from both an Oracle database and Google Sheets
      spreadsheets, transformation of data with Data Lake Analytics and Azure App Service within Azure + 18-11 - 19-05
      + Development and architecture in an ETL project.
      + Involving data extraction from both an Oracle database and Google Sheets
      spreadsheets, transformation of data with Data Lake Analytics and Azure App Service within Azure Data Factory, and load of data in Data Lake Storage CSVs which are taken by Power BI for its data representation.

      - 17-12 โ€“ 18-10
      - Full-stack developer.
      Development of a geo-analytical BI web service built with R and Javascript, and chatbot development (fully development of a Node.JS chatbot for an insurance company).

      + 18-03 - 18-10
      + Full-stack Developer for a chatbot for an insurance company.

      + + Project description:
      + A big insurance company in Spain wanted to implement a chatbot on their webpage to get a lower workload on the phone for easily answered questions. The task was to create it and implement it on the bottom of their webpage (loads when the page fully renders). The development was done using Javascript, Node.js, Python, SQL, and R.

      + + Tasks:
      + Develop a fully functional and customizable chatbot (now in vivaz.com, loads when page fully renders) from a basic Microsoft template.
      + Teach an intern how to develop and coordinate tasks with him.

      + + Technologies used:
      + Amazon Web Services (AWS EC2, Amazon CodeCommit, Amazon Redshift, Amazon Glue, and Amazon S3 among others).
      + Microsoft Azure (L.U.I.S., QnA, App Service, Data Lake Storage, Data Lake Analytics).
      + IBM Watson, Oracle Intelligent Bots, Google Cloud, and SAS.
      + Programming in Javascript, Node.js, Python, U-SQL, and R.

      + + 17-12 โ€“ 18-02
      + Full-stack developer.
      + Development of a geo-analytical BI web service built with R.

      17-08 - 17-11
      - Data mining technician and artificial intelligence training.
      Development of an artificial vision software in Python to classify vehicles. + Data mining technician and machine learning.
      + Development of an artificial vision software in Python to classify vehicles.

      + Tasks:
      + Create a Python artificial intelligence able to process laser vehicle profiles.
      + Train the model with thousands of vehicles.
      + Enhance profiling detection of the vehicles.

      + + Technologies used:
      + RHEL bash scripting.
      + WinSCP.
      + Python (libraries: sci-kit-learn, pickle, matplotlib). @@ -253,7 +300,8 @@ function getEnglishContents() { PrimeX Artificial Intelligence - 16-09 - 18-09
      Artificial Intelligence developer for a chatbot
      Java programming, data mining, and AI training using different APIs from IBM Watson Cloud, Amazon Lex, and Instagram, among others. + 16-09 - 18-09
      + Artificial Intelligence developer for a chatbot
      Java programming, data mining, and AI training using different APIs from IBM Watson Cloud, Amazon Lex, and Instagram, among others. @@ -269,7 +317,8 @@ function getEnglishContents() { IBM - 16-05 - 17-05
      Pre sales and technical consultant of IBM Watson for Drug Discovery for Spain, Portugal, Greece and Israel.
      Use cases leveraging, data mining, business analyst, and social media manager for the marketing campaign. + 16-05 - 17-05
      + Pre sales and technical consultant of IBM Watson for Drug Discovery for Spain, Portugal, Greece and Israel.
      Use cases leveraging, data mining, business analyst, and social media manager for the marketing campaign. @@ -388,14 +437,31 @@ function getEnglishContents() { Danish `; - const legend = ` - Legend - web_developer - big_data_engineer - software_developer - data_analyst_power_bi - ai_data_scientist - technical_consultant - `; + const legend = ` + Legend + + big_data_engineer + + + + software_developer + + + + ai_data_scientist + + + + data_analyst_power_bi + + + + web_developer + + + + technical_consultant + + `; return [cvHeader, cvBody, legend]; } \ No newline at end of file diff --git a/pages/tasksDashboard.html b/pages/tasksDashboard.html index cde8222..db703ad 100644 --- a/pages/tasksDashboard.html +++ b/pages/tasksDashboard.html @@ -10,11 +10,33 @@ + + - -
      -
      + +
      +
      + +
      diff --git a/style/style.css b/style/style.css index 74a7a38..7f37244 100644 --- a/style/style.css +++ b/style/style.css @@ -80,6 +80,7 @@ th+th { border-left: 1px dotted #333333; } + .A4 { background-color: #FFF; border: 1px solid #11111111; @@ -174,6 +175,16 @@ th+th { width: 1.5em; } +.close-button { + background-color: #000C; + color: #FFF; + top: 0.6em; + padding: 0 0.4em; + position: absolute; + right: 0.6em; + z-index: 999; +} + .colspan4 { padding-left: 10em; } @@ -183,10 +194,15 @@ th+th { font-size: 1.5em; } +.first-flex-container-item { + height: calc(99vh - 400px); +} + .flex-container-2 { - align-items: center; + align-content: stretch; + align-items: stretch; display: flex; - flex-direction: row; + flex-wrap: wrap; gap: 0.3em; height: calc(100% - 0.3em); justify-content: center; @@ -314,6 +330,11 @@ th+th { text-align: right; } +.summary-cell { + font-size: 1.3em; + padding: 0.6em 0em 0.6em 0.6em; +} + .table-container { overflow-y: auto; position: relative; @@ -369,9 +390,55 @@ th+th { #map * { border-width: 0; - padding: 0; + max-height: 900px; +} + +#mondayTasksByDayTableContainer td { + font-family: monospace; + font-size: 0.82rem; +} + + +::-webkit-scrollbar { + width: 0.6em; + height: 0.6em; + background: transparent; +} + +::-webkit-scrollbar-thumb { + border-radius: 0.3em; + background: #666; +} + +::-webkit-scrollbar-thumb:hover { + background: #999; +} + +::-webkit-scrollbar-corner { + opacity: 0; +} + + +@media (min-height:901px) and (min-width:600) and (max-width:1199) { + .flex-container-2 { + flex-direction: column; + } + + .first-flex-container-item { + height: 30vh; + } } +@media (max-height:900px) and (max-width:599), +(max-height:900px) and (min-width:1200) { + .flex-container-2 { + flex-direction: row; + } + + .first-flex-container-item { + height: calc(100% - 320px - 4em); + } +} @media print { * { diff --git a/tests/d3PieChart.html b/tests/d3PieChart.html new file mode 100644 index 0000000..41f9138 --- /dev/null +++ b/tests/d3PieChart.html @@ -0,0 +1,163 @@ + + + + + + + + + + \ No newline at end of file