Skip to content

Commit 5153ae0

Browse files
author
WrenchDesk Dev
committed
feat: add currency support, stats sensors, and bug fixes
Made-with: Cursor
1 parent e516d1e commit 5153ae0

4 files changed

Lines changed: 175 additions & 20 deletions

File tree

custom_components/uber_eats/coordinator.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -561,17 +561,18 @@ async def fetch_user_profile(self):
561561
async with session.post(url, headers=headers, json={}) as resp:
562562
if resp.status != 200:
563563
_LOGGER.error("getUserV1 API returned %s", resp.status)
564-
return {"picture_url": None, "first_name": "", "last_name": ""}
564+
return {"picture_url": None, "first_name": "", "last_name": "", "country_code": "US"}
565565
data = await resp.json()
566566
user_data = data.get("data", {})
567567
return {
568568
"picture_url": user_data.get("pictureUrl"),
569569
"first_name": user_data.get("firstName", ""),
570570
"last_name": user_data.get("lastName", ""),
571+
"country_code": user_data.get("geoIpCountryCode", "US"),
571572
}
572573
except Exception as e:
573574
_LOGGER.error("Error fetching user profile: %s", e, exc_info=True)
574-
return {"picture_url": None, "first_name": "", "last_name": ""}
575+
return {"picture_url": None, "first_name": "", "last_name": "", "country_code": "US"}
575576

576577
def _compute_order_statistics(self, orders, year):
577578
"""Compute statistics from orders list."""
@@ -742,13 +743,16 @@ def _process_tts_events(self, current_data):
742743
curr_with_status = dict(current_data)
743744
curr_active = current_data.get("active")
744745
messages_to_send = []
746+
747+
# Use first name only for TTS messages (sensor names still use full account_name)
748+
tts_name = (self._cached_user_profile or {}).get("first_name", "").strip() or self.account_name
745749

746750
# 1. New order: same logic as active order sensor — was off, now on (with restaurant for message)
747751
if not prev.get("active") and current_data.get("active"):
748752
rest = (current_data.get("restaurant_name") or "").strip()
749753
if rest and rest not in ("No Restaurant", "Unknown"):
750754
msg = tts_notifications.build_message(
751-
prefix, self.account_name, curr_with_status, "new_order"
755+
prefix, tts_name, curr_with_status, "new_order"
752756
)
753757
if msg:
754758
messages_to_send.append(("new_order", msg))
@@ -760,7 +764,7 @@ def _process_tts_events(self, current_data):
760764
has_driver = _has_driver(curr_driver)
761765
if not had_driver and has_driver:
762766
msg = tts_notifications.build_message(
763-
prefix, self.account_name, curr_with_status, "driver_assigned"
767+
prefix, tts_name, curr_with_status, "driver_assigned"
764768
)
765769
if msg:
766770
messages_to_send.append(("driver_assigned", msg))
@@ -775,7 +779,7 @@ def _process_tts_events(self, current_data):
775779
curr_order_status = current_data.get("order_status", "")
776780
if curr_active and curr_order_status and curr_order_status != prev_order_status:
777781
msg = tts_notifications.build_message(
778-
prefix, self.account_name, curr_with_status, "status_change"
782+
prefix, tts_name, curr_with_status, "status_change"
779783
)
780784
if msg:
781785
messages_to_send.append(("status_change", msg))
@@ -786,7 +790,7 @@ def _process_tts_events(self, current_data):
786790
elapsed = (now - self._last_interval_tts_time).total_seconds()
787791
if elapsed >= interval_minutes * 60:
788792
msg = tts_notifications.build_message(
789-
prefix, self.account_name, curr_with_status, "interval_update"
793+
prefix, tts_name, curr_with_status, "interval_update"
790794
)
791795
if msg:
792796
messages_to_send.append(("interval_update", msg))

custom_components/uber_eats/frontend/uber-eats-panel.js

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,47 @@ const UBER_EATS_LOGO_SIMPLE = `
2222
</span>
2323
`;
2424

