inout OK. g1gate KO
This commit is contained in:
parent
911e4c3b62
commit
5b44be85a5
@ -43,16 +43,16 @@
|
|||||||
</p>
|
</p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<section id="export-button">
|
||||||
|
<button id="exportButton">Export PDF</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section id="expenses">
|
<section id="expenses">
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="incomes">
|
<section id="incomes">
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="export-button">
|
|
||||||
<!-- Export button will be added here dynamically -->
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
Follow the money
|
Follow the money
|
||||||
@ -64,6 +64,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="index_fichiers/jspdf.min.js"></script>
|
<script src="index_fichiers/jspdf.min.js"></script>
|
||||||
|
|
||||||
<script type="module" src="index_fichiers/app.js">
|
<script type="module" src="index_fichiers/app.js">
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
|
@ -191,7 +191,7 @@ export const G12DU = (amount) => {
|
|||||||
export const query__expenses = (walletPk, minTime, size = MAX_NB_TX) => {
|
export const query__expenses = (walletPk, minTime, size = MAX_NB_TX) => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_source: ["amount", "recipient"]
|
_source: ["amount", "recipient", "comment", "medianTime"]
|
||||||
,sort: [
|
,sort: [
|
||||||
{
|
{
|
||||||
"medianTime": "desc"
|
"medianTime": "desc"
|
||||||
@ -245,7 +245,6 @@ export const fetchExpenses = async (pubkey, minTime, limit = MAX_NB_TX) => {
|
|||||||
|
|
||||||
let expensesByRecipient = {};
|
let expensesByRecipient = {};
|
||||||
let totalAmount = 0;
|
let totalAmount = 0;
|
||||||
let transactions = [];
|
|
||||||
|
|
||||||
for (const hit of data.hits.hits) {
|
for (const hit of data.hits.hits) {
|
||||||
|
|
||||||
@ -258,15 +257,13 @@ export const fetchExpenses = async (pubkey, minTime, limit = MAX_NB_TX) => {
|
|||||||
|
|
||||||
expensesByRecipient[tx.recipient] += tx.amount/100;
|
expensesByRecipient[tx.recipient] += tx.amount/100;
|
||||||
|
|
||||||
totalAmount += tx.amount;
|
// incomesByIssuer initialize table
|
||||||
|
expensesByRecipient[tx.medianTime] = tx.medianTime;
|
||||||
|
expensesByRecipient[tx.amout] = tx.amount;
|
||||||
|
expensesByRecipient[tx.recipient] = tx.recipient;
|
||||||
|
expensesByRecipient[tx.comment] = tx.comment;
|
||||||
|
|
||||||
transactions.push({
|
totalAmount += tx.amount;
|
||||||
date: new Date(tx.medianTime).toLocaleDateString(),
|
|
||||||
walletId: pubkey,
|
|
||||||
income: 0,
|
|
||||||
outcome: tx.amount,
|
|
||||||
comment: "Expense Comment", // Add your logic to get the comment
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +285,7 @@ export const fetchExpenses = async (pubkey, minTime, limit = MAX_NB_TX) => {
|
|||||||
export const query__incomes = (walletPk, minTime, size = MAX_NB_TX) => {
|
export const query__incomes = (walletPk, minTime, size = MAX_NB_TX) => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
_source: ["amount", "issuer"]
|
_source: ["amount", "issuer", "comment", "medianTime"]
|
||||||
,sort: [
|
,sort: [
|
||||||
{
|
{
|
||||||
"medianTime": "desc"
|
"medianTime": "desc"
|
||||||
@ -342,7 +339,6 @@ export const fetchIncomes = async (pubkey, minTime, limit = MAX_NB_TX) => {
|
|||||||
|
|
||||||
let incomesByIssuer = {};
|
let incomesByIssuer = {};
|
||||||
let totalAmount = 0;
|
let totalAmount = 0;
|
||||||
let transactions = [];
|
|
||||||
|
|
||||||
for (const hit of data.hits.hits) {
|
for (const hit of data.hits.hits) {
|
||||||
|
|
||||||
@ -355,15 +351,13 @@ export const fetchIncomes = async (pubkey, minTime, limit = MAX_NB_TX) => {
|
|||||||
|
|
||||||
incomesByIssuer[tx.issuer] += tx.amount/100;
|
incomesByIssuer[tx.issuer] += tx.amount/100;
|
||||||
|
|
||||||
totalAmount += tx.amount;
|
// incomesByIssuer initialize table
|
||||||
|
incomesByIssuer[tx.medianTime] = tx.medianTime;
|
||||||
|
incomesByIssuer[tx.amout] = tx.amount;
|
||||||
|
incomesByIssuer[tx.issuer] = tx.issuer;
|
||||||
|
incomesByIssuer[tx.comment] = tx.comment;
|
||||||
|
|
||||||
transactions.push({
|
totalAmount += tx.amount;
|
||||||
date: new Date(tx.medianTime).toLocaleDateString(),
|
|
||||||
walletId: pubkey,
|
|
||||||
income: tx.amount,
|
|
||||||
outcome: 0,
|
|
||||||
comment: "Income Comment", // Add your logic to get the comment
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,42 +496,6 @@ export const fetchCesiumProfiles = async (pubkeys, limit) => {
|
|||||||
throw new Error("Failed to fetch data from all nodes");
|
throw new Error("Failed to fetch data from all nodes");
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to export transactions to PDF
|
|
||||||
const exportToPDF = (transactions) => {
|
|
||||||
const pdf = new jsPDF();
|
|
||||||
pdf.text("Transaction Report", 20, 10);
|
|
||||||
|
|
||||||
// Define table columns
|
|
||||||
const columns = ["Date", "Wallet ID", "Income", "Outcome", "Comment"];
|
|
||||||
|
|
||||||
// Initialize y-position for table
|
|
||||||
let y = 20;
|
|
||||||
|
|
||||||
// Add table headers
|
|
||||||
columns.forEach((column, index) => {
|
|
||||||
pdf.text(column, 20 + index * 40, y);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Increment y for the next row
|
|
||||||
y += 10;
|
|
||||||
|
|
||||||
// Add transactions to the table
|
|
||||||
transactions.forEach((transaction) => {
|
|
||||||
pdf.text(transaction.date, 20, y);
|
|
||||||
pdf.text(transaction.walletId, 60, y);
|
|
||||||
pdf.text(transaction.income.toString(), 100, y);
|
|
||||||
pdf.text(transaction.outcome.toString(), 140, y);
|
|
||||||
pdf.text(transaction.comment, 180, y);
|
|
||||||
|
|
||||||
// Increment y for the next row
|
|
||||||
y += 10;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save the PDF
|
|
||||||
pdf.save("transaction_report.pdf");
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export const displayExpenses = (expensesByRecipient, expensesTotalAmount, recipientsCesiumProfiles, chartColors, currentPubkey, currentProfile) => {
|
export const displayExpenses = (expensesByRecipient, expensesTotalAmount, recipientsCesiumProfiles, chartColors, currentPubkey, currentProfile) => {
|
||||||
|
|
||||||
let screenElt = document.querySelector('#expenses');
|
let screenElt = document.querySelector('#expenses');
|
||||||
@ -715,6 +673,60 @@ const treemapIt = async (pubkey, minTime, maxNbTx = MAX_NB_TX) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Function to export transactions to PDF
|
||||||
|
const exportToPDF = (transactions) => {
|
||||||
|
const pdf = new jsPDF({
|
||||||
|
orientation: 'landscape', // Set orientation to landscape
|
||||||
|
unit: 'mm',
|
||||||
|
format: 'a4',
|
||||||
|
});
|
||||||
|
|
||||||
|
pdf.text("Transaction Report", 10, 10);
|
||||||
|
|
||||||
|
// Define table columns
|
||||||
|
const columns = ["Date", "Recipient", "Outcome", "Issuer", "Incoming", "Total"];
|
||||||
|
|
||||||
|
// Initialize y-position for the table
|
||||||
|
let y = 20;
|
||||||
|
|
||||||
|
// Add table headers
|
||||||
|
columns.forEach((column, index) => {
|
||||||
|
pdf.text(column, 10 + index * 40, y);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Increment y for the next row
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
// Add transactions to the table
|
||||||
|
transactions.forEach((transaction) => {
|
||||||
|
pdf.text(transaction.medianTime, 10, y);
|
||||||
|
pdf.text(transaction.recipient || "", 50, y);
|
||||||
|
pdf.text(transaction.amount, 90, y);
|
||||||
|
pdf.text(transaction.issuer || "", 130, y);
|
||||||
|
pdf.text(transaction.amount, 170, y);
|
||||||
|
|
||||||
|
// Increment y for the next row
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
// Add a new page if the table exceeds one page
|
||||||
|
if (y > 280) {
|
||||||
|
pdf.addPage();
|
||||||
|
y = 20;
|
||||||
|
|
||||||
|
// Add table headers for the new page
|
||||||
|
columns.forEach((column, index) => {
|
||||||
|
pdf.text(column, 10 + index * 40, y);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Increment y for the next row on the new page
|
||||||
|
y += 10;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the PDF
|
||||||
|
pdf.save("transaction_report.pdf");
|
||||||
|
};
|
||||||
|
|
||||||
const getPkInHash = () => {
|
const getPkInHash = () => {
|
||||||
|
|
||||||
let hash = window.location.hash;
|
let hash = window.location.hash;
|
||||||
@ -769,4 +781,50 @@ window.addEventListener('DOMContentLoaded', (loadEvent) => {
|
|||||||
console.log('dateStr : ', dateStr);
|
console.log('dateStr : ', dateStr);
|
||||||
|
|
||||||
minDateElt.value = dateStr;
|
minDateElt.value = dateStr;
|
||||||
|
|
||||||
|
// Add an event listener to the export button
|
||||||
|
const exportButton = document.getElementById('exportButton');
|
||||||
|
exportButton.addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const pubkey = document.querySelector('input[name="pubkey"]').value;
|
||||||
|
|
||||||
|
const { expensesTotalAmount, expensesByRecipient } = await fetchExpenses(pubkey, minTime, txLimit);
|
||||||
|
const { incomesTotalAmount, incomesByIssuer } = await fetchIncomes(pubkey, minTime, txLimit);
|
||||||
|
|
||||||
|
// Combine expenses and incomes into a single list ordered by date
|
||||||
|
const combinedTransactions = [];
|
||||||
|
|
||||||
|
for (const recipient in expensesByRecipient) {
|
||||||
|
combinedTransactions.push({
|
||||||
|
medianTime: expensesByRecipient[medianTime],
|
||||||
|
type: 'Expense',
|
||||||
|
amount: expensesByRecipient[amount],
|
||||||
|
recipient: expensesByRecipient[recipient],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const issuer in incomesByIssuer) {
|
||||||
|
combinedTransactions.push({
|
||||||
|
medianTime: incomesByIssuer[medianTime],
|
||||||
|
type: 'Income',
|
||||||
|
amount: incomesByIssuer[amount],
|
||||||
|
issuer: incomesByIssuer[issuer],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the transactions by date
|
||||||
|
combinedTransactions.sort((a, b) => new Date(a.medianTime) - new Date(b.medianTime));
|
||||||
|
|
||||||
|
// Create a PDF with the combined transactions
|
||||||
|
exportToPDF(combinedTransactions);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error exporting transactions to PDF: ${error}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
82
earth/inout/index.html
Normal file
82
earth/inout/index.html
Normal file
File diff suppressed because one or more lines are too long
772
earth/inout/index_fichiers/app.js
Normal file
772
earth/inout/index_fichiers/app.js
Normal file
@ -0,0 +1,772 @@
|
|||||||
|
import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
|
||||||
|
|
||||||
|
export const DU = 10.68;
|
||||||
|
export const MAX_NB_TX = 200;
|
||||||
|
let txLimit = MAX_NB_TX;
|
||||||
|
let minTime = null;
|
||||||
|
|
||||||
|
export const CESIUM_G1_NODES = [
|
||||||
|
"https://g1.data.brussels.ovh",
|
||||||
|
"https://g1.data.e-is.pro",
|
||||||
|
"https://g1.data.cuates.net",
|
||||||
|
"https://g1.data.madeirawonders.com",
|
||||||
|
|
||||||
|
// "https://g1.data.pini.fr" // Could not resolve hostname
|
||||||
|
// "https://g1.data.le-sou.org" // vide
|
||||||
|
// "https://abyayala.g1labs.net" // /g1/movement returns "404 Not Found"
|
||||||
|
// "https://g1.data.presles.fr", // CORS
|
||||||
|
// "https://cesiumplus971.dns1.us", // CORS
|
||||||
|
// "https://g1.data.mithril.re", // /g1/movement returns "403 Forbidden"
|
||||||
|
];
|
||||||
|
|
||||||
|
let chartColors = [
|
||||||
|
'#FFC431',
|
||||||
|
'#548AFF',
|
||||||
|
'#FF826E',
|
||||||
|
'#67B0C3',
|
||||||
|
'#CFD8DC',
|
||||||
|
'#5CD6B3'
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Treemap = (
|
||||||
|
// Copyright 2021-2023 Observable, Inc.
|
||||||
|
// Released under the ISC license.
|
||||||
|
// https://observablehq.com/@d3/treemap
|
||||||
|
function Treemap(data, { // data is either tabular (array of objects) or hierarchy (nested objects)
|
||||||
|
path, // as an alternative to id and parentId, returns an array identifier, imputing internal nodes
|
||||||
|
id = Array.isArray(data) ? d => d.id : null, // if tabular data, given a d in data, returns a unique identifier (string)
|
||||||
|
parentId = Array.isArray(data) ? d => d.parentId : null, // if tabular data, given a node d, returns its parent’s identifier
|
||||||
|
children, // if hierarchical data, given a d in data, returns its children
|
||||||
|
value, // given a node d, returns a quantitative value (for area encoding; null for count)
|
||||||
|
sort = (a, b) => d3.descending(a.value, b.value), // how to sort nodes prior to layout
|
||||||
|
label, // given a leaf node d, returns the name to display on the rectangle
|
||||||
|
group, // given a leaf node d, returns a categorical value (for color encoding)
|
||||||
|
title, // given a leaf node d, returns its hover text
|
||||||
|
link, // given a leaf node d, its link (if any)
|
||||||
|
linkTarget = "_blank", // the target attribute for links (if any)
|
||||||
|
tile = d3.treemapBinary, // treemap strategy
|
||||||
|
width = 640, // outer width, in pixels
|
||||||
|
height = 400, // outer height, in pixels
|
||||||
|
margin = 0, // shorthand for margins
|
||||||
|
marginTop = margin, // top margin, in pixels
|
||||||
|
marginRight = margin, // right margin, in pixels
|
||||||
|
marginBottom = margin, // bottom margin, in pixels
|
||||||
|
marginLeft = margin, // left margin, in pixels
|
||||||
|
padding = 1, // shorthand for inner and outer padding
|
||||||
|
paddingInner = padding, // to separate a node from its adjacent siblings
|
||||||
|
paddingOuter = padding, // shorthand for top, right, bottom, and left padding
|
||||||
|
paddingTop = paddingOuter, // to separate a node’s top edge from its children
|
||||||
|
paddingRight = paddingOuter, // to separate a node’s right edge from its children
|
||||||
|
paddingBottom = paddingOuter, // to separate a node’s bottom edge from its children
|
||||||
|
paddingLeft = paddingOuter, // to separate a node’s left edge from its children
|
||||||
|
round = true, // whether to round to exact pixels
|
||||||
|
colors = d3.schemeTableau10, // array of colors
|
||||||
|
zDomain, // array of values for the color scale
|
||||||
|
fill = "#ccc", // fill for node rects (if no group color encoding)
|
||||||
|
fillOpacity = group == null ? null : 0.6, // fill opacity for node rects
|
||||||
|
stroke, // stroke for node rects
|
||||||
|
strokeWidth, // stroke width for node rects
|
||||||
|
strokeOpacity, // stroke opacity for node rects
|
||||||
|
strokeLinejoin, // stroke line join for node rects
|
||||||
|
} = {}) {
|
||||||
|
|
||||||
|
// If id and parentId options are specified, or the path option, use d3.stratify
|
||||||
|
// to convert tabular data to a hierarchy; otherwise we assume that the data is
|
||||||
|
// specified as an object {children} with nested objects (a.k.a. the “flare.json”
|
||||||
|
// format), and use d3.hierarchy.
|
||||||
|
|
||||||
|
// We take special care of any node that has both a value and children, see
|
||||||
|
// https://observablehq.com/@d3/treemap-parent-with-value.
|
||||||
|
const stratify = data => (d3.stratify().path(path)(data)).each(node => {
|
||||||
|
if (node.children?.length && node.data != null) {
|
||||||
|
const child = new d3.Node(node.data);
|
||||||
|
node.data = null;
|
||||||
|
child.depth = node.depth + 1;
|
||||||
|
child.height = 0;
|
||||||
|
child.parent = node;
|
||||||
|
child.id = node.id + "/";
|
||||||
|
node.children.unshift(child);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const root = path != null ? stratify(data)
|
||||||
|
: id != null || parentId != null ? d3.stratify().id(id).parentId(parentId)(data)
|
||||||
|
: d3.hierarchy(data, children);
|
||||||
|
|
||||||
|
// Compute the values of internal nodes by aggregating from the leaves.
|
||||||
|
value == null ? root.count() : root.sum(d => Math.max(0, d ? value(d) : null));
|
||||||
|
|
||||||
|
// Prior to sorting, if a group channel is specified, construct an ordinal color scale.
|
||||||
|
const leaves = root.leaves();
|
||||||
|
const G = group == null ? null : leaves.map(d => group(d.data, d));
|
||||||
|
if (zDomain === undefined) zDomain = G;
|
||||||
|
zDomain = new d3.InternSet(zDomain);
|
||||||
|
const color = group == null ? null : d3.scaleOrdinal(zDomain, colors);
|
||||||
|
|
||||||
|
// Compute labels and titles.
|
||||||
|
const L = label == null ? null : leaves.map(d => label(d.data, d));
|
||||||
|
const T = title === undefined ? L : title == null ? null : leaves.map(d => title(d.data, d));
|
||||||
|
|
||||||
|
// Sort the leaves (typically by descending value for a pleasing layout).
|
||||||
|
if (sort != null) root.sort(sort);
|
||||||
|
|
||||||
|
// Compute the treemap layout.
|
||||||
|
d3.treemap()
|
||||||
|
.tile(tile)
|
||||||
|
.size([width - marginLeft - marginRight, height - marginTop - marginBottom])
|
||||||
|
.paddingInner(paddingInner)
|
||||||
|
.paddingTop(paddingTop)
|
||||||
|
.paddingRight(paddingRight)
|
||||||
|
.paddingBottom(paddingBottom)
|
||||||
|
.paddingLeft(paddingLeft)
|
||||||
|
.round(round)
|
||||||
|
(root);
|
||||||
|
|
||||||
|
const svg = d3.create("svg")
|
||||||
|
.attr("viewBox", [-marginLeft, -marginTop, width, height])
|
||||||
|
.attr("width", width)
|
||||||
|
.attr("height", height)
|
||||||
|
.attr("style", "max-width: 100%; height: auto; height: intrinsic;")
|
||||||
|
.attr("font-family", "sans-serif")
|
||||||
|
.attr("font-size", 10);
|
||||||
|
|
||||||
|
const node = svg.selectAll("a")
|
||||||
|
.data(leaves)
|
||||||
|
.join("a")
|
||||||
|
.attr("xlink:href", link == null ? null : (d, i) => link(d.data, d))
|
||||||
|
.attr("target", link == null ? null : linkTarget)
|
||||||
|
.attr("transform", d => `translate(${d.x0},${d.y0})`);
|
||||||
|
|
||||||
|
node.append("rect")
|
||||||
|
.attr("fill", color ? (d, i) => color(G[i]) : fill)
|
||||||
|
.attr("fill-opacity", fillOpacity)
|
||||||
|
.attr("stroke", stroke)
|
||||||
|
.attr("stroke-width", strokeWidth)
|
||||||
|
.attr("stroke-opacity", strokeOpacity)
|
||||||
|
.attr("stroke-linejoin", strokeLinejoin)
|
||||||
|
.attr("width", d => d.x1 - d.x0)
|
||||||
|
.attr("height", d => d.y1 - d.y0);
|
||||||
|
|
||||||
|
if (T) {
|
||||||
|
node.append("title").text((d, i) => T[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (L) {
|
||||||
|
// A unique identifier for clip paths (to avoid conflicts).
|
||||||
|
const uid = `O-${Math.random().toString(16).slice(2)}`;
|
||||||
|
|
||||||
|
node.append("clipPath")
|
||||||
|
.attr("id", (d, i) => `${uid}-clip-${i}`)
|
||||||
|
.append("rect")
|
||||||
|
.attr("width", d => d.x1 - d.x0)
|
||||||
|
.attr("height", d => d.y1 - d.y0);
|
||||||
|
|
||||||
|
node.append("text")
|
||||||
|
.attr("clip-path", (d, i) => `url(${new URL(`#${uid}-clip-${i}`, location)})`)
|
||||||
|
.selectAll("tspan")
|
||||||
|
.data((d, i) => `${L[i]}`.split(/\n/g))
|
||||||
|
.join("tspan")
|
||||||
|
.attr("x", 3)
|
||||||
|
.attr("y", (d, i, D) => `${(i === D.length - 1) * 0.3 + 1.1 + i * 0.9}em`)
|
||||||
|
.attr("fill-opacity", (d, i, D) => i === D.length - 1 ? 0.7 : null)
|
||||||
|
.text(d => d);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign(svg.node(), {scales: {color}});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const shuffle = (array) => {
|
||||||
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const G12DU = (amount) => {
|
||||||
|
|
||||||
|
return (Math.round(amount / DU * 100) / 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const query__expenses = (walletPk, minTime, size = MAX_NB_TX) => {
|
||||||
|
|
||||||
|
return {
|
||||||
|
_source: ["amount", "recipient"]
|
||||||
|
,sort: [
|
||||||
|
{
|
||||||
|
"medianTime": "desc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
,size: size
|
||||||
|
,query: {
|
||||||
|
bool: {
|
||||||
|
filter: [
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
"medianTime": {
|
||||||
|
gte: minTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
term: {
|
||||||
|
"issuer": walletPk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchExpenses = async (pubkey, minTime, limit = MAX_NB_TX) => {
|
||||||
|
|
||||||
|
shuffle(CESIUM_G1_NODES); // Mélanger la liste des noeuds
|
||||||
|
|
||||||
|
for (let node of CESIUM_G1_NODES) {
|
||||||
|
try {
|
||||||
|
const url = `${node}/g1/movement/_search`;
|
||||||
|
let queryBody = query__expenses(pubkey, minTime, limit);
|
||||||
|
|
||||||
|
console.log('expenses queryBody : \n', JSON.stringify(queryBody));
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(queryBody)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
console.log('node for the expenses of :\n', pubkey, '\n', node);
|
||||||
|
console.log('expenses data :\n', data);
|
||||||
|
|
||||||
|
let expensesByRecipient = {};
|
||||||
|
let totalAmount = 0;
|
||||||
|
let transactions = [];
|
||||||
|
|
||||||
|
for (const hit of data.hits.hits) {
|
||||||
|
|
||||||
|
const tx = hit._source;
|
||||||
|
|
||||||
|
if (!(tx.recipient in expensesByRecipient)) {
|
||||||
|
|
||||||
|
expensesByRecipient[tx.recipient] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
expensesByRecipient[tx.recipient] += tx.amount/100;
|
||||||
|
|
||||||
|
totalAmount += tx.amount;
|
||||||
|
|
||||||
|
transactions.push({
|
||||||
|
date: new Date(tx.medianTime).toLocaleDateString(),
|
||||||
|
walletId: pubkey,
|
||||||
|
income: 0,
|
||||||
|
outcome: tx.amount,
|
||||||
|
comment: "Expense Comment", // Add your logic to get the comment
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
totalAmount = totalAmount/100;
|
||||||
|
|
||||||
|
return {
|
||||||
|
expensesTotalAmount: totalAmount
|
||||||
|
,expensesByRecipient: expensesByRecipient
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch data from ${node}: ${error}`);
|
||||||
|
// Si une erreur se produit, passez simplement au noeud suivant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Failed to fetch data from all nodes");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const query__incomes = (walletPk, minTime, size = MAX_NB_TX) => {
|
||||||
|
|
||||||
|
return {
|
||||||
|
_source: ["amount", "issuer"]
|
||||||
|
,sort: [
|
||||||
|
{
|
||||||
|
"medianTime": "desc"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
,size: size
|
||||||
|
,query: {
|
||||||
|
bool: {
|
||||||
|
filter: [
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
"medianTime": {
|
||||||
|
gte: minTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
,{
|
||||||
|
term: {
|
||||||
|
"recipient": walletPk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchIncomes = async (pubkey, minTime, limit = MAX_NB_TX) => {
|
||||||
|
|
||||||
|
shuffle(CESIUM_G1_NODES); // Mélanger la liste des noeuds
|
||||||
|
|
||||||
|
for (let node of CESIUM_G1_NODES) {
|
||||||
|
try {
|
||||||
|
const url = `${node}/g1/movement/_search`;
|
||||||
|
let queryBody = query__incomes(pubkey, minTime, limit);
|
||||||
|
|
||||||
|
console.log('incomes queryBody : \n', JSON.stringify(queryBody));
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(queryBody)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
console.log('node for the incomes of :\n', pubkey, '\n', node);
|
||||||
|
console.log('incomes data :\n', data);
|
||||||
|
|
||||||
|
let incomesByIssuer = {};
|
||||||
|
let totalAmount = 0;
|
||||||
|
let transactions = [];
|
||||||
|
|
||||||
|
for (const hit of data.hits.hits) {
|
||||||
|
|
||||||
|
const tx = hit._source;
|
||||||
|
|
||||||
|
if (!(tx.issuer in incomesByIssuer)) {
|
||||||
|
|
||||||
|
incomesByIssuer[tx.issuer] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
incomesByIssuer[tx.issuer] += tx.amount/100;
|
||||||
|
|
||||||
|
totalAmount += tx.amount;
|
||||||
|
|
||||||
|
transactions.push({
|
||||||
|
date: new Date(tx.medianTime).toLocaleDateString(),
|
||||||
|
walletId: pubkey,
|
||||||
|
income: tx.amount,
|
||||||
|
outcome: 0,
|
||||||
|
comment: "Income Comment", // Add your logic to get the comment
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
totalAmount = totalAmount/100;
|
||||||
|
|
||||||
|
return {
|
||||||
|
incomesTotalAmount: totalAmount
|
||||||
|
,incomesByIssuer: incomesByIssuer
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch data from ${node}: ${error}`);
|
||||||
|
// Si une erreur se produit, passez simplement au noeud suivant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Failed to fetch data from all nodes");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const query__cesium_profile = (pubkey) => {
|
||||||
|
|
||||||
|
return {
|
||||||
|
_source: [
|
||||||
|
"title",
|
||||||
|
"issuer"
|
||||||
|
]
|
||||||
|
,query: {
|
||||||
|
bool: {
|
||||||
|
filter: [
|
||||||
|
{term: {"_type": "profile"}}
|
||||||
|
]
|
||||||
|
,should: [
|
||||||
|
{ term: { "issuer": pubkey } },
|
||||||
|
]
|
||||||
|
,minimum_should_match: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const query__cesium_profiles = (pubkeys, limit = 200) => {
|
||||||
|
|
||||||
|
return {
|
||||||
|
size: limit
|
||||||
|
,_source: ["title"]
|
||||||
|
,query: {
|
||||||
|
bool: {
|
||||||
|
filter: [
|
||||||
|
{term: {"_type": "profile"}}
|
||||||
|
]
|
||||||
|
,should: [
|
||||||
|
...pubkeys.map(pk => ({ term: { "issuer": pk } })),
|
||||||
|
]
|
||||||
|
,minimum_should_match: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchCesiumProfile = async (pubkey) => {
|
||||||
|
|
||||||
|
shuffle(CESIUM_G1_NODES); // Mélanger la liste des noeuds
|
||||||
|
|
||||||
|
for (let node of CESIUM_G1_NODES) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = `${node}/user/profile/_search`;
|
||||||
|
let queryBody = query__cesium_profile(pubkey);
|
||||||
|
|
||||||
|
console.log('cesium_profile queryBody : \n', JSON.stringify(queryBody));
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(queryBody)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.hits.hits[0] == undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.hits.hits[0]._source;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch data from ${node}: ${error}`);
|
||||||
|
// Si une erreur se produit, passez simplement au noeud suivant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Failed to fetch data from all nodes");
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchCesiumProfiles = async (pubkeys, limit) => {
|
||||||
|
|
||||||
|
shuffle(CESIUM_G1_NODES); // Mélanger la liste des noeuds
|
||||||
|
|
||||||
|
for (let node of CESIUM_G1_NODES) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
const url = `${node}/user/profile/_search`;
|
||||||
|
let queryBody = query__cesium_profiles(pubkeys, limit);
|
||||||
|
|
||||||
|
console.log('cesium_profiles queryBody : \n', JSON.stringify(queryBody));
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(queryBody)
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
let profiles = [];
|
||||||
|
|
||||||
|
for (const hit of data.hits.hits) {
|
||||||
|
|
||||||
|
profiles[hit._id] = {
|
||||||
|
title: hit._source.title
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return profiles;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch data from ${node}: ${error}`);
|
||||||
|
// Si une erreur se produit, passez simplement au noeud suivant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Failed to fetch data from all nodes");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to export transactions to PDF
|
||||||
|
const exportToPDF = (transactions) => {
|
||||||
|
const pdf = new jsPDF();
|
||||||
|
pdf.text("Transaction Report", 20, 10);
|
||||||
|
|
||||||
|
// Define table columns
|
||||||
|
const columns = ["Date", "Wallet ID", "Income", "Outcome", "Comment"];
|
||||||
|
|
||||||
|
// Initialize y-position for table
|
||||||
|
let y = 20;
|
||||||
|
|
||||||
|
// Add table headers
|
||||||
|
columns.forEach((column, index) => {
|
||||||
|
pdf.text(column, 20 + index * 40, y);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Increment y for the next row
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
// Add transactions to the table
|
||||||
|
transactions.forEach((transaction) => {
|
||||||
|
pdf.text(transaction.date, 20, y);
|
||||||
|
pdf.text(transaction.walletId, 60, y);
|
||||||
|
pdf.text(transaction.income.toString(), 100, y);
|
||||||
|
pdf.text(transaction.outcome.toString(), 140, y);
|
||||||
|
pdf.text(transaction.comment, 180, y);
|
||||||
|
|
||||||
|
// Increment y for the next row
|
||||||
|
y += 10;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the PDF
|
||||||
|
pdf.save("transaction_report.pdf");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const displayExpenses = (expensesByRecipient, expensesTotalAmount, recipientsCesiumProfiles, chartColors, currentPubkey, currentProfile) => {
|
||||||
|
|
||||||
|
let screenElt = document.querySelector('#expenses');
|
||||||
|
screenElt.innerHTML = '';
|
||||||
|
|
||||||
|
let currentProfileTitleElt = document.createElement('h2');
|
||||||
|
screenElt.append(currentProfileTitleElt);
|
||||||
|
let title = null;
|
||||||
|
if (currentProfile == undefined) {
|
||||||
|
title = 'Dépenses du portefeuille <code>' + currentPubkey.substr(0, 8) + '</code> = '+ expensesTotalAmount + ' Ğ1';
|
||||||
|
} else {
|
||||||
|
title = 'Dépenses de <q>' + currentProfile.title + '</q> <code>' + currentPubkey.substr(0, 8) + '</code> = '+ expensesTotalAmount + ' Ğ1' ;
|
||||||
|
}
|
||||||
|
currentProfileTitleElt.innerHTML = title;
|
||||||
|
|
||||||
|
let svgContainer = document.createElement('article');
|
||||||
|
screenElt.append(svgContainer);
|
||||||
|
svgContainer.classList.add("svg-container");
|
||||||
|
|
||||||
|
let chartData = [];
|
||||||
|
|
||||||
|
// Formatting data
|
||||||
|
for (const recipient in expensesByRecipient) {
|
||||||
|
|
||||||
|
let recipientObj = {};
|
||||||
|
|
||||||
|
recipientObj.pk = recipient;
|
||||||
|
recipientObj.amount = expensesByRecipient[recipient];
|
||||||
|
let numberOptions = { roundingMode: 'ceil', minimumFractionDigits: 0, maximumFractionDigits: 0 };
|
||||||
|
recipientObj.displayedAmount = G12DU(recipientObj.amount).toLocaleString('fr-FR', numberOptions) + ' DU';
|
||||||
|
|
||||||
|
if (recipientsCesiumProfiles[recipient] != undefined
|
||||||
|
&& recipientsCesiumProfiles[recipient].title != undefined
|
||||||
|
) {
|
||||||
|
recipientObj.title = recipientsCesiumProfiles[recipient].title;
|
||||||
|
} else {
|
||||||
|
recipientObj.title = recipient.substr(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
chartData.push(recipientObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
let chart = Treemap(chartData, {
|
||||||
|
path: d => d.title,
|
||||||
|
value: d => d.amount,
|
||||||
|
group: d => d.title,
|
||||||
|
label: (d, n) => d.title,
|
||||||
|
title: (d, n) => d.displayedAmount,
|
||||||
|
link: (d, n) => '#' + d.pk + '',
|
||||||
|
linkTarget: '_self',
|
||||||
|
tile: d3.treemapSquarify,
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
padding: 0,
|
||||||
|
colors: chartColors,
|
||||||
|
fillOpacity: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
svgContainer.append(chart);
|
||||||
|
|
||||||
|
screenElt.scrollIntoView({behavior: 'smooth'}, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const displayIncomes = (incomesByIssuer, incomesTotalAmount, issuersCesiumProfiles, chartColors, currentPubkey, currentProfile) => {
|
||||||
|
|
||||||
|
let screenElt = document.querySelector('#incomes');
|
||||||
|
screenElt.innerHTML = '';
|
||||||
|
|
||||||
|
let currentProfileTitleElt = document.createElement('h2');
|
||||||
|
screenElt.append(currentProfileTitleElt);
|
||||||
|
let title = null;
|
||||||
|
if (currentProfile == undefined) {
|
||||||
|
title = 'Revenus du portefeuille <code>' + currentPubkey.substr(0, 8) + '</code> = '+ incomesTotalAmount + ' Ğ1';
|
||||||
|
} else {
|
||||||
|
title = 'Revenus de <q>' + currentProfile.title + '</q> <code>' + currentPubkey.substr(0, 8) + '</code> = ' + incomesTotalAmount + ' Ğ1';
|
||||||
|
}
|
||||||
|
currentProfileTitleElt.innerHTML = title;
|
||||||
|
|
||||||
|
let svgContainer = document.createElement('article');
|
||||||
|
screenElt.append(svgContainer);
|
||||||
|
svgContainer.classList.add("svg-container");
|
||||||
|
|
||||||
|
let chartData = [];
|
||||||
|
|
||||||
|
// Formatting data
|
||||||
|
for (const issuer in incomesByIssuer) {
|
||||||
|
|
||||||
|
let issuerObj = {};
|
||||||
|
|
||||||
|
issuerObj.pk = issuer;
|
||||||
|
issuerObj.amount = incomesByIssuer[issuer];
|
||||||
|
let numberOptions = { roundingMode: 'ceil', minimumFractionDigits: 0, maximumFractionDigits: 0 };
|
||||||
|
issuerObj.displayedAmount = G12DU(issuerObj.amount).toLocaleString('fr-FR', numberOptions) + ' DU';
|
||||||
|
|
||||||
|
if (issuersCesiumProfiles[issuer] != undefined
|
||||||
|
&& issuersCesiumProfiles[issuer].title != undefined
|
||||||
|
) {
|
||||||
|
issuerObj.title = issuersCesiumProfiles[issuer].title;
|
||||||
|
} else {
|
||||||
|
issuerObj.title = issuer.substr(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
chartData.push(issuerObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
let chart = Treemap(chartData, {
|
||||||
|
path: d => d.title,
|
||||||
|
value: d => d.amount,
|
||||||
|
group: d => d.title,
|
||||||
|
label: (d, n) => d.title,
|
||||||
|
title: (d, n) => d.displayedAmount,
|
||||||
|
link: (d, n) => '#' + d.pk + '',
|
||||||
|
linkTarget: '_self',
|
||||||
|
tile: d3.treemapSquarify,
|
||||||
|
width: 1280,
|
||||||
|
height: 720,
|
||||||
|
padding: 0,
|
||||||
|
colors: chartColors,
|
||||||
|
fillOpacity: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
svgContainer.append(chart);
|
||||||
|
|
||||||
|
screenElt.scrollIntoView({behavior: 'smooth'}, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
let formElt = document.querySelector('form#explore');
|
||||||
|
|
||||||
|
const treemapIt = async (pubkey, minTime, maxNbTx = MAX_NB_TX) => {
|
||||||
|
|
||||||
|
let dotsPos = pubkey.indexOf(':');
|
||||||
|
if (dotsPos != -1) {
|
||||||
|
pubkey = pubkey.substr(0, dotsPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change pubkey in form
|
||||||
|
const pubkeyElement = document.querySelector('input[name="pubkey"]');
|
||||||
|
pubkeyElement.value = pubkey
|
||||||
|
|
||||||
|
let { expensesTotalAmount, expensesByRecipient } = await fetchExpenses(pubkey, minTime, maxNbTx);
|
||||||
|
let { incomesTotalAmount, incomesByIssuer } = await fetchIncomes(pubkey, minTime, maxNbTx);
|
||||||
|
|
||||||
|
let nbRecipients = Object.keys(expensesByRecipient).length;
|
||||||
|
let nbIssuers = Object.keys(incomesByIssuer).length;
|
||||||
|
|
||||||
|
console.log("nb recipients :\n", nbRecipients);
|
||||||
|
console.log("nb Issuers :\n", nbIssuers);
|
||||||
|
|
||||||
|
let recipientsList = Object.keys(expensesByRecipient);
|
||||||
|
let recipientsCesiumProfiles = await fetchCesiumProfiles(recipientsList, maxNbTx);
|
||||||
|
|
||||||
|
let issuersList = Object.keys(incomesByIssuer);
|
||||||
|
let issuersCesiumProfiles = await fetchCesiumProfiles(issuersList, maxNbTx);
|
||||||
|
|
||||||
|
let currentProfile = await fetchCesiumProfile(pubkey);
|
||||||
|
console.log('currentProfile :\n', currentProfile);
|
||||||
|
|
||||||
|
displayExpenses(expensesByRecipient, expensesTotalAmount, recipientsCesiumProfiles, chartColors, pubkey, currentProfile);
|
||||||
|
displayIncomes(incomesByIssuer, incomesTotalAmount, issuersCesiumProfiles, chartColors, pubkey, currentProfile);
|
||||||
|
|
||||||
|
let svg = document.querySelector('#expenses svg');
|
||||||
|
let links = svg.querySelectorAll("a");
|
||||||
|
|
||||||
|
for (const link of links) {
|
||||||
|
|
||||||
|
link.addEventListener('click', (linkEvent) => {
|
||||||
|
|
||||||
|
// linkEvent.currentTarget.preventDefault();
|
||||||
|
console.log('linkEvent.currentTarget :\n', linkEvent.currentTarget);
|
||||||
|
let pubkey = linkEvent.currentTarget.getAttribute('href').substr(1);
|
||||||
|
|
||||||
|
// treemapIt(pubkey, minDate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPkInHash = () => {
|
||||||
|
|
||||||
|
let hash = window.location.hash;
|
||||||
|
hash = hash.substring(1);
|
||||||
|
return hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("popstate", (popEvent) => {
|
||||||
|
|
||||||
|
let pk = getPkInHash();
|
||||||
|
console.log('\n\n\npubkey :\n', pk);
|
||||||
|
|
||||||
|
if (pk != '') {
|
||||||
|
treemapIt(pk, minTime, txLimit);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
formElt.addEventListener('submit', (formEvent) => {
|
||||||
|
|
||||||
|
formEvent.preventDefault();
|
||||||
|
|
||||||
|
txLimit = formEvent.target.querySelector('input[name="txLimit"]').value;
|
||||||
|
|
||||||
|
let minDateStr = formEvent.target.querySelector('input[name="minDate"]').value;
|
||||||
|
let minDate = new Date(minDateStr);
|
||||||
|
minTime = Math.floor(minDate.valueOf()/1000);
|
||||||
|
console.log('minTime :\n', minTime);
|
||||||
|
|
||||||
|
let pubkey = formEvent.target.querySelector('input[name="pubkey"]').value;
|
||||||
|
|
||||||
|
window.location = '#';
|
||||||
|
window.location = '#' + pubkey;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', (loadEvent) => {
|
||||||
|
|
||||||
|
let formElt = document.getElementById('explore');
|
||||||
|
|
||||||
|
let minDateElt = formElt.querySelector('input[name="minDate"]');
|
||||||
|
|
||||||
|
let now = Date();
|
||||||
|
console.log('now : \n', now);
|
||||||
|
|
||||||
|
let aMonthAgo = new Date(now);
|
||||||
|
aMonthAgo.setMonth(aMonthAgo.getMonth() - 1);
|
||||||
|
|
||||||
|
console.log('aMonthAgo : ', aMonthAgo);
|
||||||
|
console.log('aMonthAgo.getMonth() :\n', aMonthAgo.getMonth());
|
||||||
|
|
||||||
|
let dateStr = aMonthAgo.getFullYear() + '-' + (aMonthAgo.getMonth()+1).toString().padStart(2,0) + '-' + aMonthAgo.getDate().toString().padStart(2,0);
|
||||||
|
|
||||||
|
console.log('dateStr : ', dateStr);
|
||||||
|
|
||||||
|
minDateElt.value = dateStr;
|
||||||
|
});
|
286
earth/inout/index_fichiers/jspdf.min.js
vendored
Normal file
286
earth/inout/index_fichiers/jspdf.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
72
earth/inout/index_fichiers/styles.css
Normal file
72
earth/inout/index_fichiers/styles.css
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
header {
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header #description {
|
||||||
|
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
#expenses svg {
|
||||||
|
|
||||||
|
font-size: 24px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#incomes svg {
|
||||||
|
|
||||||
|
font-size: 24px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
|
||||||
|
margin-top: 3rem;
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
|
||||||
|
border-top: 2px solid hsl(0, 0%, 95%);
|
||||||
|
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer blockquote {
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
footer a,
|
||||||
|
footer a:visited {
|
||||||
|
|
||||||
|
text-decoration: none;
|
||||||
|
color: hsl(0, 0%, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#export-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#export-button button {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@ -370,10 +370,10 @@ loadGridNumbers(zoneURL).then(gridNumbers => {
|
|||||||
buttonContainer.style.justifyContent = 'center';
|
buttonContainer.style.justifyContent = 'center';
|
||||||
|
|
||||||
const button = document.createElement('button');
|
const button = document.createElement('button');
|
||||||
if (deg === 0.01) { button.innerText = 'EXPLORE UMAP'; }
|
if (NSize === 0.001) { button.innerText = 'EXPLORE UMAP'; }
|
||||||
if (deg === 0.1) { button.innerText = 'EXPLORE SECTOR'; }
|
if (NSize === 0.01) { button.innerText = 'EXPLORE SECTOR'; }
|
||||||
if (deg === "1") { button.innerText = 'EXPLORE REGION'; }
|
if (NSize === 0.1) { button.innerText = 'EXPLORE REGION'; }
|
||||||
if (deg === "") { button.innerText = 'EXPLORE ZONE'; }
|
if (NSize == "") { button.innerText = 'EXPLORE ZONE'; }
|
||||||
button.className = 'button';
|
button.className = 'button';
|
||||||
|
|
||||||
// Add an event listener to the button
|
// Add an event listener to the button
|
||||||
|
Loading…
Reference in New Issue
Block a user