feat(web client): date time format options

This commit is contained in:
Henri Bourcereau 2026-05-22 16:54:37 +02:00
parent 9443a04ad6
commit 25554126a8
4 changed files with 63 additions and 8 deletions

View file

@ -99,7 +99,7 @@
"resend_verification": "Renvoyer l'email de vérification",
"verification_email_resent": "Email de vérification envoyé.",
"loading": "Chargement…",
"member_since": "Membre depuis",
"member_since": "Membre depuis le",
"stat_games": "Parties",
"stat_wins": "Victoires",
"stat_losses": "Défaites",

View file

@ -244,10 +244,53 @@ pub async fn post_reset_password(token: &str, new_password: &str) -> Result<(),
// ── Utilities ─────────────────────────────────────────────────────────────────
pub fn format_ts(ts: i64) -> String {
/// Maps to the `Intl.DateTimeFormat` options object accepted by `Date.toLocaleString`.
/// `Default` passes no options (browser default: full date + time).
pub struct DateFormatOptions {
/// "full" | "long" | "medium" | "short" — omit to suppress date part
pub date_style: Option<&'static str>,
/// "full" | "long" | "medium" | "short" — omit to suppress time part
pub time_style: Option<&'static str>,
}
impl Default for DateFormatOptions {
fn default() -> Self {
Self { date_style: None, time_style: None }
}
}
impl DateFormatOptions {
pub fn date_only() -> Self {
Self { date_style: Some("short"), time_style: None }
}
pub fn time_only() -> Self {
Self { date_style: None, time_style: Some("short") }
}
pub fn date_time() -> Self {
Self { date_style: Some("short"), time_style: Some("short") }
}
fn to_js_value(&self) -> wasm_bindgen::JsValue {
if self.date_style.is_none() && self.time_style.is_none() {
return wasm_bindgen::JsValue::UNDEFINED;
}
let obj = js_sys::Object::new();
if let Some(v) = self.date_style {
let _ = js_sys::Reflect::set(&obj, &"dateStyle".into(), &v.into());
}
if let Some(v) = self.time_style {
let _ = js_sys::Reflect::set(&obj, &"timeStyle".into(), &v.into());
}
obj.into()
}
}
pub fn format_ts(ts: i64, locale: &str, opts: &DateFormatOptions) -> String {
let ms = (ts * 1000) as f64;
let date = js_sys::Date::new(&wasm_bindgen::JsValue::from_f64(ms));
date.to_locale_string("en-GB", &wasm_bindgen::JsValue::UNDEFINED)
date.to_locale_string(locale, &opts.to_js_value())
.as_string()
.unwrap_or_default()
}

View file

@ -32,8 +32,12 @@ pub fn GameDetailPage() -> impl IntoView {
#[component]
fn GameDetailView(game: GameDetail) -> impl IntoView {
let i18n = use_i18n();
let started = api::format_ts(game.started_at);
let ended = game.ended_at.map(api::format_ts)
let locale_tag = match i18n.get_locale() {
Locale::en => "en-GB",
Locale::fr => "fr-FR",
};
let started = api::format_ts(game.started_at, locale_tag, &api::DateFormatOptions::date_only());
let ended = game.ended_at.map(|ts| api::format_ts(ts, locale_tag, &api::DateFormatOptions::date_only()))
.unwrap_or_else(|| t_string!(i18n, game_ongoing).to_string());
view! {

View file

@ -37,7 +37,11 @@ fn ProfileContent(profile: UserProfile, username: String) -> impl IntoView {
async move { api::get_user_games(&u, p).await }
});
let joined = api::format_ts(profile.created_at);
let locale_tag = match i18n.get_locale() {
Locale::en => "en-GB",
Locale::fr => "fr-FR",
};
let joined = api::format_ts(profile.created_at, locale_tag, &api::DateFormatOptions::date_only());
view! {
<div class="portal-card">
@ -84,6 +88,10 @@ fn ProfileContent(profile: UserProfile, username: String) -> impl IntoView {
#[component]
fn GamesTable(games: Vec<GameSummary>, page: RwSignal<i64>) -> impl IntoView {
let i18n = use_i18n();
let locale_tag = match i18n.get_locale() {
Locale::en => "en-GB",
Locale::fr => "fr-FR",
};
let rows = games.clone();
let has_next = games.len() == 20;
@ -100,8 +108,8 @@ fn GamesTable(games: Vec<GameSummary>, page: RwSignal<i64>) -> impl IntoView {
</thead>
<tbody>
{rows.into_iter().map(|g| {
let started = api::format_ts(g.started_at);
let ended = g.ended_at.map(api::format_ts).unwrap_or_else(|| "".into());
let started = api::format_ts(g.started_at, locale_tag, &api::DateFormatOptions::date_only());
let ended = g.ended_at.map(|ts| api::format_ts(ts, locale_tag, &api::DateFormatOptions::date_only())).unwrap_or_else(|| "".into());
let outcome_class = match g.outcome.as_deref() {
Some("win") => "outcome-win",
Some("loss") => "outcome-loss",