feat(activities): attach voice and files on activity notes

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-02 16:34:03 +03:30
co-authored by Cursor
parent 43437b0280
commit 06977d27ed
9 changed files with 1009 additions and 117 deletions
+55 -6
View File
@@ -57,7 +57,7 @@ const loading = ref(true);
const saving = ref(false);
const error = ref('');
const activity = ref<ActivityDto | null>(null);
const imageUrls = ref<Array<{ id: string; name: string; url: string }>>([]);
const imageUrls = ref<Array<{ id: string; name: string; url: string; kind: 'image' | 'audio' | 'file' }>>([]);
const moreOpen = ref(false);
const commentOpen = ref(false);
const postponeOpen = ref(false);
@@ -174,15 +174,22 @@ function clearImageUrls() {
async function loadActivityImages(row: ActivityDto) {
clearImageUrls();
const next: Array<{ id: string; name: string; url: string }> = [];
const next: Array<{ id: string; name: string; url: string; kind: 'image' | 'audio' | 'file' }> = [];
for (const image of row.images ?? []) {
const mime = image.mimeType.toLowerCase();
const kind = mime.startsWith('audio/') ? 'audio' : mime.startsWith('image/') ? 'image' : 'file';
try {
const { data } = await api.get<Blob>(`/activities/${row.id}/images/${image.id}`, {
responseType: 'blob',
});
next.push({ id: image.id, name: image.fileName, url: URL.createObjectURL(data) });
next.push({
id: image.id,
name: image.fileName,
url: URL.createObjectURL(data),
kind,
});
} catch {
/* عکس در دسترس نیست */
/* فایل در دسترس نیست */
}
}
imageUrls.value = next;
@@ -560,11 +567,31 @@ onMounted(() => {
<div v-show="noteOpen">
<p v-if="activity.description?.trim()">{{ activity.description.trim() }}</p>
<p v-else-if="!imageUrls.length" class="empty">یادداشتی ثبت نشده.</p>
<div v-if="imageUrls.length" class="note-photos">
<a v-for="image in imageUrls" :key="image.id" :href="image.url" target="_blank" rel="noopener">
<div v-if="imageUrls.some((image) => image.kind === 'image')" class="note-photos">
<a
v-for="image in imageUrls.filter((item) => item.kind === 'image')"
:key="image.id"
:href="image.url"
target="_blank"
rel="noopener"
>
<img :src="image.url" :alt="image.name" />
</a>
</div>
<div v-if="imageUrls.some((image) => image.kind === 'audio')" class="note-voices">
<audio
v-for="image in imageUrls.filter((item) => item.kind === 'audio')"
:key="image.id"
:src="image.url"
controls
preload="metadata"
/>
</div>
<ul v-if="imageUrls.some((image) => image.kind === 'file')" class="note-files">
<li v-for="image in imageUrls.filter((item) => item.kind === 'file')" :key="image.id">
<a :href="image.url" :download="image.name">{{ image.name }}</a>
</li>
</ul>
</div>
</article>
@@ -967,6 +994,28 @@ dd {
border: 1px solid #e2e8f0;
}
.note-voices {
display: grid;
gap: 0.45rem;
margin-top: 0.75rem;
}
.note-voices audio {
width: 100%;
}
.note-files {
list-style: none;
margin: 0.75rem 0 0;
padding: 0;
display: grid;
gap: 0.35rem;
}
.note-files a {
color: #0f766e;
}
.dock {
position: fixed;
inset-inline: 0.85rem;