25+
// Currency symbols by country code (ISO 3166-1 alpha-2)
26+
const CURRENCY_SYMBOLS = {
27+
US: "$", CA: "$", AU: "$", NZ: "$", SG: "$", HK: "$", MX: "$", CL: "$", CO: "$", AR: "$",
28+
GB: "£", UK: "£",
29+
EU: "€", DE: "€", FR: "€", IT: "€", ES: "€", NL: "€", BE: "€", AT: "€", IE: "€", PT: "€", FI: "€", GR: "€",
30+
JP: "¥", CN: "¥",
31+
KR: "₩",
32+
IN: "₹",
33+
BR: "R$",
34+
ZA: "R",
35+
AE: "د.إ", SA: "﷼",
36+
TH: "฿",
37+
PL: "zł",
38+
SE: "kr", NO: "kr", DK: "kr",
39+
CH: "CHF",
40+
RU: "₽",
41+
TR: "₺",
42+
IL: "₪",
43+
MY: "RM",
44+
PH: "₱",
45+
ID: "Rp",
46+
VN: "₫",
47+
TW: "NT$",
48+
CZ: "Kč",
49+
HU: "Ft",
50+
RO: "lei",
51+
UA: "₴",
52+
NG: "₦",
53+
EG: "E£",
54+
KE: "KSh",
55+
PK: "₨",
56+
BD: "৳",
57+
LK: "Rs",
58+
};
59+
60+
function getCurrencySymbol(countryCode) {
61+
if (!countryCode) return "$";
62+
const code = countryCode.toUpperCase();
63+
return CURRENCY_SYMBOLS[code] || "$";
64+
}
65+
2566
class UberEatsPanel extends HTMLElement {
2667
constructor() {
2768
super();
@@ -2658,6 +2699,22 @@ class UberEatsPanel extends HTMLElement {
26582699
return div.innerHTML;
26592700
}
26602701

2702+
/** Get currency symbol based on user's country from profile */
2703+
_getCurrencySymbol() {
2704+
const countryCode = this._userProfile?.country_code || "US";
2705+
return getCurrencySymbol(countryCode);
2706+
}
2707+
2708+
/** Format a currency value with the correct symbol */
2709+
_formatCurrency(value, fallback = "—") {
2710+
if (typeof value !== "number") return fallback;
2711+
const symbol = this._getCurrencySymbol();
2712+
if (value < 0) {
2713+
return `-${symbol}${Math.abs(value).toFixed(2)}`;
2714+
}
2715+
return `${symbol}${value.toFixed(2)}`;
2716+
}
2717+
26612718
/** Parse simple YAML-like key: value lines into a dict. */
26622719
_parseYamlOptions(text) {
26632720
const result = {};
@@ -2706,15 +2763,16 @@ class UberEatsPanel extends HTMLElement {
27062763

27072764
const year = stats.year || new Date().getFullYear();
27082765
const totalOrders = stats.total_orders || 0;
2709-
const totalSpent = typeof stats.total_spent === "number" ? `$${stats.total_spent.toFixed(2)}` : "$0.00";
2710-
const totalDeliveryFees = typeof stats.total_delivery_fees === "number" ? `$${stats.total_delivery_fees.toFixed(2)}` : "$0.00";
2766+
const currencySymbol = this._getCurrencySymbol();
2767+
const totalSpent = typeof stats.total_spent === "number" ? `${currencySymbol}${stats.total_spent.toFixed(2)}` : `${currencySymbol}0.00`;
2768+
const totalDeliveryFees = typeof stats.total_delivery_fees === "number" ? `${currencySymbol}${stats.total_delivery_fees.toFixed(2)}` : `${currencySymbol}0.00`;
27112769
const topRestaurants = stats.top_restaurants || [];
27122770

27132771
const topRestaurantsHtml = topRestaurants.length > 0
27142772
? topRestaurants.map((r, idx) => {
27152773
const name = this._escapeHtml(r.name || "Unknown");
27162774
const orderCount = r.order_count || 0;
2717-
const spent = typeof r.total_spent === "number" ? `$${r.total_spent.toFixed(2)}` : "$0.00";
2775+
const spent = typeof r.total_spent === "number" ? `${currencySymbol}${r.total_spent.toFixed(2)}` : `${currencySymbol}0.00`;
27182776
const medals = ["🥇", "🥈", "🥉"];
27192777
return `
27202778
<div class="top-restaurant-row">
@@ -2788,11 +2846,22 @@ class UberEatsPanel extends HTMLElement {
27882846
const imgUrl = order.hero_image_url || "";
27892847
const name = this._escapeHtml(order.restaurant_name || "Unknown");
27902848
const date = this._escapeHtml(order.date || "");
2791-
const subtotal = typeof order.subtotal === "number" ? `$${order.subtotal.toFixed(2)}` : "—";
2792-
const deliveryFee = typeof order.delivery_fee === "number" ? `$${order.delivery_fee.toFixed(2)}` : "—";
2793-
const tax = typeof order.tax === "number" ? `$${order.tax.toFixed(2)}` : null;
2794-
const promotions = typeof order.promotions === "number" && order.promotions < 0 ? `-$${Math.abs(order.promotions).toFixed(2)}` : null;
2795-
const total = typeof order.total === "number" ? `$${order.total.toFixed(2)}` : "—";
2849+
const currencySymbol = this._getCurrencySymbol();
2850+
const subtotal = typeof order.subtotal === "number" ? `${currencySymbol}${order.subtotal.toFixed(2)}` : "—";
2851+
const deliveryFee = typeof order.delivery_fee === "number" ? `${currencySymbol}${order.delivery_fee.toFixed(2)}` : "—";
2852+
const tax = typeof order.tax === "number" ? `${currencySymbol}${order.tax.toFixed(2)}` : null;
2853+
const promotions = typeof order.promotions === "number" && order.promotions < 0 ? `-${currencySymbol}${Math.abs(order.promotions).toFixed(2)}` : null;
2854+
const total = typeof order.total === "number" ? `${currencySymbol}${order.total.toFixed(2)}` : "—";
2855+
2856+
// Calculate "Other Fees" if breakdown doesn't match total
2857+
const subtotalNum = typeof order.subtotal === "number" ? order.subtotal : 0;
2858+
const deliveryFeeNum = typeof order.delivery_fee === "number" ? order.delivery_fee : 0;
2859+
const taxNum = typeof order.tax === "number" ? order.tax : 0;
2860+
const promotionsNum = typeof order.promotions === "number" ? order.promotions : 0;
2861+
const totalNum = typeof order.total === "number" ? order.total : 0;
2862+
const expectedTotal = subtotalNum + deliveryFeeNum + taxNum + promotionsNum;
2863+
const otherFeesNum = totalNum - expectedTotal;
2864+
const otherFees = Math.abs(otherFeesNum) > 0.01 ? `${currencySymbol}${otherFeesNum.toFixed(2)}` : null;
27962865
// Clean address: remove empty fields (double commas, leading/trailing commas)
27972866
const rawAddress = order.store_address || "—";
27982867
const cleanAddress = rawAddress
@@ -2832,6 +2901,12 @@ class UberEatsPanel extends HTMLElement {
28322901
<span class="past-order-value">${promotions}</span>
28332902
</div>
28342903
` : ""}
2904+
${otherFees ? `
2905+
<div class="past-order-row">
2906+
<span class="past-order-label">Other Fees</span>
2907+
<span class="past-order-value">${otherFees}</span>
2908+
</div>
2909+
` : ""}
28352910
<div class="past-order-row total">
28362911
<span class="past-order-label">Total</span>
28372912
<span class="past-order-value">${total}</span>

custom_components/uber_eats/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"dependencies": ["http", "frontend", "panel_custom"],
77
"codeowners": ["@zodyking"],
88
"iot_class": "cloud_polling",
9-
"version": "1.4.6",
9+
"version": "1.4.7",
1010
"config_flow": true,
1111
"integration_type": "service"
1212
}

custom_components/uber_eats/sensor.py

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from typing import Any
66

77
from .const import DOMAIN, CONF_ACCOUNT_NAME
8-
from homeassistant.components.sensor import SensorEntity
8+
from homeassistant.components.sensor import SensorEntity, SensorStateClass
99
from homeassistant.helpers.entity_registry import async_get as async_get_entity_reg
1010
from homeassistant.util import dt as dt_util
1111

@@ -71,6 +71,11 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
7171
UberEatsDriverLocationAddress(coordinator, account_name),
7272

7373
UberEatsDriverETT(coordinator, account_name),
74+
75+
# Statistics sensors
76+
UberEatsTotalDeliveries(coordinator, account_name),
77+
UberEatsTotalSpent(coordinator, account_name),
78+
UberEatsTotalDeliveryFees(coordinator, account_name),
7479
]
7580

7681
async_add_entities(entities)
@@ -221,8 +226,15 @@ class UberEatsDriverLatitude(UberEatsEntity):
221226
_attr_translation_key = "driver_latitude"
222227
_attr_native_unit_of_measurement = "°"
223228
@property
224-
def native_value(self) -> str:
225-
return _order_count_state(self.coordinator)
229+
def native_value(self) -> float:
230+
"""Return driver latitude or HA home latitude when no active order."""
231+
orders = _get_orders(self.coordinator)
232+
if orders:
233+
lat = orders[0].get("driver_location_lat")
234+
if isinstance(lat, (int, float)):
235+
return float(lat)
236+
# Fall back to HA home latitude
237+
return float(self.coordinator.hass.config.latitude or 0.0)
226238
@property
227239
def extra_state_attributes(self) -> dict[str, Any]:
228240
return _multi_order_attrs(self.coordinator, "driver_location_lat", "No Active Order")
@@ -231,8 +243,15 @@ class UberEatsDriverLongitude(UberEatsEntity):
231243
_attr_translation_key = "driver_longitude"
232244
_attr_native_unit_of_measurement = "°"
233245
@property
234-
def native_value(self) -> str:
235-
return _order_count_state(self.coordinator)
246+
def native_value(self) -> float:
247+
"""Return driver longitude or HA home longitude when no active order."""
248+
orders = _get_orders(self.coordinator)
249+
if orders:
250+
lon = orders[0].get("driver_location_lon")
251+
if isinstance(lon, (int, float)):
252+
return float(lon)
253+
# Fall back to HA home longitude
254+
return float(self.coordinator.hass.config.longitude or 0.0)
236255
@property
237256
def extra_state_attributes(self) -> dict[str, Any]:
238257
return _multi_order_attrs(self.coordinator, "driver_location_lon", "No Active Order")
@@ -296,3 +315,60 @@ def extra_state_attributes(self) -> dict[str, Any]:
296315
minutes = o.get("minutes_remaining", None)
297316
attrs[f"order{i}_minutes_remaining"] = minutes if minutes is not None else "No ETT Available"
298317
return attrs
318+
319+
320+
# ---------- Statistics Sensors ----------
321+
def _get_statistics(coordinator) -> dict[str, Any]:
322+
"""Get statistics from coordinator's cached past orders."""
323+
cached = getattr(coordinator, "_cached_past_orders", None)
324+
if cached and isinstance(cached, dict):
325+
return cached.get("statistics", {})
326+
return {}
327+
328+
329+
class UberEatsTotalDeliveries(UberEatsEntity):
330+
_attr_translation_key = "total_deliveries"
331+
_attr_state_class = SensorStateClass.TOTAL
332+
_attr_native_unit_of_measurement = "orders"
333+
334+
@property
335+
def native_value(self) -> int:
336+
stats = _get_statistics(self.coordinator)
337+
return stats.get("total_orders", 0)
338+
339+
@property
340+
def extra_state_attributes(self) -> dict[str, Any]:
341+
stats = _get_statistics(self.coordinator)
342+
return {"year": stats.get("year", datetime.now().year)}
343+
344+
345+
class UberEatsTotalSpent(UberEatsEntity):
346+
_attr_translation_key = "total_spent"
347+
_attr_state_class = SensorStateClass.TOTAL
348+
_attr_native_unit_of_measurement = "USD"
349+
350+
@property
351+
def native_value(self) -> float:
352+
stats = _get_statistics(self.coordinator)
353+
return stats.get("total_spent", 0.0)
354+
355+
@property
356+
def extra_state_attributes(self) -> dict[str, Any]:
357+
stats = _get_statistics(self.coordinator)
358+
return {"year": stats.get("year", datetime.now().year)}
359+
360+
361+
class UberEatsTotalDeliveryFees(UberEatsEntity):
362+
_attr_translation_key = "total_delivery_fees"
363+
_attr_state_class = SensorStateClass.TOTAL
364+
_attr_native_unit_of_measurement = "USD"
365+
366+
@property
367+
def native_value(self) -> float:
368+
stats = _get_statistics(self.coordinator)
369+
return stats.get("total_delivery_fees", 0.0)
370+
371+
@property
372+
def extra_state_attributes(self) -> dict[str, Any]:
373+
stats = _get_statistics(self.coordinator)
374+
return {"year": stats.get("year", datetime.now().year)}

0 commit comments

Comments
 (0)