+49
-3
src/components/LookUp.vue
+49
-3
src/components/LookUp.vue
···
32
32
const loading = ref(false);
33
33
const artists = ref<{ name: string; plays: number }[]>([]);
34
34
const tracks = ref<{ name: string; artist: string; plays: number }[]>([]);
35
+
const topDays = ref<{ date: string; plays: number }[]>([]);
35
36
const totalSongs = ref(0);
36
37
37
38
const formatNumber = (n: number) => n.toLocaleString();
39
+
40
+
// Returns YYYY-MM-DD in the browser's local time zone
41
+
const localDayKey = (iso?: string): string | null => {
42
+
if (!iso) return null;
43
+
const d = new Date(iso);
44
+
if (isNaN(d.getTime())) return null;
45
+
const y = d.getFullYear();
46
+
const m = (d.getMonth() + 1).toString().padStart(2, "0");
47
+
const day = d.getDate().toString().padStart(2, "0");
48
+
return `${y}-${m}-${day}`;
49
+
};
38
50
39
51
const lookup = async () => {
40
52
loading.value = true;
···
59
71
let totalCount = 0;
60
72
let inner_tracks: { name: string; artist: string; plays: number }[] = [];
61
73
let inner_artists: { name: string; plays: number }[] = [];
74
+
const dayCountMap = new Map<string, number>();
62
75
63
76
let response = await agent.com.atproto.repo.listRecords({
64
77
repo: did,
···
82
95
) {
83
96
continue;
84
97
}
85
-
// new version of lexicon
98
+
// Aggregate by artist(s)
86
99
if (play.value?.artists) {
87
100
for (const artist of play.value?.artists) {
88
101
let alreadyPlayed = inner_artists.find(
···
94
107
plays: 1,
95
108
});
96
109
} else {
97
-
console.log(`Artist already played: ${alreadyPlayed}`);
98
110
alreadyPlayed.plays++;
99
111
}
100
112
}
···
112
124
}
113
125
}
114
126
127
+
// Aggregate by track
115
128
let alreadyPlayed = inner_tracks.find(
116
129
(a) => a.name === play.value.trackName,
117
130
);
···
126
139
} else if (alreadyPlayed) {
127
140
alreadyPlayed.plays++;
128
141
}
142
+
143
+
// Aggregate by local day using playedTime
144
+
const key = localDayKey(play.value?.playedTime);
145
+
if (key) {
146
+
dayCountMap.set(key, (dayCountMap.get(key) ?? 0) + 1);
147
+
}
129
148
}
130
149
131
-
// update reactive values incrementally (top 10)
150
+
// update reactive values incrementally (top 25)
132
151
artists.value = inner_artists
133
152
.sort((a, b) => b.plays - a.plays)
134
153
.slice(0, 25);
···
137
156
.slice(0, 25);
138
157
totalSongs.value = totalCount;
139
158
159
+
// compute top 25 days
160
+
topDays.value = Array.from(dayCountMap.entries())
161
+
.map(([date, plays]) => ({ date, plays }))
162
+
.sort((a, b) => b.plays - a.plays)
163
+
.slice(0, 25);
164
+
140
165
cursor = response.data.cursor;
141
166
if (!cursor) break;
142
167
···
221
246
<td>{{ idx + 1 }}.</td>
222
247
<td>{{ formatNumber(artist.plays) }}</td>
223
248
<td>{{ artist.name }}</td>
249
+
</tr>
250
+
</tbody>
251
+
</table>
252
+
</div>
253
+
</div>
254
+
<div v-if="topDays.length > 0" class="mt-8">
255
+
<h2 class="text-2xl font-bold mb-4">Top Days (Most Songs Played)</h2>
256
+
<div class="overflow-x-auto">
257
+
<table class="table w-full">
258
+
<thead>
259
+
<tr>
260
+
<th>Rank</th>
261
+
<th>Plays</th>
262
+
<th>Date (yyyy-mm-dd)</th>
263
+
</tr>
264
+
</thead>
265
+
<tbody>
266
+
<tr v-for="(day, idx) in topDays" :key="day.date">
267
+
<td>{{ idx + 1 }}.</td>
268
+
<td>{{ formatNumber(day.plays) }}</td>
269
+
<td>{{ day.date }}</td>
224
270
</tr>
225
271
</tbody>
226
272
</table>