feat: add display setting for dates (#1192)
This commit is contained in:
parent
7363355fe0
commit
b444cf8d43
|
|
@ -0,0 +1,57 @@
|
|||
import {UserFactory} from '../../factories/user'
|
||||
import {ProjectFactory} from '../../factories/project'
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {login} from '../../support/authenticateUser'
|
||||
import {DATE_DISPLAY} from '../../../src/constants/dateDisplay'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const createdDate = new Date(Date.UTC(2022, 6, 25, 12))
|
||||
const now = new Date(Date.UTC(2022, 6, 30, 12))
|
||||
|
||||
const expectedFormats = {
|
||||
[DATE_DISPLAY.RELATIVE]: dayjs(createdDate).from(now),
|
||||
[DATE_DISPLAY.MM_DD_YYYY]: dayjs(createdDate).format('MM-DD-YYYY'),
|
||||
[DATE_DISPLAY.DD_MM_YYYY]: dayjs(createdDate).format('DD-MM-YYYY'),
|
||||
[DATE_DISPLAY.YYYY_MM_DD]: dayjs(createdDate).format('YYYY-MM-DD'),
|
||||
[DATE_DISPLAY.MM_SLASH_DD_YYYY]: dayjs(createdDate).format('MM/DD/YYYY'),
|
||||
[DATE_DISPLAY.DD_SLASH_MM_YYYY]: dayjs(createdDate).format('DD/MM/YYYY'),
|
||||
[DATE_DISPLAY.YYYY_SLASH_MM_DD]: dayjs(createdDate).format('YYYY/MM/DD'),
|
||||
[DATE_DISPLAY.DAY_MONTH_YEAR]: new Intl.DateTimeFormat('en', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
}).format(createdDate),
|
||||
[DATE_DISPLAY.WEEKDAY_DAY_MONTH_YEAR]: new Intl.DateTimeFormat('en', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
}).format(createdDate),
|
||||
}
|
||||
|
||||
describe('Date display setting', () => {
|
||||
Object.entries(expectedFormats).forEach(([format, expected]) => {
|
||||
it(`shows ${format}`, () => {
|
||||
const user = UserFactory.create(1, {
|
||||
frontend_settings: JSON.stringify({dateDisplay: format}),
|
||||
})[0]
|
||||
const project = ProjectFactory.create(1, {owner_id: user.id})[0]
|
||||
TaskFactory.truncate()
|
||||
const task = TaskFactory.create(1, {
|
||||
id: 1,
|
||||
project_id: project.id,
|
||||
created_by_id: user.id,
|
||||
created: createdDate.toISOString(),
|
||||
updated: createdDate.toISOString(),
|
||||
})[0]
|
||||
|
||||
cy.clock(now, ['Date'])
|
||||
login(user)
|
||||
cy.visit(`/tasks/${task.id}`)
|
||||
cy.get('.task-view .created time span').should('contain', expected)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1,5 +1,10 @@
|
|||
import {createFakeUserAndLogin} from '../../support/authenticateUser'
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
import {TaskFactory} from '../../factories/task'
|
||||
import {ProjectFactory} from '../../factories/project'
|
||||
import {TaskCommentFactory} from '../../factories/task_comment'
|
||||
|
|
@ -629,10 +634,9 @@ describe('Task', () => {
|
|||
.click()
|
||||
|
||||
const today = new Date()
|
||||
const day = today.toLocaleString('default', {day: 'numeric'})
|
||||
const month = today.toLocaleString('default', {month: 'short'})
|
||||
const year = today.toLocaleString('default', {year: 'numeric'})
|
||||
const date = `${month} ${day}, ${year} 12:00 PM`
|
||||
today.setHours(12)
|
||||
today.setMinutes(0)
|
||||
today.setSeconds(0)
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input .datepicker-popup')
|
||||
|
|
@ -640,7 +644,7 @@ describe('Task', () => {
|
|||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input')
|
||||
.should('contain.text', date)
|
||||
.should('contain.text', dayjs(today).fromNow())
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
|
@ -658,6 +662,9 @@ describe('Task', () => {
|
|||
})
|
||||
|
||||
const today = new Date(2025, 2, 5)
|
||||
today.setHours(12)
|
||||
today.setMinutes(0)
|
||||
today.setSeconds(0)
|
||||
|
||||
cy.visit(`/tasks/${tasks[0].id}`)
|
||||
|
||||
|
|
@ -674,10 +681,6 @@ describe('Task', () => {
|
|||
.contains('Confirm')
|
||||
.click()
|
||||
|
||||
const day = today.toLocaleString('default', {day: 'numeric'})
|
||||
const month = today.toLocaleString('default', {month: 'short'})
|
||||
const year = today.toLocaleString('default', {year: 'numeric'})
|
||||
const date = `${month} ${day}, ${year} 12:00 PM`
|
||||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input .datepicker-popup')
|
||||
|
|
@ -685,7 +688,7 @@ describe('Task', () => {
|
|||
cy.get('.task-view .columns.details .column')
|
||||
.contains('Due Date')
|
||||
.get('.date-input')
|
||||
.should('contain.text', date)
|
||||
.should('contain.text', dayjs(today).fromNow())
|
||||
cy.get('.global-notification')
|
||||
.should('contain', 'Success')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
:disabled="disabled || undefined"
|
||||
@click.stop="toggleDatePopup"
|
||||
>
|
||||
{{ date === null ? chooseDateLabel : formatDateShort(date) }}
|
||||
{{ date === null ? chooseDateLabel : formatDisplayDate(date) }}
|
||||
</SimpleButton>
|
||||
|
||||
<CustomTransition name="fade">
|
||||
|
|
@ -39,7 +39,7 @@ import CustomTransition from '@/components/misc/CustomTransition.vue'
|
|||
import DatepickerInline from '@/components/input/DatepickerInline.vue'
|
||||
import SimpleButton from '@/components/input/SimpleButton.vue'
|
||||
|
||||
import {formatDateShort} from '@/helpers/time/formatDate'
|
||||
import {formatDisplayDate} from '@/helpers/time/formatDate'
|
||||
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
||||
import {createDateFromString} from '@/helpers/time/createDateFromString'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
v-tooltip="formatDateLong(n.created)"
|
||||
class="created"
|
||||
>
|
||||
{{ formatDateSince(n.created) }}
|
||||
{{ formatDisplayDate(n.created) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -95,7 +95,7 @@ import CustomTransition from '@/components/misc/CustomTransition.vue'
|
|||
import User from '@/components/misc/User.vue'
|
||||
import { NOTIFICATION_NAMES as names, type INotification} from '@/modelTypes/INotification'
|
||||
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
||||
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {formatDateLong, formatDisplayDate} from '@/helpers/time/formatDate'
|
||||
import {getDisplayName} from '@/models/user'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
import XButton from '@/components/input/Button.vue'
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@
|
|||
scope="global"
|
||||
>
|
||||
<span v-tooltip="formatDateLong(a.created)">
|
||||
{{ formatDateSince(a.created) }}
|
||||
{{ formatDisplayDate(a.created) }}
|
||||
</span>
|
||||
<User
|
||||
:avatar-size="24"
|
||||
|
|
@ -185,7 +185,7 @@ import type {IAttachment} from '@/modelTypes/IAttachment'
|
|||
import type {ITask} from '@/modelTypes/ITask'
|
||||
|
||||
import {useAttachmentStore} from '@/stores/attachments'
|
||||
import {formatDateSince, formatDateLong} from '@/helpers/time/formatDate'
|
||||
import {formatDisplayDate, formatDateLong} from '@/helpers/time/formatDate'
|
||||
import {uploadFiles, generateAttachmentUrl} from '@/helpers/attachments'
|
||||
import {getHumanSize} from '@/helpers/getHumanSize'
|
||||
import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
|
||||
|
|
|
|||
|
|
@ -53,13 +53,13 @@
|
|||
v-tooltip="formatDateLong(c.created)"
|
||||
class="has-text-grey"
|
||||
>
|
||||
{{ formatDateSince(c.created) }}
|
||||
{{ formatDisplayDate(c.created) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="+new Date(c.created) !== +new Date(c.updated)"
|
||||
v-tooltip="formatDateLong(c.updated)"
|
||||
>
|
||||
· {{ $t('task.comment.edited', {date: formatDateSince(c.updated)}) }}
|
||||
· {{ $t('task.comment.edited', {date: formatDisplayDate(c.updated)}) }}
|
||||
</span>
|
||||
<a
|
||||
v-tooltip="$t('task.comment.permalink')"
|
||||
|
|
@ -221,7 +221,7 @@ import type {ITask} from '@/modelTypes/ITask'
|
|||
|
||||
import {uploadFile} from '@/helpers/attachments'
|
||||
import {success} from '@/message'
|
||||
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {formatDateLong, formatDisplayDate} from '@/helpers/time/formatDate'
|
||||
import {fetchAvatarBlobUrl, getDisplayName} from '@/models/user'
|
||||
import type {IUser} from '@/modelTypes/IUser'
|
||||
import {useConfigStore} from '@/stores/config'
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
keypath="task.detail.created"
|
||||
scope="global"
|
||||
>
|
||||
<span>{{ formatDateSince(task.created) }}</span>
|
||||
<span>{{ formatDisplayDate(task.created) }}</span>
|
||||
{{ getDisplayName(task.createdBy) }}
|
||||
</i18n-t>
|
||||
</time>
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
<script lang="ts" setup>
|
||||
import {computed} from 'vue'
|
||||
import type {ITask} from '@/modelTypes/ITask'
|
||||
import {formatISO, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {formatISO, formatDateLong, formatDisplayDate} from '@/helpers/time/formatDate'
|
||||
import {getDisplayName} from '@/models/user'
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
@ -54,9 +54,9 @@ const props = defineProps<{
|
|||
}>()
|
||||
|
||||
// Computed properties to show the actual date every time it gets updated
|
||||
const updatedSince = computed(() => formatDateSince(props.task.updated))
|
||||
const updatedSince = computed(() => formatDisplayDate(props.task.updated))
|
||||
const updatedFormatted = computed(() => formatDateLong(props.task.updated))
|
||||
const doneSince = computed(() => formatDateSince(props.task.doneAt))
|
||||
const doneSince = computed(() => formatDisplayDate(props.task.doneAt))
|
||||
const doneFormatted = computed(() => formatDateLong(props.task.doneAt))
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<td v-tooltip="+date === 0 ? '' : formatDateLong(date)">
|
||||
<time :datetime="date ? formatISO(date) : undefined">
|
||||
{{ +date === 0 ? '-' : formatDateSince(date) }}
|
||||
{{ +date === 0 ? '-' : formatDisplayDate(date) }}
|
||||
</time>
|
||||
</td>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {formatISO, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {formatISO, formatDateLong, formatDisplayDate} from '@/helpers/time/formatDate'
|
||||
|
||||
withDefaults(defineProps<{
|
||||
date?: Date
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@
|
|||
<Icon :icon="['far', 'calendar-alt']" />
|
||||
</span>
|
||||
<time :datetime="formatISO(task.dueDate)">
|
||||
{{ formatDateSince(task.dueDate) }}
|
||||
{{ formatDisplayDate(task.dueDate) }}
|
||||
</time>
|
||||
</span>
|
||||
<h3>{{ task.title }}</h3>
|
||||
|
|
@ -121,7 +121,7 @@ import type {IProject} from '@/modelTypes/IProject'
|
|||
import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment'
|
||||
import AttachmentService, {PREVIEW_SIZE} from '@/services/attachment'
|
||||
|
||||
import {formatDateLong, formatDateSince, formatISO} from '@/helpers/time/formatDate'
|
||||
import {formatDateLong, formatDisplayDate, formatISO} from '@/helpers/time/formatDate'
|
||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||
import {useTaskStore} from '@/stores/tasks'
|
||||
import AssigneeList from '@/components/tasks/partials/AssigneeList.vue'
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<Popup @update:open="showFormSwitch = null">
|
||||
<template #trigger="{toggle}">
|
||||
<SimpleButton
|
||||
v-tooltip="reminder.reminder && reminder.relativeTo !== null ? formatDateShort(reminder.reminder) : null"
|
||||
v-tooltip="reminder.reminder && reminder.relativeTo !== null ? formatDisplayDate(reminder.reminder) : null"
|
||||
@click.prevent.stop="toggle()"
|
||||
>
|
||||
{{ reminderText }}
|
||||
|
|
@ -77,7 +77,7 @@ import {useI18n} from 'vue-i18n'
|
|||
|
||||
import {type PeriodUnit, secondsToPeriod} from '@/helpers/time/period'
|
||||
import type {ITaskReminder} from '@/modelTypes/ITaskReminder'
|
||||
import {formatDateShort} from '@/helpers/time/formatDate'
|
||||
import {formatDisplayDate} from '@/helpers/time/formatDate'
|
||||
|
||||
import DatepickerInline from '@/components/input/DatepickerInline.vue'
|
||||
import ReminderPeriod from '@/components/tasks/partials/ReminderPeriod.vue'
|
||||
|
|
@ -132,7 +132,7 @@ const reminderText = computed(() => {
|
|||
}
|
||||
|
||||
if (reminder.value.reminder !== null) {
|
||||
return formatDateShort(reminder.value.reminder)
|
||||
return formatDisplayDate(reminder.value.reminder)
|
||||
}
|
||||
|
||||
return t('task.addReminder')
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ import Popup from '@/components/misc/Popup.vue'
|
|||
|
||||
import TaskService from '@/services/task'
|
||||
|
||||
import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate'
|
||||
import {formatDisplayDate, formatISO, formatDateLong} from '@/helpers/time/formatDate'
|
||||
import {success} from '@/message'
|
||||
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
|
@ -286,7 +286,7 @@ function updateDueDate() {
|
|||
return
|
||||
}
|
||||
|
||||
dueDateFormatted.value = formatDateSince(task.value.dueDate)
|
||||
dueDateFormatted.value = formatDisplayDate(task.value.dueDate)
|
||||
}
|
||||
|
||||
const dueDateFormatted = ref('')
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@
|
|||
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
|
||||
class="is-italic"
|
||||
>
|
||||
– {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
|
||||
– {{ $t('task.detail.due', {at: formatDisplayDate(task.dueDate)}) }}
|
||||
</time>
|
||||
</span>
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ import ChecklistSummary from '@/components/tasks/partials/ChecklistSummary.vue'
|
|||
|
||||
import ColorBubble from '@/components/misc/ColorBubble.vue'
|
||||
|
||||
import {formatDateSince, formatISO, formatDateLong} from '@/helpers/time/formatDate'
|
||||
import {formatDisplayDate, formatISO, formatDateLong} from '@/helpers/time/formatDate'
|
||||
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
import AssigneeList from '@/components/tasks/partials/AssigneeList.vue'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
import {computed} from 'vue'
|
||||
import {createSharedComposable} from '@vueuse/core'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
|
||||
export const useDateDisplay = createSharedComposable(() => {
|
||||
const authStore = useAuthStore()
|
||||
const store = computed(() => authStore.settings.frontendSettings.dateDisplay)
|
||||
return {store}
|
||||
})
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
export const DATE_DISPLAY = {
|
||||
RELATIVE: 'relative',
|
||||
MM_DD_YYYY: 'mm-dd-yyyy',
|
||||
DD_MM_YYYY: 'dd-mm-yyyy',
|
||||
YYYY_MM_DD: 'yyyy-mm-dd',
|
||||
MM_SLASH_DD_YYYY: 'mm/dd/yyyy',
|
||||
DD_SLASH_MM_YYYY: 'dd/mm/yyyy',
|
||||
YYYY_SLASH_MM_DD: 'yyyy/mm/dd',
|
||||
DAY_MONTH_YEAR: 'dayMonthYear',
|
||||
WEEKDAY_DAY_MONTH_YEAR: 'weekdayDayMonthYear',
|
||||
} as const
|
||||
|
||||
export type DateDisplay = typeof DATE_DISPLAY[keyof typeof DATE_DISPLAY]
|
||||
|
|
@ -4,6 +4,8 @@ import dayjs from 'dayjs'
|
|||
import {i18n} from '@/i18n'
|
||||
import {createSharedComposable} from '@vueuse/core'
|
||||
import {computed, toValue, type MaybeRefOrGetter} from 'vue'
|
||||
import {useDateDisplay} from '@/composables/useDateDisplay'
|
||||
import {DATE_DISPLAY, type DateDisplay} from '@/constants/dateDisplay'
|
||||
import {DAYJS_LOCALE_MAPPING} from '@/i18n/useDayjsLanguageSync.ts'
|
||||
|
||||
export function dateIsValid(date: Date | null) {
|
||||
|
|
@ -67,3 +69,44 @@ export function useWeekDayFromDate() {
|
|||
|
||||
return computed(() => (date: Date) => dateTimeFormatter.value.format(date))
|
||||
}
|
||||
|
||||
export function formatDisplayDate(date: Date | string | null) {
|
||||
const {store} = useDateDisplay()
|
||||
const current = store.value
|
||||
|
||||
return formatDisplayDateFormat(date, current)
|
||||
}
|
||||
|
||||
export function formatDisplayDateFormat(date: Date | string | null, format: DateDisplay) {
|
||||
if (typeof date === 'string') {
|
||||
date = createDateFromString(date)
|
||||
}
|
||||
|
||||
if (date === null || !dateIsValid(date)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case DATE_DISPLAY.MM_DD_YYYY:
|
||||
return formatDate(date, 'MM-DD-YYYY')
|
||||
case DATE_DISPLAY.DD_MM_YYYY:
|
||||
return formatDate(date, 'DD-MM-YYYY')
|
||||
case DATE_DISPLAY.YYYY_MM_DD:
|
||||
return formatDate(date, 'YYYY-MM-DD')
|
||||
case DATE_DISPLAY.MM_SLASH_DD_YYYY:
|
||||
return formatDate(date, 'MM/DD/YYYY')
|
||||
case DATE_DISPLAY.DD_SLASH_MM_YYYY:
|
||||
return formatDate(date, 'DD/MM/YYYY')
|
||||
case DATE_DISPLAY.YYYY_SLASH_MM_DD:
|
||||
return formatDate(date, 'YYYY/MM/DD')
|
||||
case DATE_DISPLAY.DAY_MONTH_YEAR: {
|
||||
return new Intl.DateTimeFormat(i18n.global.locale.value, {day: 'numeric', month: 'long', year: 'numeric'}).format(date)
|
||||
}
|
||||
case DATE_DISPLAY.WEEKDAY_DAY_MONTH_YEAR: {
|
||||
return new Intl.DateTimeFormat(i18n.global.locale.value, {weekday: 'long', day: 'numeric', month: 'long', year: 'numeric'}).format(date)
|
||||
}
|
||||
case DATE_DISPLAY.RELATIVE:
|
||||
default:
|
||||
return formatDateSince(date)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,6 +103,16 @@
|
|||
"overdueTasksRemindersTime": "Overdue tasks reminder email time",
|
||||
"filterUsedOnOverview": "Saved filter used on the overview page",
|
||||
"minimumPriority": "Minimum visible task priority",
|
||||
"dateDisplay": "Date display format",
|
||||
"dateDisplayOptions": {
|
||||
"relative": "Relative (e.g. 3 days ago)",
|
||||
"mm-dd-yyyy": "MM-DD-YYYY",
|
||||
"dd-mm-yyyy": "DD-MM-YYYY",
|
||||
"yyyy-mm-dd": "YYYY-MM-DD",
|
||||
"mm\/dd\/yyyy": "MM\/DD\/YYYY",
|
||||
"dd\/mm\/yyyy": "DD\/MM\/YYYY",
|
||||
"yyyy\/mm\/dd": "YYYY\/MM\/DD"
|
||||
},
|
||||
"externalUserNameChange": "Your name is managed by your login provider ({provider}). To change it, please update it there instead."
|
||||
},
|
||||
"totp": {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import type {BasicColorSchema} from '@vueuse/core'
|
|||
import type {SupportedLocale} from '@/i18n'
|
||||
import type {DefaultProjectViewKind} from '@/modelTypes/IProjectView'
|
||||
import type {Priority} from '@/constants/priorities'
|
||||
import type {DateDisplay} from '@/constants/dateDisplay'
|
||||
|
||||
export interface IFrontendSettings {
|
||||
playSoundWhenDone: boolean
|
||||
|
|
@ -15,6 +16,7 @@ export interface IFrontendSettings {
|
|||
filterIdUsedOnOverview: IProject['id'] | null
|
||||
defaultView?: DefaultProjectViewKind
|
||||
minimumPriority: Priority
|
||||
dateDisplay: DateDisplay
|
||||
}
|
||||
|
||||
export interface IUserSettings extends IAbstract {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {getBrowserLanguage} from '@/i18n'
|
|||
import {PrefixMode} from '@/modules/parseTaskText'
|
||||
import {DEFAULT_PROJECT_VIEW_SETTINGS} from '@/modelTypes/IProjectView'
|
||||
import {PRIORITIES} from '@/constants/priorities'
|
||||
import {DATE_DISPLAY} from '@/constants/dateDisplay'
|
||||
|
||||
export default class UserSettingsModel extends AbstractModel<IUserSettings> implements IUserSettings {
|
||||
name = ''
|
||||
|
|
@ -24,6 +25,7 @@ export default class UserSettingsModel extends AbstractModel<IUserSettings> impl
|
|||
allowIconChanges: true,
|
||||
defaultView: DEFAULT_PROJECT_VIEW_SETTINGS.FIRST,
|
||||
minimumPriority: PRIORITIES.MEDIUM,
|
||||
dateDisplay: DATE_DISPLAY.RELATIVE,
|
||||
}
|
||||
|
||||
constructor(data: Partial<IUserSettings> = {}) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import {useConfigStore} from '@/stores/config'
|
|||
import UserSettingsModel from '@/models/userSettings'
|
||||
import {MILLISECONDS_A_SECOND} from '@/constants/date'
|
||||
import {PrefixMode} from '@/modules/parseTaskText'
|
||||
import {DATE_DISPLAY} from '@/constants/dateDisplay'
|
||||
import type {IProvider} from '@/types/IProvider'
|
||||
|
||||
function redirectToSpecifiedProvider() {
|
||||
|
|
@ -131,6 +132,7 @@ export const useAuthStore = defineStore('auth', () => {
|
|||
quickAddMagicMode: PrefixMode.Default,
|
||||
colorSchema: 'auto',
|
||||
allowIconChanges: true,
|
||||
dateDisplay: DATE_DISPLAY.RELATIVE,
|
||||
...newSettings.frontendSettings,
|
||||
},
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
>
|
||||
{{
|
||||
$t('user.deletion.scheduled', {
|
||||
date: formatDateShort(deletionScheduledAt),
|
||||
date: formatDisplayDate(deletionScheduledAt),
|
||||
dateSince: formatDateSince(deletionScheduledAt),
|
||||
})
|
||||
}}
|
||||
|
|
@ -55,7 +55,7 @@ import ImportHint from '@/components/home/ImportHint.vue'
|
|||
|
||||
import {getHistory} from '@/modules/projectHistory'
|
||||
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||
import {formatDateShort, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {formatDateSince, formatDisplayDate} from '@/helpers/time/formatDate'
|
||||
import {useDaytimeSalutation} from '@/composables/useDaytimeSalutation'
|
||||
|
||||
import {useProjectStore} from '@/stores/projects'
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import ApiTokenService from '@/services/apiToken'
|
||||
import {computed, onMounted, ref} from 'vue'
|
||||
import {useFlatpickrLanguage} from '@/helpers/useFlatpickrLanguage'
|
||||
import {formatDateShort, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {formatDateSince, formatDisplayDate} from '@/helpers/time/formatDate'
|
||||
import XButton from '@/components/input/Button.vue'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import ApiTokenModel from '@/models/apiTokenModel'
|
||||
|
|
@ -207,7 +207,7 @@ function toggleGroupPermissionsFromChild(group: string, checked: boolean) {
|
|||
</template>
|
||||
</td>
|
||||
<td>
|
||||
{{ formatDateShort(tk.expiresAt) }}
|
||||
{{ formatDisplayDate(tk.expiresAt) }}
|
||||
<p
|
||||
v-if="tk.expiresAt < new Date()"
|
||||
class="has-text-danger"
|
||||
|
|
@ -215,7 +215,7 @@ function toggleGroupPermissionsFromChild(group: string, checked: boolean) {
|
|||
{{ $t('user.settings.apiTokens.expired', {ago: formatDateSince(tk.expiresAt)}) }}
|
||||
</p>
|
||||
</td>
|
||||
<td>{{ formatDateShort(tk.created) }}</td>
|
||||
<td>{{ formatDisplayDate(tk.created) }}</td>
|
||||
<td class="has-text-right">
|
||||
<XButton
|
||||
variant="secondary"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<p>
|
||||
{{
|
||||
$t('user.deletion.scheduled', {
|
||||
date: formatDateShort(deletionScheduledAt),
|
||||
date: formatDisplayDate(deletionScheduledAt),
|
||||
dateSince: formatDateSince(deletionScheduledAt),
|
||||
})
|
||||
}}
|
||||
|
|
@ -116,7 +116,7 @@ import {useI18n} from 'vue-i18n'
|
|||
|
||||
import AccountDeleteService from '@/services/accountDelete'
|
||||
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||
import {formatDateShort, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {formatDateSince, formatDisplayDate} from '@/helpers/time/formatDate'
|
||||
import {useTitle} from '@/composables/useTitle'
|
||||
import {success} from '@/message'
|
||||
import {useAuthStore} from '@/stores/auth'
|
||||
|
|
|
|||
|
|
@ -228,6 +228,22 @@
|
|||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="is-flex is-align-items-center">
|
||||
<span>
|
||||
{{ $t('user.settings.general.dateDisplay') }}
|
||||
</span>
|
||||
<div class="select ml-2">
|
||||
<select v-model="settings.frontendSettings.dateDisplay">
|
||||
<option
|
||||
v-for="(label, value) in dateDisplaySettings"
|
||||
:key="value"
|
||||
:value="value"
|
||||
>{{ label }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="is-flex is-align-items-center">
|
||||
<span>
|
||||
|
|
@ -269,6 +285,7 @@ import Multiselect from '@/components/input/Multiselect.vue'
|
|||
import {SUPPORTED_LOCALES} from '@/i18n'
|
||||
import {createRandomID} from '@/helpers/randomId'
|
||||
import {AuthenticatedHTTPFactory} from '@/helpers/fetcher'
|
||||
import {formatDisplayDateFormat} from '@/helpers/time/formatDate'
|
||||
|
||||
import {useTitle} from '@/composables/useTitle'
|
||||
|
||||
|
|
@ -278,6 +295,7 @@ import type {IUserSettings} from '@/modelTypes/IUserSettings'
|
|||
import {isSavedFilter} from '@/services/savedFilter'
|
||||
import {DEFAULT_PROJECT_VIEW_SETTINGS} from '@/modelTypes/IProjectView'
|
||||
import {PRIORITIES} from '@/constants/priorities'
|
||||
import {DATE_DISPLAY} from '@/constants/dateDisplay'
|
||||
|
||||
defineOptions({name: 'UserSettingsGeneral'})
|
||||
|
||||
|
|
@ -292,6 +310,18 @@ const colorSchemeSettings = computed(() => ({
|
|||
dark: t('user.settings.appearance.colorScheme.dark'),
|
||||
}))
|
||||
|
||||
const dateDisplaySettings = computed(() => ({
|
||||
[DATE_DISPLAY.RELATIVE]: t('user.settings.general.dateDisplayOptions.relative'),
|
||||
[DATE_DISPLAY.MM_DD_YYYY]: t('user.settings.general.dateDisplayOptions.mm-dd-yyyy'),
|
||||
[DATE_DISPLAY.DD_MM_YYYY]: t('user.settings.general.dateDisplayOptions.dd-mm-yyyy'),
|
||||
[DATE_DISPLAY.YYYY_MM_DD]: t('user.settings.general.dateDisplayOptions.yyyy-mm-dd'),
|
||||
[DATE_DISPLAY.MM_SLASH_DD_YYYY]: t('user.settings.general.dateDisplayOptions.mm/dd/yyyy'),
|
||||
[DATE_DISPLAY.DD_SLASH_MM_YYYY]: t('user.settings.general.dateDisplayOptions.dd/mm/yyyy'),
|
||||
[DATE_DISPLAY.YYYY_SLASH_MM_DD]: t('user.settings.general.dateDisplayOptions.yyyy/mm/dd'),
|
||||
[DATE_DISPLAY.DAY_MONTH_YEAR]: formatDisplayDateFormat(new Date(), DATE_DISPLAY.DAY_MONTH_YEAR),
|
||||
[DATE_DISPLAY.WEEKDAY_DAY_MONTH_YEAR]: formatDisplayDateFormat(new Date(), DATE_DISPLAY.WEEKDAY_DAY_MONTH_YEAR),
|
||||
}))
|
||||
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const settings = ref<IUserSettings>({
|
||||
|
|
@ -306,6 +336,7 @@ const settings = ref<IUserSettings>({
|
|||
minimumPriority: authStore.settings.frontendSettings.minimumPriority ?? PRIORITIES.MEDIUM,
|
||||
// Add fallback for old settings that don't have the logo change setting set
|
||||
allowIconChanges: authStore.settings.frontendSettings.allowIconChanges ?? true,
|
||||
dateDisplay: authStore.settings.frontendSettings.dateDisplay ?? DATE_DISPLAY.RELATIVE,
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue