ydassdsa
All checks were successful
Deploy / deploy-staging (push) Successful in 30s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-05-08 23:47:00 +02:00
parent bec6fb1e0a
commit ee5a46254f
3 changed files with 180 additions and 21 deletions

View File

@@ -45,6 +45,13 @@
sequence: 0,
};
window.__nexusDebugBus = debugBus;
const browserTimezone = (() => {
try {
return Intl.DateTimeFormat().resolvedOptions().timeZone || 'Europe/Berlin';
} catch (_error) {
return 'Europe/Berlin';
}
})();
function emitDebug(entry) {
debugBus.sequence += 1;
@@ -124,19 +131,96 @@
}).format(Number(value));
}
function parseStoredUtcDate(value) {
const raw = String(value || '').trim();
if (!raw) {
return null;
}
let normalized = raw.replace(' ', 'T');
if (/^\d{4}-\d{2}-\d{2}$/.test(normalized)) {
normalized = `${normalized}T00:00:00Z`;
} else if (!/[zZ]$|[+-]\d{2}:\d{2}$/.test(normalized)) {
normalized = `${normalized}Z`;
}
const parsed = new Date(normalized);
return Number.isNaN(parsed.getTime()) ? null : parsed;
}
function formatDateByParts(value, includeTime) {
const parsed = parseStoredUtcDate(value);
if (!parsed) {
return value ? String(value).replace('T', ' ').slice(0, includeTime ? 16 : 10) : 'n/a';
}
const parts = new Intl.DateTimeFormat('de-DE', {
timeZone: browserTimezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
...(includeTime ? { hour: '2-digit', minute: '2-digit' } : {}),
}).formatToParts(parsed);
const map = Object.fromEntries(parts.map((part) => [part.type, part.value]));
return includeTime
? `${map.day}.${map.month}.${map.year} ${map.hour}:${map.minute}`
: `${map.day}.${map.month}.${map.year}`;
}
function toDateTimeLocalValue(value) {
const parsed = parseStoredUtcDate(value);
if (!parsed) {
return '';
}
const parts = new Intl.DateTimeFormat('sv-SE', {
timeZone: browserTimezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
}).formatToParts(parsed);
const map = Object.fromEntries(parts.map((part) => [part.type, part.value]));
return `${map.year}-${map.month}-${map.day}T${map.hour}:${map.minute}`;
}
function nowDateTimeLocalValue() {
const now = new Date();
const parts = new Intl.DateTimeFormat('sv-SE', {
timeZone: browserTimezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false,
}).formatToParts(now);
const map = Object.fromEntries(parts.map((part) => [part.type, part.value]));
return `${map.year}-${map.month}-${map.day}T${map.hour}:${map.minute}`;
}
function todayLocalDateValue() {
const now = new Date();
const parts = new Intl.DateTimeFormat('sv-SE', {
timeZone: browserTimezone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
}).formatToParts(now);
const map = Object.fromEntries(parts.map((part) => [part.type, part.value]));
return `${map.year}-${map.month}-${map.day}`;
}
function fmtDate(value) {
if (!value) {
return 'n/a';
}
return value.replace('T', ' ').slice(0, 16);
return formatDateByParts(value, true);
}
function fmtDateTime(value) {
if (!value) {
return 'n/a';
}
const normalized = String(value).replace('T', ' ');
return normalized.slice(0, 16);
return formatDateByParts(value, true);
}
async function request(path, options) {
@@ -579,7 +663,7 @@
const [importHelpOpen, setImportHelpOpen] = useState(false);
const [ocrForm, setOcrForm] = useState({
image: null,
date_context: new Date().toISOString().slice(0, 10),
date_context: todayLocalDateValue(),
ocr_hint_text: '',
});
const [ocrPreview, setOcrPreview] = useState(null);
@@ -1093,18 +1177,18 @@
const basePrice = baseComparison ? Number(baseComparison.effective_price_per_coin ?? baseComparison.price_per_coin) : null;
return {
mining: measurements.map((row) => ({ x: row.measured_at.slice(5, 16), y: row.coins_total })),
mining: measurements.map((row) => ({ x: fmtDate(row.measured_at), y: row.coins_total })),
performance: measurements.filter((row) => row.doge_per_day_interval !== null)
.map((row) => ({ x: row.measured_at.slice(5, 16), y: row.doge_per_day_interval })),
.map((row) => ({ x: fmtDate(row.measured_at), y: row.doge_per_day_interval })),
pricing: measurements.filter((row) => row.price_per_coin !== null)
.map((row) => ({ x: row.measured_at.slice(5, 16), y: row.price_per_coin })),
.map((row) => ({ x: fmtDate(row.measured_at), y: row.price_per_coin })),
miningVsPrice: baseMining && basePrice ? [
{
key: 'mining-rate',
label: 'Mining/h je MH/s Index',
color: '#2dd4bf',
data: comparisonRows.map((row) => ({
x: row.measured_at.slice(5, 16),
x: fmtDate(row.measured_at),
y: (Number(row.doge_per_hour_per_mh_interval) / baseMining) * 100,
})),
},
@@ -1113,7 +1197,7 @@
label: 'DOGE-Kurs Index',
color: '#f59e0b',
data: comparisonRows.map((row) => ({
x: row.measured_at.slice(5, 16),
x: fmtDate(row.measured_at),
y: (Number(row.effective_price_per_coin ?? row.price_per_coin) / basePrice) * 100,
})),
},
@@ -1501,7 +1585,7 @@
await request(`${apiBase}/projects/${encodeURIComponent(projectKey)}/miner-offers/${offerId}/purchase`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(overrides || { purchased_at: new Date().toISOString().slice(0, 19).replace('T', ' ') }),
body: JSON.stringify(overrides || { purchased_at: nowDateTimeLocalValue() }),
});
setMessage('Miner als gemietet erfasst.');
setPurchaseMinerModalOpen(false);
@@ -1521,7 +1605,7 @@
}
await purchaseMinerOffer(purchaseMinerForm.offer_id, {
purchased_at: purchaseMinerForm.purchased_at || new Date().toISOString().slice(0, 19).replace('T', ' '),
purchased_at: purchaseMinerForm.purchased_at || nowDateTimeLocalValue(),
total_cost_amount: purchaseMinerForm.total_cost_amount || null,
currency: purchaseMinerForm.currency || null,
reference_price_amount: purchaseMinerForm.reference_price_amount || null,
@@ -2210,7 +2294,7 @@
onClick: () => {
setPurchaseMinerForm({
offer_id: String(offer.id),
purchased_at: new Date().toISOString().slice(0, 16),
purchased_at: nowDateTimeLocalValue(),
total_cost_amount: offer.effective_price_amount !== null && offer.effective_price_amount !== undefined ? String(offer.effective_price_amount) : '',
currency: offer.effective_price_currency || offer.base_price_currency || 'USD',
reference_price_amount: offer.base_price_amount !== null && offer.base_price_amount !== undefined ? String(offer.base_price_amount) : '',
@@ -2404,7 +2488,7 @@
const offer = availableMinerOffers.find((item) => String(item.id) === String(value));
setPurchaseMinerForm({
offer_id: value,
purchased_at: purchaseMinerForm.purchased_at || new Date().toISOString().slice(0, 16),
purchased_at: purchaseMinerForm.purchased_at || nowDateTimeLocalValue(),
total_cost_amount: offer && offer.effective_price_amount !== null && offer.effective_price_amount !== undefined ? String(offer.effective_price_amount) : '',
currency: offer?.effective_price_currency || offer?.base_price_currency || 'USD',
reference_price_amount: offer && offer.reference_price_amount !== null && offer.reference_price_amount !== undefined ? String(offer.reference_price_amount) : '',
@@ -2552,7 +2636,7 @@
className: 'mc-form',
onSubmit: submitSettings,
}, [
inputField('Baseline Zeitpunkt', 'datetime-local', settingsForm.baseline_measured_at ? settingsForm.baseline_measured_at.replace(' ', 'T').slice(0, 16) : '', (value) => setSettingsForm({ ...settingsForm, baseline_measured_at: value })),
inputField('Baseline Zeitpunkt', 'datetime-local', toDateTimeLocalValue(settingsForm.baseline_measured_at), (value) => setSettingsForm({ ...settingsForm, baseline_measured_at: value })),
inputField('Baseline Coins', 'number', settingsForm.baseline_coins_total, (value) => setSettingsForm({ ...settingsForm, baseline_coins_total: value }), '0.000001'),
selectField('Standard-FIAT-Währung', settingsForm.report_currency || 'EUR', selectableFiatCurrencies.map((currency) => currency.code), (value) => setSettingsForm({ ...settingsForm, report_currency: value })),
selectField('Standard-Krypto-Währung', settingsForm.crypto_currency || 'DOGE', selectableCryptoCurrencies.map((currency) => currency.code), (value) => setSettingsForm({ ...settingsForm, crypto_currency: value })),