From b1cc9dbb21e9ba5b52222d45b0e0dc4c7036fe47 Mon Sep 17 00:00:00 2001 From: Daniel Luiz Alves Date: Mon, 23 Jun 2025 17:11:08 -0300 Subject: [PATCH] feat: add bulk actions for file management in received files modal - Implemented bulk selection functionality for files in the ReceivedFilesModal, allowing users to select multiple files for actions such as download, copy, and delete. - Added UI elements for bulk actions, including a dropdown menu for selecting actions and confirmation dialogs for deletion. - Enhanced user experience by providing feedback during bulk operations and clearing selections after successful actions. - Localized new messages for bulk actions across multiple languages to ensure consistent user feedback. --- apps/web/messages/ar-SA.json | 20 +- apps/web/messages/de-DE.json | 18 +- apps/web/messages/en-US.json | 18 +- apps/web/messages/es-ES.json | 18 +- apps/web/messages/fr-FR.json | 18 +- apps/web/messages/hi-IN.json | 20 +- apps/web/messages/it-IT.json | 18 +- apps/web/messages/ja-JP.json | 20 +- apps/web/messages/ko-KR.json | 20 +- apps/web/messages/nl-NL.json | 20 +- apps/web/messages/pl-PL.json | 20 +- apps/web/messages/pt-BR.json | 18 +- apps/web/messages/ru-RU.json | 20 +- apps/web/messages/tr-TR.json | 20 +- apps/web/messages/zh-CN.json | 20 +- .../components/received-files-modal.tsx | 310 +++++++++++++++++- 16 files changed, 573 insertions(+), 25 deletions(-) diff --git a/apps/web/messages/ar-SA.json b/apps/web/messages/ar-SA.json index da358aa..592c302 100644 --- a/apps/web/messages/ar-SA.json +++ b/apps/web/messages/ar-SA.json @@ -1189,7 +1189,23 @@ "editError": "خطأ في تحديث الملف", "previewNotAvailable": "المعاينة غير متوفرة", "copyError": "خطأ نسخ الملف إلى ملفاتك", - "copySuccess": "تم نسخ الملف إلى ملفاتك بنجاح" + "copySuccess": "تم نسخ الملف إلى ملفاتك بنجاح", + "bulkActions": { + "selected": "{count, plural, =0 {لا ملفات محددة} =1 {ملف واحد محدد} =2 {ملفان محددان} other {# ملفات محددة}}", + "actions": "إجراءات", + "download": "تحميل المحدد", + "copyToMyFiles": "نسخ المحدد إلى ملفاتي", + "delete": "حذف المحدد" + }, + "bulkCopyProgress": "جارٍ نسخ {count, plural, =1 {ملف واحد} =2 {ملفين} other {# ملفات}} إلى ملفاتك...", + "bulkCopySuccess": "{count, plural, =1 {تم نسخ ملف واحد إلى ملفاتك بنجاح} =2 {تم نسخ ملفين إلى ملفاتك بنجاح} other {تم نسخ # ملفات إلى ملفاتك بنجاح}}", + "bulkDeleteConfirmButton": "حذف {count, plural, =1 {الملف} =2 {الملفين} other {الملفات}}", + "bulkDeleteConfirmMessage": "هل أنت متأكد أنك تريد حذف {count, plural, =1 {هذا الملف} =2 {هذين الملفين} other {هذه الملفات الـ #}}؟ لا يمكن التراجع عن هذا الإجراء.", + "bulkDeleteConfirmTitle": "حذف الملفات المحددة", + "bulkDeleteProgress": "جارٍ حذف {count, plural, =1 {ملف واحد} =2 {ملفين} other {# ملفات}}...", + "bulkDeleteSuccess": "{count, plural, =1 {تم حذف ملف واحد بنجاح} =2 {تم حذف ملفين بنجاح} other {تم حذف # ملفات بنجاح}}", + "selectAll": "تحديد الكل", + "selectFile": "تحديد الملف {fileName}" } }, "form": { @@ -1397,4 +1413,4 @@ } } } -} +} \ No newline at end of file diff --git a/apps/web/messages/de-DE.json b/apps/web/messages/de-DE.json index a8052c4..e6463d5 100644 --- a/apps/web/messages/de-DE.json +++ b/apps/web/messages/de-DE.json @@ -1189,7 +1189,23 @@ "editError": "Fehler beim Aktualisieren der Datei", "previewNotAvailable": "Vorschau nicht verfügbar", "copyError": "Fehler beim Kopieren der Datei in Ihre Dateien", - "copySuccess": "Datei erfolgreich in Ihre Dateien kopiert" + "copySuccess": "Datei erfolgreich in Ihre Dateien kopiert", + "bulkActions": { + "selected": "{count, plural, =1 {1 Datei ausgewählt} other {# Dateien ausgewählt}}", + "actions": "Aktionen", + "download": "Ausgewählte herunterladen", + "copyToMyFiles": "Ausgewählte in meine Dateien kopieren", + "delete": "Ausgewählte löschen" + }, + "bulkCopyProgress": "{count, plural, =1 {1 Datei wird} other {# Dateien werden}} in Ihre Dateien kopiert...", + "bulkCopySuccess": "{count, plural, =1 {1 Datei wurde erfolgreich in Ihre Dateien kopiert} other {# Dateien wurden erfolgreich in Ihre Dateien kopiert}}", + "bulkDeleteConfirmButton": "{count, plural, =1 {Datei löschen} other {Dateien löschen}}", + "bulkDeleteConfirmMessage": "Sind Sie sicher, dass Sie {count, plural, =1 {diese Datei} other {diese # Dateien}} löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.", + "bulkDeleteConfirmTitle": "Ausgewählte Dateien löschen", + "bulkDeleteProgress": "{count, plural, =1 {1 Datei wird} other {# Dateien werden}} gelöscht...", + "bulkDeleteSuccess": "{count, plural, =1 {1 Datei wurde erfolgreich gelöscht} other {# Dateien wurden erfolgreich gelöscht}}", + "selectAll": "Alle auswählen", + "selectFile": "Datei {fileName} auswählen" } }, "form": { diff --git a/apps/web/messages/en-US.json b/apps/web/messages/en-US.json index 2fa5af5..482f0f2 100644 --- a/apps/web/messages/en-US.json +++ b/apps/web/messages/en-US.json @@ -1189,7 +1189,23 @@ "editError": "Error updating file", "previewNotAvailable": "Preview not available", "copySuccess": "File copied to your files successfully", - "copyError": "Error copying file to your files" + "copyError": "Error copying file to your files", + "bulkCopySuccess": "{count, plural, =1 {1 file copied to your files successfully} other {# files copied to your files successfully}}", + "bulkDeleteSuccess": "{count, plural, =1 {1 file deleted successfully} other {# files deleted successfully}}", + "bulkCopyProgress": "Copying {count, plural, =1 {1 file} other {# files}} to your files...", + "bulkDeleteProgress": "Deleting {count, plural, =1 {1 file} other {# files}}...", + "bulkDeleteConfirmTitle": "Delete Selected Files", + "bulkDeleteConfirmMessage": "Are you sure you want to delete {count, plural, =1 {this file} other {these # files}}? This action cannot be undone.", + "bulkDeleteConfirmButton": "Delete {count, plural, =1 {File} other {Files}}", + "bulkActions": { + "selected": "{count, plural, =1 {1 file selected} other {# files selected}}", + "actions": "Actions", + "download": "Download Selected", + "copyToMyFiles": "Copy Selected to My Files", + "delete": "Delete Selected" + }, + "selectAll": "Select all", + "selectFile": "Select file {fileName}" } }, "form": { diff --git a/apps/web/messages/es-ES.json b/apps/web/messages/es-ES.json index 94d6846..dbd63ca 100644 --- a/apps/web/messages/es-ES.json +++ b/apps/web/messages/es-ES.json @@ -1189,7 +1189,23 @@ "editError": "Error al actualizar archivo", "previewNotAvailable": "Vista previa no disponible", "copyError": "Error de copiar el archivo a sus archivos", - "copySuccess": "Archivo copiado en sus archivos correctamente" + "copySuccess": "Archivo copiado en sus archivos correctamente", + "bulkActions": { + "selected": "{count, plural, =1 {1 archivo seleccionado} other {# archivos seleccionados}}", + "actions": "Acciones", + "download": "Descargar Seleccionados", + "copyToMyFiles": "Copiar Seleccionados a Mis Archivos", + "delete": "Eliminar Seleccionados" + }, + "bulkCopyProgress": "Copiando {count, plural, =1 {1 archivo} other {# archivos}} a tus archivos...", + "bulkCopySuccess": "{count, plural, =1 {1 archivo copiado a tus archivos correctamente} other {# archivos copiados a tus archivos correctamente}}", + "bulkDeleteConfirmButton": "Eliminar {count, plural, =1 {Archivo} other {Archivos}}", + "bulkDeleteConfirmMessage": "¿Estás seguro de que quieres eliminar {count, plural, =1 {este archivo} other {estos # archivos}}? Esta acción no se puede deshacer.", + "bulkDeleteConfirmTitle": "Eliminar Archivos Seleccionados", + "bulkDeleteProgress": "Eliminando {count, plural, =1 {1 archivo} other {# archivos}}...", + "bulkDeleteSuccess": "{count, plural, =1 {1 archivo eliminado correctamente} other {# archivos eliminados correctamente}}", + "selectAll": "Seleccionar todo", + "selectFile": "Seleccionar archivo {fileName}" } }, "form": { diff --git a/apps/web/messages/fr-FR.json b/apps/web/messages/fr-FR.json index df6b037..a1321cd 100644 --- a/apps/web/messages/fr-FR.json +++ b/apps/web/messages/fr-FR.json @@ -1189,7 +1189,23 @@ "editError": "Erreur lors de la mise à jour du fichier", "previewNotAvailable": "Aperçu non disponible", "copyError": "Erreur de copie du fichier dans vos fichiers", - "copySuccess": "Fichier copié dans vos fichiers avec succès" + "copySuccess": "Fichier copié dans vos fichiers avec succès", + "bulkActions": { + "selected": "{count, plural, =1 {1 fichier sélectionné} other {# fichiers sélectionnés}}", + "actions": "Actes", + "download": "Télécharger la sélection", + "copyToMyFiles": "Copier la sélection dans mes fichiers", + "delete": "Supprimer la sélection" + }, + "bulkCopyProgress": "Copie de {count, plural, =1 {1 fichier} other {# fichiers}} dans vos fichiers...", + "bulkCopySuccess": "{count, plural, =1 {1 fichier copié dans vos fichiers avec succès} other {# fichiers copiés dans vos fichiers avec succès}}", + "bulkDeleteConfirmButton": "Supprimer {count, plural, =1 {le fichier} other {les fichiers}}", + "bulkDeleteConfirmMessage": "Êtes-vous sûr de vouloir supprimer {count, plural, =1 {ce fichier} other {ces # fichiers}} ? Cette action est irréversible.", + "bulkDeleteConfirmTitle": "Supprimer les fichiers sélectionnés", + "bulkDeleteProgress": "Suppression de {count, plural, =1 {1 fichier} other {# fichiers}}...", + "bulkDeleteSuccess": "{count, plural, =1 {1 fichier supprimé avec succès} other {# fichiers supprimés avec succès}}", + "selectAll": "Tout sélectionner", + "selectFile": "Sélectionner le fichier {fileName}" } }, "form": { diff --git a/apps/web/messages/hi-IN.json b/apps/web/messages/hi-IN.json index 3fd533d..e5ae8e8 100644 --- a/apps/web/messages/hi-IN.json +++ b/apps/web/messages/hi-IN.json @@ -1189,7 +1189,23 @@ "editError": "फ़ाइल अपडेट करने में त्रुटि", "previewNotAvailable": "पूर्वावलोकन उपलब्ध नहीं है", "copyError": "अपनी फ़ाइलों में फ़ाइल की नकल करने में त्रुटि", - "copySuccess": "आपकी फ़ाइलों को सफलतापूर्वक कॉपी की गई फ़ाइल" + "copySuccess": "आपकी फ़ाइलों को सफलतापूर्वक कॉपी की गई फ़ाइल", + "bulkActions": { + "selected": "{count, plural, =1 {1 फ़ाइल चयनित} other {# फ़ाइलें चयनित}}", + "actions": "कार्रवाइयां", + "download": "चयनित डाउनलोड करें", + "copyToMyFiles": "चयनित को मेरी फ़ाइलों में कॉपी करें", + "delete": "चयनित हटाएं" + }, + "bulkCopyProgress": "{count, plural, =1 {1 फ़ाइल} other {# फ़ाइलें}} आपकी फ़ाइलों में कॉपी की जा रही हैं...", + "bulkCopySuccess": "{count, plural, =1 {1 फ़ाइल सफलतापूर्वक आपकी फ़ाइलों में कॉपी की गई} other {# फ़ाइलें सफलतापूर्वक आपकी फ़ाइलों में कॉपी की गईं}}", + "bulkDeleteConfirmButton": "{count, plural, =1 {फ़ाइल} other {फ़ाइलें}} हटाएं", + "bulkDeleteConfirmMessage": "क्या आप वाकई {count, plural, =1 {इस फ़ाइल} other {इन # फ़ाइलों}} को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।", + "bulkDeleteConfirmTitle": "चयनित फ़ाइलें हटाएं", + "bulkDeleteProgress": "{count, plural, =1 {1 फ़ाइल} other {# फ़ाइलें}} हटाई जा रही हैं...", + "bulkDeleteSuccess": "{count, plural, =1 {1 फ़ाइल सफलतापूर्वक हटाई गई} other {# फ़ाइलें सफलतापूर्वक हटाई गईं}}", + "selectAll": "सभी चुनें", + "selectFile": "फ़ाइल {fileName} चुनें" } }, "form": { @@ -1397,4 +1413,4 @@ } } } -} +} \ No newline at end of file diff --git a/apps/web/messages/it-IT.json b/apps/web/messages/it-IT.json index 3131f51..7830066 100644 --- a/apps/web/messages/it-IT.json +++ b/apps/web/messages/it-IT.json @@ -1189,7 +1189,23 @@ "editError": "Errore durante l'aggiornamento del file", "previewNotAvailable": "Anteprima non disponibile", "copyError": "Errore di copia del file sui tuoi file", - "copySuccess": "File copiato sui tuoi file correttamente" + "copySuccess": "File copiato sui tuoi file correttamente", + "bulkActions": { + "selected": "{count, plural, =1 {1 file selezionato} other {# file selezionati}}", + "actions": "Azioni", + "download": "Scarica Selezionati", + "copyToMyFiles": "Copia Selezionati sui Miei File", + "delete": "Elimina Selezionati" + }, + "bulkCopyProgress": "Copia di {count, plural, =1 {1 file} other {# file}} sui tuoi file in corso...", + "bulkCopySuccess": "{count, plural, =1 {1 file copiato sui tuoi file con successo} other {# file copiati sui tuoi file con successo}}", + "bulkDeleteConfirmButton": "Elimina {count, plural, =1 {File} other {File}}", + "bulkDeleteConfirmMessage": "Sei sicuro di voler eliminare {count, plural, =1 {questo file} other {questi # file}}? Questa azione non può essere annullata.", + "bulkDeleteConfirmTitle": "Elimina File Selezionati", + "bulkDeleteProgress": "Eliminazione di {count, plural, =1 {1 file} other {# file}} in corso...", + "bulkDeleteSuccess": "{count, plural, =1 {1 file eliminato con successo} other {# file eliminati con successo}}", + "selectAll": "Seleziona tutto", + "selectFile": "Seleziona file {fileName}" } }, "form": { diff --git a/apps/web/messages/ja-JP.json b/apps/web/messages/ja-JP.json index 28f2875..eecbd99 100644 --- a/apps/web/messages/ja-JP.json +++ b/apps/web/messages/ja-JP.json @@ -1189,7 +1189,23 @@ "editError": "ファイルの更新に失敗しました", "previewNotAvailable": "プレビューは利用できません", "copyError": "ファイルにファイルをコピーするエラー", - "copySuccess": "ファイルに正常にコピーされたファイル" + "copySuccess": "ファイルに正常にコピーされたファイル", + "bulkActions": { + "selected": "{count, plural, =1 {1ファイルを選択} other {#ファイルを選択}}", + "actions": "アクション", + "download": "選択したファイルをダウンロード", + "copyToMyFiles": "選択したファイルを私のファイルにコピー", + "delete": "選択したファイルを削除" + }, + "bulkCopyProgress": "{count, plural, =1 {1ファイル} other {#ファイル}}を私のファイルにコピー中...", + "bulkCopySuccess": "{count, plural, =1 {1ファイル} other {#ファイル}}を私のファイルに正常にコピーしました", + "bulkDeleteConfirmButton": "{count, plural, =1 {ファイル} other {ファイル}}を削除", + "bulkDeleteConfirmMessage": "{count, plural, =1 {このファイル} other {これらの#ファイル}}を削除してもよろしいですか?この操作は取り消せません。", + "bulkDeleteConfirmTitle": "選択したファイルを削除", + "bulkDeleteProgress": "{count, plural, =1 {1ファイル} other {#ファイル}}を削除中...", + "bulkDeleteSuccess": "{count, plural, =1 {1ファイル} other {#ファイル}}を正常に削除しました", + "selectAll": "すべて選択", + "selectFile": "ファイル{fileName}を選択" } }, "form": { @@ -1397,4 +1413,4 @@ } } } -} +} \ No newline at end of file diff --git a/apps/web/messages/ko-KR.json b/apps/web/messages/ko-KR.json index 5fd30e8..ba2e281 100644 --- a/apps/web/messages/ko-KR.json +++ b/apps/web/messages/ko-KR.json @@ -1189,7 +1189,23 @@ "editError": "파일 업데이트 오류", "previewNotAvailable": "미리보기 불가", "copyError": "파일에 파일을 복사합니다", - "copySuccess": "파일을 파일에 성공적으로 복사했습니다" + "copySuccess": "파일을 파일에 성공적으로 복사했습니다", + "bulkActions": { + "selected": "{count, plural, =1 {1개의 파일 선택됨} other {#개의 파일 선택됨}}", + "actions": "작업", + "download": "선택한 항목 다운로드", + "copyToMyFiles": "선택한 항목을 내 파일로 복사", + "delete": "선택한 항목 삭제" + }, + "bulkCopyProgress": "{count, plural, =1 {1개의 파일} other {#개의 파일}}을(를) 내 파일로 복사하는 중...", + "bulkCopySuccess": "{count, plural, =1 {1개의 파일이} other {#개의 파일이}} 내 파일로 성공적으로 복사됨", + "bulkDeleteConfirmButton": "{count, plural, =1 {파일} other {파일}} 삭제", + "bulkDeleteConfirmMessage": "{count, plural, =1 {이 파일을} other {이 #개의 파일을}} 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.", + "bulkDeleteConfirmTitle": "선택한 파일 삭제", + "bulkDeleteProgress": "{count, plural, =1 {1개의 파일} other {#개의 파일}} 삭제 중...", + "bulkDeleteSuccess": "{count, plural, =1 {1개의 파일이} other {#개의 파일이}} 성공적으로 삭제됨", + "selectAll": "모두 선택", + "selectFile": "{fileName} 파일 선택" } }, "form": { @@ -1397,4 +1413,4 @@ } } } -} +} \ No newline at end of file diff --git a/apps/web/messages/nl-NL.json b/apps/web/messages/nl-NL.json index 40969c2..fb4c55d 100644 --- a/apps/web/messages/nl-NL.json +++ b/apps/web/messages/nl-NL.json @@ -1189,7 +1189,23 @@ "editError": "Fout bij bijwerken bestand", "previewNotAvailable": "Voorvertoning niet beschikbaar", "copyError": "Fout bij het kopiëren van bestand naar uw bestanden", - "copySuccess": "Bestand gekopieerd naar uw bestanden succesvol" + "copySuccess": "Bestand gekopieerd naar uw bestanden succesvol", + "bulkActions": { + "selected": "{count, plural, =1 {1 bestand geselecteerd} other {# bestanden geselecteerd}}", + "actions": "Acties", + "download": "Geselecteerde Downloaden", + "copyToMyFiles": "Geselecteerde Kopiëren naar Mijn Bestanden", + "delete": "Geselecteerde Verwijderen" + }, + "bulkCopyProgress": "{count, plural, =1 {1 bestand} other {# bestanden}} kopiëren naar uw bestanden...", + "bulkCopySuccess": "{count, plural, =1 {1 bestand succesvol gekopieerd naar uw bestanden} other {# bestanden succesvol gekopieerd naar uw bestanden}}", + "bulkDeleteConfirmButton": "{count, plural, =1 {Bestand} other {Bestanden}} Verwijderen", + "bulkDeleteConfirmMessage": "Weet u zeker dat u {count, plural, =1 {dit bestand} other {deze # bestanden}} wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", + "bulkDeleteConfirmTitle": "Geselecteerde Bestanden Verwijderen", + "bulkDeleteProgress": "{count, plural, =1 {1 bestand} other {# bestanden}} verwijderen...", + "bulkDeleteSuccess": "{count, plural, =1 {1 bestand succesvol verwijderd} other {# bestanden succesvol verwijderd}}", + "selectAll": "Alles selecteren", + "selectFile": "Selecteer bestand {fileName}" } }, "form": { @@ -1397,4 +1413,4 @@ } } } -} +} \ No newline at end of file diff --git a/apps/web/messages/pl-PL.json b/apps/web/messages/pl-PL.json index bcf578e..db85d29 100644 --- a/apps/web/messages/pl-PL.json +++ b/apps/web/messages/pl-PL.json @@ -1189,7 +1189,23 @@ "editError": "Błąd aktualizacji pliku", "previewNotAvailable": "Podgląd niedostępny", "copyError": "Plik kopiowania błędów do plików", - "copySuccess": "Plik skopiowany do plików pomyślnie" + "copySuccess": "Plik skopiowany do plików pomyślnie", + "bulkActions": { + "selected": "{count, plural, =1 {1 plik wybrany} other {# plików wybranych}}", + "actions": "Akcje", + "download": "Pobierz wybrane", + "copyToMyFiles": "Skopiuj wybrane do Moich plików", + "delete": "Usuń wybrane" + }, + "bulkCopyProgress": "Kopiowanie {count, plural, =1 {1 pliku} other {# plików}} do twoich plików...", + "bulkCopySuccess": "{count, plural, =1 {1 plik skopiowany pomyślnie do twoich plików} other {# plików skopiowanych pomyślnie do twoich plików}}", + "bulkDeleteConfirmButton": "Usuń {count, plural, =1 {plik} other {pliki}}", + "bulkDeleteConfirmMessage": "Czy na pewno chcesz usunąć {count, plural, =1 {ten plik} other {te # pliki}}? Tej akcji nie można cofnąć.", + "bulkDeleteConfirmTitle": "Usuń wybrane pliki", + "bulkDeleteProgress": "Usuwanie {count, plural, =1 {1 pliku} other {# plików}}...", + "bulkDeleteSuccess": "{count, plural, =1 {1 plik usunięty pomyślnie} other {# plików usuniętych pomyślnie}}", + "selectAll": "Zaznacz wszystko", + "selectFile": "Wybierz plik {fileName}" } }, "form": { @@ -1397,4 +1413,4 @@ } } } -} +} \ No newline at end of file diff --git a/apps/web/messages/pt-BR.json b/apps/web/messages/pt-BR.json index b8b7b26..a0acdfb 100644 --- a/apps/web/messages/pt-BR.json +++ b/apps/web/messages/pt-BR.json @@ -1189,7 +1189,23 @@ "editError": "Erro ao atualizar arquivo", "previewNotAvailable": "Visualização não disponível", "copySuccess": "Arquivo copiado para seus arquivos com sucesso", - "copyError": "Erro ao copiar arquivo para seus arquivos" + "copyError": "Erro ao copiar arquivo para seus arquivos", + "bulkActions": { + "selected": "{count, plural, =1 {1 arquivo selecionado} other {# arquivos selecionados}}", + "actions": "Ações", + "download": "Baixar Selecionados", + "copyToMyFiles": "Copiar Selecionados para Meus Arquivos", + "delete": "Excluir Selecionados" + }, + "bulkCopyProgress": "Copiando {count, plural, =1 {1 arquivo} other {# arquivos}} para seus arquivos...", + "bulkCopySuccess": "{count, plural, =1 {1 arquivo copiado para seus arquivos com sucesso} other {# arquivos copiados para seus arquivos com sucesso}}", + "bulkDeleteConfirmButton": "Excluir {count, plural, =1 {Arquivo} other {Arquivos}}", + "bulkDeleteConfirmMessage": "Tem certeza que deseja excluir {count, plural, =1 {este arquivo} other {estes # arquivos}}? Esta ação não pode ser desfeita.", + "bulkDeleteConfirmTitle": "Excluir Arquivos Selecionados", + "bulkDeleteProgress": "Excluindo {count, plural, =1 {1 arquivo} other {# arquivos}}...", + "bulkDeleteSuccess": "{count, plural, =1 {1 arquivo excluído com sucesso} other {# arquivos excluídos com sucesso}}", + "selectAll": "Selecionar todos", + "selectFile": "Selecionar arquivo {fileName}" } }, "form": { diff --git a/apps/web/messages/ru-RU.json b/apps/web/messages/ru-RU.json index aa67a75..a870fd4 100644 --- a/apps/web/messages/ru-RU.json +++ b/apps/web/messages/ru-RU.json @@ -1189,7 +1189,23 @@ "editError": "Ошибка при обновлении файла", "previewNotAvailable": "Предпросмотр недоступен", "copyError": "Ошибка копирования файла в ваши файлы", - "copySuccess": "Файл успешно скопирован в ваши файлы" + "copySuccess": "Файл успешно скопирован в ваши файлы", + "bulkActions": { + "selected": "{count, plural, =1 {1 файл выбран} other {# файлов выбрано}}", + "actions": "Действия", + "download": "Скачать выбранные", + "copyToMyFiles": "Копировать выбранные в мои файлы", + "delete": "Удалить выбранные" + }, + "bulkCopyProgress": "Копирование {count, plural, =1 {1 файла} other {# файлов}} в ваши файлы...", + "bulkCopySuccess": "{count, plural, =1 {1 файл успешно скопирован в ваши файлы} other {# файлов успешно скопировано в ваши файлы}}", + "bulkDeleteConfirmButton": "Удалить {count, plural, =1 {файл} other {файлы}}", + "bulkDeleteConfirmMessage": "Вы уверены, что хотите удалить {count, plural, =1 {этот файл} other {эти # файлов}}? Это действие нельзя отменить.", + "bulkDeleteConfirmTitle": "Удалить выбранные файлы", + "bulkDeleteProgress": "Удаление {count, plural, =1 {1 файла} other {# файлов}}...", + "bulkDeleteSuccess": "{count, plural, =1 {1 файл успешно удален} other {# файлов успешно удалено}}", + "selectAll": "Выбрать все", + "selectFile": "Выбрать файл {fileName}" } }, "form": { @@ -1397,4 +1413,4 @@ } } } -} +} \ No newline at end of file diff --git a/apps/web/messages/tr-TR.json b/apps/web/messages/tr-TR.json index 06f8a95..da44049 100644 --- a/apps/web/messages/tr-TR.json +++ b/apps/web/messages/tr-TR.json @@ -1189,7 +1189,23 @@ "editError": "Dosya güncellenirken hata oluştu", "previewNotAvailable": "Önizleme mevcut değil", "copyError": "Dosyalarınıza dosyayı kopyalama hatası", - "copySuccess": "Dosyalarınıza başarılı bir şekilde kopyalanan dosya" + "copySuccess": "Dosyalarınıza başarılı bir şekilde kopyalanan dosya", + "bulkActions": { + "selected": "{count, plural, =1 {1 dosya seçildi} other {# dosya seçildi}}", + "actions": "İşlemler", + "download": "Seçilenleri İndir", + "copyToMyFiles": "Seçilenleri Dosyalarıma Kopyala", + "delete": "Seçilenleri Sil" + }, + "bulkCopyProgress": "Dosyalarınıza {count, plural, =1 {1 dosya} other {# dosya}} kopyalanıyor...", + "bulkCopySuccess": "{count, plural, =1 {1 dosya dosyalarınıza başarıyla kopyalandı} other {# dosya dosyalarınıza başarıyla kopyalandı}}", + "bulkDeleteConfirmButton": "{count, plural, =1 {Dosyayı} other {Dosyaları}} Sil", + "bulkDeleteConfirmMessage": "{count, plural, =1 {Bu dosyayı} other {Bu # dosyayı}} silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "bulkDeleteConfirmTitle": "Seçili Dosyaları Sil", + "bulkDeleteProgress": "{count, plural, =1 {1 dosya} other {# dosya}} siliniyor...", + "bulkDeleteSuccess": "{count, plural, =1 {1 dosya başarıyla silindi} other {# dosya başarıyla silindi}}", + "selectAll": "Tümünü seç", + "selectFile": "{fileName} dosyasını seç" } }, "form": { @@ -1397,4 +1413,4 @@ } } } -} +} \ No newline at end of file diff --git a/apps/web/messages/zh-CN.json b/apps/web/messages/zh-CN.json index 3ef72d9..a63a72c 100644 --- a/apps/web/messages/zh-CN.json +++ b/apps/web/messages/zh-CN.json @@ -1189,7 +1189,23 @@ "editError": "更新文件时出错", "previewNotAvailable": "预览不可用", "copyError": "错误将文件复制到您的文件", - "copySuccess": "文件已成功复制到您的文件" + "copySuccess": "文件已成功复制到您的文件", + "bulkActions": { + "selected": "{count, plural, =1 {已选择1个文件} other {已选择#个文件}}", + "actions": "操作", + "download": "下载所选", + "copyToMyFiles": "复制所选到我的文件", + "delete": "删除所选" + }, + "bulkCopyProgress": "正在将{count, plural, =1 {1个文件} other {#个文件}}复制到您的文件...", + "bulkCopySuccess": "{count, plural, =1 {1个文件已成功复制到您的文件} other {#个文件已成功复制到您的文件}}", + "bulkDeleteConfirmButton": "删除{count, plural, =1 {文件} other {文件}}", + "bulkDeleteConfirmMessage": "您确定要删除{count, plural, =1 {这个文件} other {这些#个文件}}吗?此操作无法撤消。", + "bulkDeleteConfirmTitle": "删除所选文件", + "bulkDeleteProgress": "正在删除{count, plural, =1 {1个文件} other {#个文件}}...", + "bulkDeleteSuccess": "{count, plural, =1 {1个文件已成功删除} other {#个文件已成功删除}}", + "selectAll": "全选", + "selectFile": "选择文件 {fileName}" } }, "form": { @@ -1397,4 +1413,4 @@ } } } -} +} \ No newline at end of file diff --git a/apps/web/src/app/(shares)/reverse-shares/components/received-files-modal.tsx b/apps/web/src/app/(shares)/reverse-shares/components/received-files-modal.tsx index 511755a..3476667 100644 --- a/apps/web/src/app/(shares)/reverse-shares/components/received-files-modal.tsx +++ b/apps/web/src/app/(shares)/reverse-shares/components/received-files-modal.tsx @@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "react"; import { IconCheck, + IconChevronDown, IconClipboardCopy, IconDownload, IconEdit, @@ -19,7 +20,21 @@ import { toast } from "sonner"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; -import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator"; @@ -262,6 +277,7 @@ interface FileRowProps { inputRef: React.RefObject; hoveredFile: HoverState | null; copyingFile: string | null; + isSelected: boolean; onStartEdit: (fileId: string, field: string, currentValue: string) => void; onSaveEdit: () => void; onCancelEdit: () => void; @@ -272,6 +288,7 @@ interface FileRowProps { onDownload: (file: ReverseShareFile) => void; onDelete: (file: ReverseShareFile) => void; onCopy: (file: ReverseShareFile) => void; + onSelectFile: (fileId: string, checked: boolean) => void; } function FileRow({ @@ -281,6 +298,7 @@ function FileRow({ inputRef, hoveredFile, copyingFile, + isSelected, onStartEdit, onSaveEdit, onCancelEdit, @@ -291,12 +309,20 @@ function FileRow({ onDownload, onDelete, onCopy, + onSelectFile, }: FileRowProps) { const t = useTranslations(); const { icon: FileIcon, color } = getFileIcon(file.name); return ( + + onSelectFile(file.id, checked)} + aria-label={t("reverseShares.modals.receivedFiles.selectFile", { fileName: file.name })} + /> +
@@ -425,9 +451,19 @@ export function ReceivedFilesModal({ const [previewFile, setPreviewFile] = useState(null); const [hoveredFile, setHoveredFile] = useState(null); const [copyingFile, setCopyingFile] = useState(null); + const [selectedFiles, setSelectedFiles] = useState>(new Set()); + const [bulkCopying, setBulkCopying] = useState(false); + const [bulkDeleting, setBulkDeleting] = useState(false); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [filesToDeleteBulk, setFilesToDeleteBulk] = useState([]); const { editingFile, editValue, setEditValue, inputRef, startEdit, cancelEdit } = useFileEdit(); + // Clear selections when files change + useEffect(() => { + setSelectedFiles(new Set()); + }, [reverseShare?.files]); + const getTotalSize = () => { if (!reverseShare?.files) return "0 B"; const totalBytes = reverseShare.files.reduce((acc, file) => acc + parseInt(file.size), 0); @@ -548,6 +584,176 @@ export function ReceivedFilesModal({ const files = reverseShare.files || []; + const handleSelectAll = (checked: boolean) => { + if (checked) { + setSelectedFiles(new Set(files.map((file) => file.id))); + } else { + setSelectedFiles(new Set()); + } + }; + + const handleSelectFile = (fileId: string, checked: boolean) => { + const newSelected = new Set(selectedFiles); + if (checked) { + newSelected.add(fileId); + } else { + newSelected.delete(fileId); + } + setSelectedFiles(newSelected); + }; + + const getSelectedFileObjects = () => { + return files.filter((file) => selectedFiles.has(file.id)); + }; + + const isAllSelected = files.length > 0 && selectedFiles.size === files.length; + + const handleBulkDownload = async () => { + const selectedFileObjects = getSelectedFileObjects(); + if (selectedFileObjects.length === 0) return; + + try { + toast.promise( + (async () => { + const JSZip = (await import("jszip")).default; + const zip = new JSZip(); + + const downloadPromises = selectedFileObjects.map(async (file) => { + try { + const response = await downloadReverseShareFile(file.id); + const downloadUrl = response.data.url; + const fileResponse = await fetch(downloadUrl); + + if (!fileResponse.ok) { + throw new Error(`Failed to download ${file.name}`); + } + + const blob = await fileResponse.blob(); + zip.file(file.name, blob); + } catch (error) { + console.error(`Error downloading file ${file.name}:`, error); + throw error; + } + }); + + await Promise.all(downloadPromises); + + const zipBlob = await zip.generateAsync({ type: "blob" }); + const zipName = `${reverseShare.name || "received_files"}_files.zip`; + + const url = URL.createObjectURL(zipBlob); + const a = document.createElement("a"); + a.href = url; + a.download = zipName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + // Clear selections after successful download + setSelectedFiles(new Set()); + })(), + { + loading: t("shareManager.creatingZip"), + success: t("shareManager.zipDownloadSuccess"), + error: t("shareManager.zipDownloadError"), + } + ); + } catch (error) { + console.error("Error creating ZIP:", error); + } + }; + + const handleBulkCopyToMyFiles = async () => { + const selectedFileObjects = getSelectedFileObjects(); + if (selectedFileObjects.length === 0) return; + + toast.promise( + (async () => { + setBulkCopying(true); + try { + const copyPromises = selectedFileObjects.map(async (file) => { + try { + await copyReverseShareFileToUserFiles(file.id); + } catch (error: any) { + console.error(`Error copying file ${file.name}:`, error); + throw new Error(`Failed to copy ${file.name}: ${error.response?.data?.error || error.message}`); + } + }); + + await Promise.all(copyPromises); + + // Clear selections after successful copy + setSelectedFiles(new Set()); + } finally { + setBulkCopying(false); + } + })(), + { + loading: t("reverseShares.modals.receivedFiles.bulkCopyProgress", { count: selectedFileObjects.length }), + success: t("reverseShares.modals.receivedFiles.bulkCopySuccess", { count: selectedFileObjects.length }), + error: (error: any) => { + if (error.message.includes("File size exceeds") || error.message.includes("Insufficient storage")) { + return error.message; + } else { + return t("reverseShares.modals.receivedFiles.copyError"); + } + }, + } + ); + }; + + const handleBulkDelete = () => { + const selectedFileObjects = getSelectedFileObjects(); + if (selectedFileObjects.length === 0) return; + + setFilesToDeleteBulk(selectedFileObjects); + setShowDeleteConfirm(true); + }; + + const confirmBulkDelete = async () => { + if (filesToDeleteBulk.length === 0) return; + + setShowDeleteConfirm(false); + + toast.promise( + (async () => { + setBulkDeleting(true); + try { + const deletePromises = filesToDeleteBulk.map(async (file) => { + try { + await deleteReverseShareFile(file.id); + } catch (error) { + console.error(`Error deleting file ${file.name}:`, error); + throw new Error(`Failed to delete ${file.name}`); + } + }); + + await Promise.all(deletePromises); + + // Clear selections and refresh data + setSelectedFiles(new Set()); + setFilesToDeleteBulk([]); + if (onRefresh) { + await onRefresh(); + } + if (refreshReverseShare) { + await refreshReverseShare(reverseShare.id); + } + } finally { + setBulkDeleting(false); + } + })(), + { + loading: t("reverseShares.modals.receivedFiles.bulkDeleteProgress", { count: filesToDeleteBulk.length }), + success: t("reverseShares.modals.receivedFiles.bulkDeleteSuccess", { count: filesToDeleteBulk.length }), + error: "Error deleting selected files", + } + ); + }; + + const showBulkActions = selectedFiles.size > 0; + return ( <> @@ -574,6 +780,59 @@ export function ReceivedFilesModal({ + {showBulkActions && ( +
+
+ + {t("reverseShares.modals.receivedFiles.bulkActions.selected", { count: selectedFiles.size })} + +
+
+ + + + + + + + {t("reverseShares.modals.receivedFiles.bulkActions.download")} + + + {bulkCopying ? ( +
+ ) : ( + + )} + {t("reverseShares.modals.receivedFiles.bulkActions.copyToMyFiles")} +
+ + {bulkDeleting ? ( +
+ ) : ( + + )} + {t("reverseShares.modals.receivedFiles.bulkActions.delete")} +
+
+
+ +
+
+ )} + {files.length === 0 ? (
@@ -591,6 +850,13 @@ export function ReceivedFilesModal({ + + + {t("reverseShares.modals.receivedFiles.columns.file")} {t("reverseShares.modals.receivedFiles.columns.size")} {t("reverseShares.modals.receivedFiles.columns.sender")} @@ -610,6 +876,7 @@ export function ReceivedFilesModal({ inputRef={inputRef} hoveredFile={hoveredFile} copyingFile={copyingFile} + isSelected={selectedFiles.has(file.id)} onStartEdit={startEdit} onSaveEdit={saveEdit} onCancelEdit={cancelEdit} @@ -620,6 +887,7 @@ export function ReceivedFilesModal({ onDownload={handleDownload} onDelete={handleDeleteFile} onCopy={handleCopyFile} + onSelectFile={handleSelectFile} /> ))} @@ -630,6 +898,46 @@ export function ReceivedFilesModal({ + {/* Delete Confirmation Modal */} + + + + {t("reverseShares.modals.receivedFiles.bulkDeleteConfirmTitle")} + + {t("reverseShares.modals.receivedFiles.bulkDeleteConfirmMessage", { count: filesToDeleteBulk.length })} + + + +
+
+ {filesToDeleteBulk.map((file) => { + const { icon: FileIcon, color } = getFileIcon(file.name); + return ( +
+ + + {file.name} + +
+ ); + })} +
+
+ + + + + +
+
+ {previewFile && (