Skip to content

Commit 06c5896

Browse files
committed
feat: show playlist update time in settings dialog
1 parent 070771f commit 06c5896

4 files changed

Lines changed: 83 additions & 9 deletions

File tree

src/components/Playlist_Settings.vue

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { showFormValidationErrors } from '@/helpers/form.validation.alert.js'
2020
import { createAccountOptions } from '@/helpers/account.options.js'
2121
import { compareMediaInfo, createFileSizeSearchTokens, formatDuration, formatFileSize } from '@/helpers/media.format.js'
2222
import { itemsPerPageOptions } from '@/helpers/items.per.page.js'
23+
import { formatRuDateTime } from '@/helpers/date.format.js'
2324
import { getVideoCategoryTitle } from '@/helpers/video.scope.helpers.js'
2425
import {
2526
duplicatePlaylistDescriptionFallbackMessage,
@@ -59,7 +60,9 @@ const { videoPreview } = storeToRefs(videosStore)
5960
const playlist = ref({
6061
title: '',
6162
filename: '',
62-
accountId: props.accountId ?? null
63+
accountId: props.accountId ?? null,
64+
createdAt: null,
65+
updatedAt: null
6366
})
6467
6568
function generatePlaylistFilename() {
@@ -256,6 +259,8 @@ const someVisiblePlaylistItemsSelected = computed(() => (
256259
const playlistButtonText = computed(() => (props.register ? 'Создать' : 'Сохранить'))
257260
const playlistTitleText = computed(() => (props.register ? 'Новый плейлист' : 'Настройки плейлиста' ))
258261
const formKey = computed(() => `${props.register ? 'create' : 'edit'}-${playlist.value.accountId ?? 'none'}`)
262+
const createdAtText = computed(() => formatRuDateTime(playlist.value.createdAt))
263+
const updatedAtText = computed(() => formatRuDateTime(playlist.value.updatedAt))
259264
const faCheckDouble = 'fa-solid fa-check-double'
260265
const faXmark = 'fa-solid fa-xmark'
261266
@@ -296,7 +301,9 @@ if (!props.register) {
296301
playlist.value = {
297302
title: loadedPlaylist.title || '',
298303
filename: loadedPlaylist.filename || '',
299-
accountId: loadedPlaylist.accountId ?? null
304+
accountId: loadedPlaylist.accountId ?? null,
305+
createdAt: loadedPlaylist.createdAt ?? null,
306+
updatedAt: loadedPlaylist.updatedAt ?? null
300307
}
301308
playlistItems.value = normalizePlaylistItems(loadedPlaylist.items)
302309
} catch (err) {
@@ -728,6 +735,13 @@ function onInvalidSubmit(context) {
728735
/>
729736
</div>
730737
738+
<div v-if="!props.register" class="form-group">
739+
<label class="label-1">Создан / изменён:</label>
740+
<div class="form-control input-1 playlist-readonly-value playlist-timestamps-inline" data-test="playlist-timestamps">
741+
<span data-test="playlist-created-updated-at">{{ createdAtText }} / {{ updatedAtText }}</span>
742+
</div>
743+
</div>
744+
731745
<div class="playlist-columns">
732746
<div class="playlist-column">
733747
<div class="playlist-column-header header-with-actions">
@@ -1121,6 +1135,18 @@ function onInvalidSubmit(context) {
11211135
color: var(--primary-color-dark);
11221136
}
11231137
1138+
.playlist-readonly-value {
1139+
background-color: #f8f9fa;
1140+
}
1141+
1142+
.playlist-timestamps-inline {
1143+
display: flex;
1144+
gap: 16px;
1145+
flex-wrap: wrap;
1146+
align-items: center;
1147+
}
1148+
1149+
11241150
@media (max-width: 1100px) {
11251151
.playlist-columns {
11261152
grid-template-columns: 1fr;

src/components/Playlists_List.vue

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const selectWidth = computed(() => estimateSelectWidth(accountOptions.value))
4646
const isBusy = computed(() => loading.value || accountsLoading.value)
4747
const playlistItems = computed(() => (playlists.value || []).map(playlist => ({
4848
...playlist,
49-
updatedAtSortKey: getPlaylistTimestamp(playlist)
49+
updatedAtSortKey: getPlaylistSortTimestamp(playlist)
5050
})))
5151
5252
function ensureSelection(options) {
@@ -99,19 +99,24 @@ function filterPlaylists(value, query, item) {
9999
return [
100100
rawPlaylist.title,
101101
rawPlaylist.filename,
102+
formatPlaylistCreatedAt(rawPlaylist),
102103
formatPlaylistUpdatedAt(rawPlaylist),
103104
...createFileSizeSearchTokens(rawPlaylist.totalFileSizeBytes),
104105
rawPlaylist.totalDurationSeconds,
105106
rawPlaylist.videoCount
106107
].some(field => (field || '').toString().toLocaleLowerCase().includes(q))
107108
}
108109
109-
function getPlaylistTimestamp(item) {
110+
function getPlaylistSortTimestamp(item) {
110111
return item?.updatedAt || item?.createdAt || null
111112
}
112113
114+
function formatPlaylistCreatedAt(item) {
115+
return formatRuDateTime(item?.createdAt)
116+
}
117+
113118
function formatPlaylistUpdatedAt(item) {
114-
return formatRuDateTime(getPlaylistTimestamp(item))
119+
return formatRuDateTime(item?.updatedAt)
115120
}
116121
117122
function createPlaylist() {
@@ -203,7 +208,10 @@ async function deletePlaylist(item) {
203208
{{ formatFileSize(item.totalFileSizeBytes) }}
204209
</template>
205210
<template v-slot:[`item.updatedAtSortKey`]="{ item }">
206-
{{ formatPlaylistUpdatedAt(item) }}
211+
<div class="playlist-timestamps-cell">
212+
<div>{{ formatPlaylistCreatedAt(item) }}</div>
213+
<div>{{ formatPlaylistUpdatedAt(item) }}</div>
214+
</div>
207215
</template>
208216
<template v-slot:[`item.totalDurationSeconds`]="{ item }">
209217
{{ formatDuration(item.totalDurationSeconds) }}
@@ -232,3 +240,13 @@ async function deletePlaylist(item) {
232240
</v-card>
233241
</div>
234242
</template>
243+
244+
<style scoped>
245+
.playlist-timestamps-cell {
246+
display: flex;
247+
flex-direction: column;
248+
gap: 2px;
249+
line-height: 1.2;
250+
}
251+
252+
</style>

tests/Playlist_Settings.spec.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1570,6 +1570,24 @@ describe('Playlist_Settings.vue', () => {
15701570
expect(wrapper.find('[data-test="form"]').exists()).toBe(true)
15711571
})
15721572

1573+
it('shows read-only created and updated timestamps on one line below title in edit mode', async () => {
1574+
playlistsStore.playlist = {
1575+
id: 9,
1576+
title: 'Existing',
1577+
filename: 'existing.m3u',
1578+
accountId: 1,
1579+
createdAt: '2026-06-23T10:00:00',
1580+
updatedAt: '2026-06-24T12:15:30',
1581+
items: []
1582+
}
1583+
1584+
const wrapper = mountSettings({ register: false, id: 9 })
1585+
await flushPromises()
1586+
1587+
expect(wrapper.find('[data-test="playlist-timestamps"]').exists()).toBe(true)
1588+
expect(wrapper.find('[data-test="playlist-created-updated-at"]').text()).toContain('23.06.2026, 10:00:00 / 24.06.2026, 12:15:30')
1589+
})
1590+
15731591
it('error objects without message property use the error itself as the message', async () => {
15741592
videosStore.getAllByAccount = vi.fn().mockRejectedValue({ code: 'ERR_NETWORK' })
15751593
mountSettings({ accountId: 1 })

tests/Playlists_List.spec.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ describe('Playlists_List.vue', () => {
177177
expect(wrapper.text()).toContain('1:05')
178178
})
179179

180-
it('renders updated playlist timestamp column', async () => {
180+
it('renders created and updated playlist timestamps in one column', async () => {
181181
playlistsStore.playlists.value = [{
182182
id: 2,
183183
updatedAt: '2026-06-24T12:15:30',
@@ -187,11 +187,13 @@ describe('Playlists_List.vue', () => {
187187
await flushPromises()
188188

189189
expect(wrapper.find('[data-test="table-header-updatedAtSortKey"]').text()).toBe('Создан/Изменён')
190+
expect(wrapper.text()).toContain('23.06.2026')
191+
expect(wrapper.text()).toContain('10:00:00')
190192
expect(wrapper.text()).toContain('24.06.2026')
191193
expect(wrapper.text()).toContain('12:15:30')
192194
})
193195

194-
it('falls back to created timestamp when updatedAt is missing', async () => {
196+
it('keeps created timestamp visible when updatedAt is missing', async () => {
195197
playlistsStore.playlists.value = [{
196198
id: 3,
197199
updatedAt: null,
@@ -202,16 +204,26 @@ describe('Playlists_List.vue', () => {
202204

203205
expect(wrapper.text()).toContain('23.06.2026')
204206
expect(wrapper.text()).toContain('10:05:40')
207+
expect(wrapper.text()).toContain('—')
205208
})
206209

207-
it('matches playlist search against formatted timestamp', async () => {
210+
it('matches playlist search against formatted created and updated timestamps', async () => {
208211
const wrapper = mount(PlaylistsList, { global: { stubs: globalStubs } })
209212
await flushPromises()
210213

214+
expect(wrapper.vm.filterPlaylists(null, '23.06.2026', {
215+
raw: {
216+
title: 'Playlist',
217+
filename: 'playlist.m3u',
218+
createdAt: '2026-06-23T10:00:00',
219+
updatedAt: '2026-06-24T12:15:30'
220+
}
221+
})).toBe(true)
211222
expect(wrapper.vm.filterPlaylists(null, '24.06.2026', {
212223
raw: {
213224
title: 'Playlist',
214225
filename: 'playlist.m3u',
226+
createdAt: '2026-06-23T10:00:00',
215227
updatedAt: '2026-06-24T12:15:30'
216228
}
217229
})).toBe(true)

0 commit comments

Comments
 (0)