mirror of
https://github.com/sysadminsmedia/homebox.git
synced 2025-12-21 21:33:02 +01:00
* fix: #321 use date-fns for localized datetime formatting * chore: lint fixes for use-formatters * chore: more lint fixes for use-formatters * date and currency localization fixes --------- Co-authored-by: Ádám Kleizer <adkl@boyum-it.com>
This commit is contained in:
@@ -3,13 +3,27 @@
|
|||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text"> {{ label }} </span>
|
<span class="label-text"> {{ label }} </span>
|
||||||
</label>
|
</label>
|
||||||
<VueDatePicker v-model="selected" :enable-time-picker="false" clearable :dark="isDark" :teleport="true" />
|
<VueDatePicker
|
||||||
|
v-model="selected"
|
||||||
|
:enable-time-picker="false"
|
||||||
|
clearable
|
||||||
|
:dark="isDark"
|
||||||
|
:teleport="true"
|
||||||
|
:format="formatDate"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
|
<div v-else class="sm:grid sm:grid-cols-4 sm:items-start sm:gap-4">
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span class="label-text"> {{ label }} </span>
|
<span class="label-text"> {{ label }} </span>
|
||||||
</label>
|
</label>
|
||||||
<VueDatePicker v-model="selected" :enable-time-picker="false" clearable :dark="isDark" :teleport="true" />
|
<VueDatePicker
|
||||||
|
v-model="selected"
|
||||||
|
:enable-time-picker="false"
|
||||||
|
clearable
|
||||||
|
:dark="isDark"
|
||||||
|
:teleport="true"
|
||||||
|
:format="formatDate"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -38,6 +52,8 @@
|
|||||||
|
|
||||||
const isDark = useIsDark();
|
const isDark = useIsDark();
|
||||||
|
|
||||||
|
const formatDate = (date: Date | string | number) => fmtDate(date, "human", "date");
|
||||||
|
|
||||||
const selected = computed<Date | null>({
|
const selected = computed<Date | null>({
|
||||||
get() {
|
get() {
|
||||||
// String
|
// String
|
||||||
|
|||||||
@@ -22,6 +22,6 @@
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmtDate(props.date, props.format);
|
return fmtDate(props.date, props.format, props.datetimeType);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useI18n } from "vue-i18n";
|
import { format, formatDistance } from "date-fns";
|
||||||
import { type UseTimeAgoMessages, type UseTimeAgoUnitNamesDefault } from "@vueuse/core";
|
/* eslint import/namespace: ['error', { allowComputed: true }] */
|
||||||
|
import * as Locales from "date-fns/locale";
|
||||||
|
|
||||||
const cache = {
|
const cache = {
|
||||||
currency: "",
|
currency: "",
|
||||||
@@ -20,105 +21,63 @@ export async function useFormatCurrency() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (value: number | string) => fmtCurrency(value, cache.currency);
|
return (value: number | string) => fmtCurrency(value, cache.currency, getLocaleCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DateTimeFormat = "relative" | "long" | "short" | "human";
|
export type DateTimeFormat = "relative" | "long" | "short" | "human";
|
||||||
export type DateTimeType = "date" | "time" | "datetime";
|
export type DateTimeType = "date" | "time" | "datetime";
|
||||||
|
|
||||||
function ordinalIndicator(num: number) {
|
export function getLocaleCode() {
|
||||||
if (num > 3 && num < 21) return "th";
|
const { $i18nGlobal } = useNuxtApp();
|
||||||
switch (num % 10) {
|
return ($i18nGlobal?.locale?.value as string) ?? "en-US";
|
||||||
case 1:
|
|
||||||
return "st";
|
|
||||||
case 2:
|
|
||||||
return "nd";
|
|
||||||
case 3:
|
|
||||||
return "rd";
|
|
||||||
default:
|
|
||||||
return "th";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLocaleTimeAgo(date: Date) {
|
function getLocaleForDate() {
|
||||||
const { t } = useI18n();
|
const localeCode = getLocaleCode();
|
||||||
|
const lang = localeCode.length > 1 ? localeCode.substring(0, 2) : localeCode;
|
||||||
const I18N_MESSAGES: UseTimeAgoMessages<UseTimeAgoUnitNamesDefault> = {
|
const region = localeCode.length > 2 ? localeCode.substring(3) : "";
|
||||||
justNow: t("components.global.date_time.just-now"),
|
return Locales[(lang + region) as keyof typeof Locales] ?? Locales[lang as keyof typeof Locales] ?? Locales.enUS;
|
||||||
past: n => (n.match(/\d/) ? t("components.global.date_time.ago", [n]) : n),
|
|
||||||
future: n => (n.match(/\d/) ? t("components.global.date_time.in", [n]) : n),
|
|
||||||
month: (n, past) =>
|
|
||||||
n === 1
|
|
||||||
? past
|
|
||||||
? t("components.global.date_time.last-month")
|
|
||||||
: t("components.global.date_time.next-month")
|
|
||||||
: `${n} ${t(`components.global.date_time.months`)}`,
|
|
||||||
year: (n, past) =>
|
|
||||||
n === 1
|
|
||||||
? past
|
|
||||||
? t("components.global.date_time.last-year")
|
|
||||||
: t("components.global.date_time.next-year")
|
|
||||||
: `${n} ${t(`components.global.date_time.years`)}`,
|
|
||||||
day: (n, past) =>
|
|
||||||
n === 1
|
|
||||||
? past
|
|
||||||
? t("components.global.date_time.yesterday")
|
|
||||||
: t("components.global.date_time.tomorrow")
|
|
||||||
: `${n} ${t(`components.global.date_time.days`)}`,
|
|
||||||
week: (n, past) =>
|
|
||||||
n === 1
|
|
||||||
? past
|
|
||||||
? t("components.global.date_time.last-week")
|
|
||||||
: t("components.global.date_time.next-week")
|
|
||||||
: `${n} ${t(`components.global.date_time.weeks`)}`,
|
|
||||||
hour: n => `${n} ${n === 1 ? t("components.global.date_time.hour") : t("components.global.date_time.hours")}`,
|
|
||||||
minute: n => `${n} ${n === 1 ? t("components.global.date_time.minute") : t("components.global.date_time.minutes")}`,
|
|
||||||
second: n => `${n} ${n === 1 ? t("components.global.date_time.second") : t("components.global.date_time.seconds")}`,
|
|
||||||
invalid: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
return useTimeAgo(date, {
|
|
||||||
fullDateFormatter: (date: Date) => date.toLocaleDateString(),
|
|
||||||
messages: I18N_MESSAGES,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fmtDate(value: string | Date, fmt: DateTimeFormat = "human"): string {
|
export function fmtDate(
|
||||||
const months = [
|
value: string | Date | number,
|
||||||
"January",
|
fmt: DateTimeFormat = "human",
|
||||||
"February",
|
type: DateTimeType = "date"
|
||||||
"March",
|
): string {
|
||||||
"April",
|
const dt = typeof value === "string" || typeof value === "number" ? new Date(value) : value;
|
||||||
"May",
|
|
||||||
"June",
|
|
||||||
"July",
|
|
||||||
"August",
|
|
||||||
"September",
|
|
||||||
"October",
|
|
||||||
"November",
|
|
||||||
"December",
|
|
||||||
];
|
|
||||||
|
|
||||||
const dt = typeof value === "string" ? new Date(value) : value;
|
if (!dt || !validDate(dt)) {
|
||||||
if (!dt) {
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validDate(dt)) {
|
const localeOptions = { locale: getLocaleForDate() };
|
||||||
return "";
|
|
||||||
|
if (fmt === "relative") {
|
||||||
|
return `${formatDistance(dt, new Date(), { ...localeOptions, addSuffix: true })} (${fmtDate(dt, "short", "date")})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type === "time") {
|
||||||
|
return format(dt, "p", localeOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
let formatStr = "";
|
||||||
|
|
||||||
switch (fmt) {
|
switch (fmt) {
|
||||||
case "relative":
|
|
||||||
return useLocaleTimeAgo(dt).value + useDateFormat(dt, " (YYYY-MM-DD)").value;
|
|
||||||
case "long":
|
|
||||||
return useDateFormat(dt, "YYYY-MM-DD (dddd)").value;
|
|
||||||
case "short":
|
|
||||||
return useDateFormat(dt, "YYYY-MM-DD").value;
|
|
||||||
case "human":
|
case "human":
|
||||||
// January 1st, 2021
|
formatStr = "PPP";
|
||||||
return `${months[dt.getMonth()]} ${dt.getDate()}${ordinalIndicator(dt.getDate())}, ${dt.getFullYear()}`;
|
break;
|
||||||
|
case "long":
|
||||||
|
formatStr = "PP";
|
||||||
|
break;
|
||||||
|
case "short":
|
||||||
|
formatStr = "P";
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
if (type === "datetime") {
|
||||||
|
formatStr += "p";
|
||||||
|
}
|
||||||
|
|
||||||
|
return format(dt, formatStr, localeOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ export function format(date: Date | string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function zeroTime(date: Date): Date {
|
export function zeroTime(date: Date): Date {
|
||||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
return new Date(
|
||||||
|
new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - date.getTimezoneOffset() * 60000
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function factorRange(offset: number = 7): [Date, Date] {
|
export function factorRange(offset: number = 7): [Date, Date] {
|
||||||
|
|||||||
@@ -99,16 +99,12 @@
|
|||||||
|
|
||||||
let purchasePrice = 0;
|
let purchasePrice = 0;
|
||||||
let soldPrice = 0;
|
let soldPrice = 0;
|
||||||
let purchaseTime = null;
|
|
||||||
if (item.value.purchasePrice) {
|
if (item.value.purchasePrice) {
|
||||||
purchasePrice = item.value.purchasePrice;
|
purchasePrice = item.value.purchasePrice;
|
||||||
}
|
}
|
||||||
if (item.value.soldPrice) {
|
if (item.value.soldPrice) {
|
||||||
soldPrice = item.value.soldPrice;
|
soldPrice = item.value.soldPrice;
|
||||||
}
|
}
|
||||||
if (item.value.purchaseTime && typeof item.value.purchaseTime !== "string") {
|
|
||||||
purchaseTime = new Date(item.value.purchaseTime.getTime() - item.value.purchaseTime.getTimezoneOffset() * 60000);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log((item.value.purchasePrice ??= 0));
|
console.log((item.value.purchasePrice ??= 0));
|
||||||
console.log((item.value.soldPrice ??= 0));
|
console.log((item.value.soldPrice ??= 0));
|
||||||
@@ -121,7 +117,7 @@
|
|||||||
assetId: item.value.assetId,
|
assetId: item.value.assetId,
|
||||||
purchasePrice,
|
purchasePrice,
|
||||||
soldPrice,
|
soldPrice,
|
||||||
purchaseTime: purchaseTime as Date,
|
purchaseTime: item.value.purchaseTime as Date,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { error } = await api.items.update(itemId.value, payload);
|
const { error } = await api.items.update(itemId.value, payload);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
import MdiFill from "~icons/mdi/fill";
|
import MdiFill from "~icons/mdi/fill";
|
||||||
import MdiPencil from "~icons/mdi/pencil";
|
import MdiPencil from "~icons/mdi/pencil";
|
||||||
import MdiAccountMultiple from "~icons/mdi/account-multiple";
|
import MdiAccountMultiple from "~icons/mdi/account-multiple";
|
||||||
|
import { getLocaleCode } from "~/composables/use-formatters";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: ["auth"],
|
middleware: ["auth"],
|
||||||
@@ -52,12 +53,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const currencyExample = computed(() => {
|
const currencyExample = computed(() => {
|
||||||
const formatter = new Intl.NumberFormat("en-US", {
|
return fmtCurrency(1000, currency.value?.code ?? "USD", getLocaleCode());
|
||||||
style: "currency",
|
});
|
||||||
currency: currency.value ? currency.value.code : "USD",
|
|
||||||
});
|
|
||||||
|
|
||||||
return formatter.format(1000);
|
const dateExample = computed(() => {
|
||||||
|
return fmtDate(new Date(Date.now() - 15 * 60000), "relative");
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: group } = useAsyncData(async () => {
|
const { data: group } = useAsyncData(async () => {
|
||||||
@@ -389,6 +389,7 @@
|
|||||||
{{ $t(`languages.${lang}`) }} ({{ $t(`languages.${lang}`, 1, { locale: lang }) }})
|
{{ $t(`languages.${lang}`) }} ({{ $t(`languages.${lang}`, 1, { locale: lang }) }})
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
<p class="m-2 text-sm">{{ $t("profile.example") }}: {{ $t("global.created") }} {{ dateExample }}</p>
|
||||||
</div>
|
</div>
|
||||||
</BaseCard>
|
</BaseCard>
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ export default defineNuxtPlugin(({ vueApp }) => {
|
|||||||
messages: messages(),
|
messages: messages(),
|
||||||
});
|
});
|
||||||
vueApp.use(i18n);
|
vueApp.use(i18n);
|
||||||
|
|
||||||
|
return {
|
||||||
|
provide: {
|
||||||
|
i18nGlobal: i18n.global,
|
||||||
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const messages = () => {
|
export const messages = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user