mirror of
https://github.com/kyantech/Palmr.git
synced 2025-11-02 21:13:17 +00:00
feat: enhance file sharing and management features
- Added support for file descriptions in sharing modals, allowing users to provide additional context when sharing files. - Implemented bulk sharing functionality, enabling users to share multiple files at once with a single action. - Enhanced file management capabilities with new modals for bulk downloads and deletions, improving user experience. - Updated localization files to include new strings related to file sharing and management across multiple languages. - Improved the file manager to handle bulk actions and state management more effectively.
This commit is contained in:
@@ -15,13 +15,15 @@
|
||||
"createShare": {
|
||||
"title": "إنشاء مشاركة",
|
||||
"nameLabel": "اسم المشاركة",
|
||||
"descriptionLabel": "الوصف",
|
||||
"descriptionPlaceholder": "أدخل وصفًا (اختياري)",
|
||||
"expirationLabel": "تاريخ الانتهاء",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"maxViewsLabel": "أقصى عدد للمشاهدات",
|
||||
"maxViewsPlaceholder": "اتركه فارغاً للمحدودية غير المحدودة",
|
||||
"passwordProtection": "حماية بكلمة المرور",
|
||||
"maxViewsLabel": "الحد الأقصى للمشاهدات",
|
||||
"maxViewsPlaceholder": "اتركه فارغًا للحصول على عدد غير محدود",
|
||||
"passwordProtection": "محمي بكلمة مرور",
|
||||
"passwordLabel": "كلمة المرور",
|
||||
"create": "إنشاء المشاركة",
|
||||
"create": "إنشاء مشاركة",
|
||||
"success": "تم إنشاء المشاركة بنجاح",
|
||||
"error": "فشل في إنشاء المشاركة"
|
||||
},
|
||||
@@ -82,20 +84,27 @@
|
||||
"saveChanges": "احفظ التغييرات"
|
||||
},
|
||||
"files": {
|
||||
"title": "كل الملفات",
|
||||
"title": "جميع الملفات",
|
||||
"uploadFile": "رفع ملف",
|
||||
"loadError": "فشل في تحميل الملفات",
|
||||
"pageTitle": "ملفاتي",
|
||||
"breadcrumb": "ملفاتي",
|
||||
"downloadStart": "بدأ التنزيل",
|
||||
"downloadError": "فشل في تنزيل الملف",
|
||||
"downloadStart": "بدأ التحميل",
|
||||
"downloadError": "فشل في تحميل الملف",
|
||||
"updateSuccess": "تم تحديث الملف بنجاح",
|
||||
"updateError": "فشل في تحديث الملف",
|
||||
"deleteSuccess": "تم حذف الملف بنجاح",
|
||||
"deleteError": "فشل في حذف الملف"
|
||||
"deleteError": "فشل في حذف الملف",
|
||||
"bulkDownloadSuccess": "بدأ تحميل الملفات بنجاح",
|
||||
"bulkDownloadError": "خطأ في إنشاء ملف ZIP",
|
||||
"bulkDownloadFileError": "خطأ في تحميل الملف {fileName}",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {تم حذف ملف واحد بنجاح} other {تم حذف # ملفات بنجاح}}",
|
||||
"bulkDeleteError": "خطأ في حذف الملفات المحددة"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "جدول الملفات",
|
||||
"selectAll": "تحديد الكل",
|
||||
"selectFile": "تحديد الملف {fileName}",
|
||||
"columns": {
|
||||
"name": "الاسم",
|
||||
"description": "الوصف",
|
||||
@@ -106,11 +115,18 @@
|
||||
},
|
||||
"actions": {
|
||||
"menu": "قائمة إجراءات الملف",
|
||||
"preview": "المعاينة",
|
||||
"edit": "تعديل",
|
||||
"preview": "معاينة",
|
||||
"edit": "تحرير",
|
||||
"share": "مشاركة",
|
||||
"download": "تحميل",
|
||||
"delete": "حذف"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {تم تحديد ملف واحد} other {تم تحديد # ملفات}}",
|
||||
"actions": "الإجراءات",
|
||||
"download": "تحميل المحدد",
|
||||
"share": "مشاركة المحدد",
|
||||
"delete": "حذف المحدد"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -457,42 +473,53 @@
|
||||
},
|
||||
"shareActions": {
|
||||
"deleteTitle": "حذف المشاركة",
|
||||
"deleteConfirmation": "هل أنت متأكد من رغبتك في حذف هذه المشاركة؟ هذا الإجراء لا يمكن التراجع عنه.",
|
||||
"editTitle": "تعديل المشاركة",
|
||||
"deleteConfirmation": "هل أنت متأكد من أنك تريد حذف هذه المشاركة؟ لا يمكن التراجع عن هذا الإجراء.",
|
||||
"editTitle": "تحرير المشاركة",
|
||||
"nameLabel": "اسم المشاركة",
|
||||
"expirationLabel": "تاريخ الانتهاء",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"maxViewsLabel": "أقصى عدد للمشاهدات",
|
||||
"maxViewsPlaceholder": "اتركه فارغاً للمحدودية غير المحدودة",
|
||||
"passwordProtection": "محمي بكلمة المرور",
|
||||
"descriptionLabel": "الوصف",
|
||||
"descriptionPlaceholder": "أدخل وصفاً (اختياري)",
|
||||
"expirationLabel": "تاريخ انتهاء الصلاحية",
|
||||
"expirationPlaceholder": "YYYY/MM/DD HH:MM",
|
||||
"maxViewsLabel": "الحد الأقصى للمشاهدات",
|
||||
"maxViewsPlaceholder": "اتركه فارغاً للمشاهدات غير المحدودة",
|
||||
"passwordProtection": "محمي بكلمة مرور",
|
||||
"passwordLabel": "كلمة المرور",
|
||||
"passwordPlaceholder": "أدخل كلمة المرور",
|
||||
"newPasswordLabel": "كلمة مرور جديدة (اتركه فارغاً للاحتفاظ بالحالي)",
|
||||
"newPasswordPlaceholder": "أدخل كلمة مرور جديدة",
|
||||
"newPasswordLabel": "كلمة المرور الجديدة (اتركها فارغة للاحتفاظ بالحالية)",
|
||||
"newPasswordPlaceholder": "أدخل كلمة المرور الجديدة",
|
||||
"manageFilesTitle": "إدارة الملفات",
|
||||
"manageRecipientsTitle": "إدارة المستلمين",
|
||||
"manageRecipientsTitle": "إدارة المستقبلين",
|
||||
"editSuccess": "تم تحديث المشاركة بنجاح",
|
||||
"editError": "فشل في تحديث المشاركة"
|
||||
},
|
||||
"shareDetails": {
|
||||
"title": "تفاصيل المشاركة",
|
||||
"subtitle": "معلومات مفصلة عن هذه المشاركة",
|
||||
"subtitle": "معلومات تفصيلية حول هذه المشاركة",
|
||||
"basicInfo": "المعلومات الأساسية",
|
||||
"name": "الاسم",
|
||||
"description": "الوصف",
|
||||
"noDescription": "لم يتم توفير وصف",
|
||||
"untitled": "بدون عنوان",
|
||||
"shareLink": "رابط المشاركة",
|
||||
"editLink": "تحرير الرابط",
|
||||
"generateLink": "إنشاء رابط",
|
||||
"noLink": "لم يتم إنشاء رابط بعد",
|
||||
"copyLink": "نسخ الرابط",
|
||||
"openLink": "فتح في علامة تبويب جديدة",
|
||||
"linkCopied": "تم نسخ الرابط إلى الحافظة",
|
||||
"views": "المشاهدات",
|
||||
"dates": "التواريخ",
|
||||
"created": "تم الإنشاء",
|
||||
"expires": "تنتهي",
|
||||
"never": "أبدًا",
|
||||
"expires": "ينتهي",
|
||||
"never": "أبداً",
|
||||
"security": "الأمان",
|
||||
"passwordProtected": "محمي بكلمة المرور",
|
||||
"publicAccess": "الوصول العام",
|
||||
"maxViews": "أقصى عدد للمشاهدات:",
|
||||
"passwordProtected": "محمي بكلمة مرور",
|
||||
"publicAccess": "وصول عام",
|
||||
"maxViews": "المشاهدات القصوى:",
|
||||
"files": "الملفات",
|
||||
"recipients": "المستلمون",
|
||||
"notAvailable": "غير متوفر",
|
||||
"invalidDate": "تاريخ غير صالح",
|
||||
"recipients": "المستقبلون",
|
||||
"notAvailable": "غير متاح",
|
||||
"invalidDate": "تاريخ غير صحيح",
|
||||
"loadError": "فشل في تحميل تفاصيل المشاركة"
|
||||
},
|
||||
"shareManager": {
|
||||
@@ -538,38 +565,39 @@
|
||||
},
|
||||
"sharesTable": {
|
||||
"ariaLabel": "جدول المشاركات",
|
||||
"never": "أبدًا",
|
||||
"never": "أبداً",
|
||||
"columns": {
|
||||
"name": "الاسم",
|
||||
"description": "الوصف",
|
||||
"createdAt": "تاريخ الإنشاء",
|
||||
"expiresAt": "تاريخ الانتهاء",
|
||||
"expiresAt": "تاريخ انتهاء الصلاحية",
|
||||
"status": "الحالة",
|
||||
"security": "الأمان",
|
||||
"files": "الملفات",
|
||||
"recipients": "المستلمون",
|
||||
"recipients": "المستقبلون",
|
||||
"actions": "الإجراءات"
|
||||
},
|
||||
"status": {
|
||||
"neverExpires": "لا تنتهي",
|
||||
"active": "نشطة",
|
||||
"expired": "منتهية"
|
||||
"neverExpires": "لا تنتهي صلاحيتها أبداً",
|
||||
"active": "نشط",
|
||||
"expired": "منتهي الصلاحية"
|
||||
},
|
||||
"security": {
|
||||
"protected": "محمي",
|
||||
"public": "عام"
|
||||
},
|
||||
"filesCount": "ملف",
|
||||
"recipientsCount": "مستلم",
|
||||
"filesCount": "ملفات",
|
||||
"recipientsCount": "مستقبلين",
|
||||
"actions": {
|
||||
"menu": "قائمة إجراءات المشاركة",
|
||||
"edit": "تعديل",
|
||||
"edit": "تحرير",
|
||||
"manageFiles": "إدارة الملفات",
|
||||
"manageRecipients": "إدارة المستلمين",
|
||||
"manageRecipients": "إدارة المستقبلين",
|
||||
"viewDetails": "عرض التفاصيل",
|
||||
"generateLink": "إنشاء الرابط",
|
||||
"editLink": "تعديل الرابط",
|
||||
"generateLink": "إنشاء رابط",
|
||||
"editLink": "تحرير الرابط",
|
||||
"copyLink": "نسخ الرابط",
|
||||
"notifyRecipients": "إعلام المستلمين",
|
||||
"notifyRecipients": "إشعار المستقبلين",
|
||||
"delete": "حذف"
|
||||
}
|
||||
},
|
||||
@@ -694,14 +722,16 @@
|
||||
"passwordRequired": "كلمة المرور مطلوبة"
|
||||
},
|
||||
"shareFile": {
|
||||
"title": "مشاركة الملف",
|
||||
"linkTitle": "إنشاء الرابط",
|
||||
"title": "مشاركة ملف",
|
||||
"linkTitle": "إنشاء رابط",
|
||||
"nameLabel": "اسم المشاركة",
|
||||
"namePlaceholder": "أدخل اسم المشاركة",
|
||||
"descriptionLabel": "الوصف",
|
||||
"descriptionPlaceholder": "أدخل وصفاً (اختياري)",
|
||||
"expirationLabel": "تاريخ انتهاء الصلاحية",
|
||||
"expirationPlaceholder": "يوم/شهر/سنة ساعة:دقيقة",
|
||||
"expirationPlaceholder": "YYYY/MM/DD HH:MM",
|
||||
"maxViewsLabel": "الحد الأقصى للمشاهدات",
|
||||
"maxViewsPlaceholder": "اتركه فارغاً للمشاهدات اللامحدودة",
|
||||
"maxViewsPlaceholder": "اتركه فارغاً للمشاهدات غير المحدودة",
|
||||
"passwordProtection": "محمي بكلمة مرور",
|
||||
"passwordLabel": "كلمة المرور",
|
||||
"passwordPlaceholder": "أدخل كلمة المرور",
|
||||
@@ -710,7 +740,29 @@
|
||||
"aliasPlaceholder": "أدخل اسماً مستعاراً مخصصاً",
|
||||
"linkReady": "رابط المشاركة جاهز:",
|
||||
"createShare": "إنشاء مشاركة",
|
||||
"generateLink": "إنشاء الرابط",
|
||||
"generateLink": "إنشاء رابط",
|
||||
"copyLink": "نسخ الرابط"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "التحميل بالجملة",
|
||||
"zipNameLabel": "اسم ملف ZIP",
|
||||
"zipNamePlaceholder": "أدخل اسم الملف",
|
||||
"description": "{count, plural, =1 {سيتم ضغط ملف واحد} other {سيتم ضغط # ملفات}}",
|
||||
"download": "تحميل ZIP"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "الملفات المراد حذفها"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "مشاركة ملفات متعددة",
|
||||
"shareNameLabel": "اسم المشاركة",
|
||||
"shareNamePlaceholder": "أدخل اسم المشاركة",
|
||||
"descriptionLabel": "الوصف",
|
||||
"descriptionPlaceholder": "أدخل وصفًا (اختياري)",
|
||||
"filesToShare": "الملفات للمشاركة",
|
||||
"files": "ملفات",
|
||||
"totalSize": "الحجم الإجمالي",
|
||||
"creating": "جاري الإنشاء...",
|
||||
"create": "إنشاء مشاركة"
|
||||
}
|
||||
}
|
||||
@@ -13,15 +13,17 @@
|
||||
"back": "Zurück"
|
||||
},
|
||||
"createShare": {
|
||||
"title": "Freigabe erstellen",
|
||||
"nameLabel": "Freigabename",
|
||||
"title": "Freigabe Erstellen",
|
||||
"nameLabel": "Freigabe-Name",
|
||||
"descriptionLabel": "Beschreibung",
|
||||
"descriptionPlaceholder": "Geben Sie eine Beschreibung ein (optional)",
|
||||
"expirationLabel": "Ablaufdatum",
|
||||
"expirationPlaceholder": "MM/TT/JJJJ SS:MM",
|
||||
"expirationPlaceholder": "TT.MM.JJJJ HH:MM",
|
||||
"maxViewsLabel": "Maximale Ansichten",
|
||||
"maxViewsPlaceholder": "Leer lassen für unbegrenzt",
|
||||
"passwordProtection": "Passwortgeschützt",
|
||||
"passwordProtection": "Passwort-geschützt",
|
||||
"passwordLabel": "Passwort",
|
||||
"create": "Freigabe erstellen",
|
||||
"create": "Freigabe Erstellen",
|
||||
"success": "Freigabe erfolgreich erstellt",
|
||||
"error": "Fehler beim Erstellen der Freigabe"
|
||||
},
|
||||
@@ -83,7 +85,7 @@
|
||||
},
|
||||
"files": {
|
||||
"title": "Alle Dateien",
|
||||
"uploadFile": "Datei hochladen",
|
||||
"uploadFile": "Datei Hochladen",
|
||||
"loadError": "Fehler beim Laden der Dateien",
|
||||
"pageTitle": "Meine Dateien",
|
||||
"breadcrumb": "Meine Dateien",
|
||||
@@ -92,10 +94,17 @@
|
||||
"updateSuccess": "Datei erfolgreich aktualisiert",
|
||||
"updateError": "Fehler beim Aktualisieren der Datei",
|
||||
"deleteSuccess": "Datei erfolgreich gelöscht",
|
||||
"deleteError": "Fehler beim Löschen der Datei"
|
||||
"deleteError": "Fehler beim Löschen der Datei",
|
||||
"bulkDownloadSuccess": "Datei-Download erfolgreich gestartet",
|
||||
"bulkDownloadError": "Fehler beim Erstellen der ZIP-Datei",
|
||||
"bulkDownloadFileError": "Fehler beim Herunterladen der Datei {fileName}",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {1 Datei erfolgreich gelöscht} other {# Dateien erfolgreich gelöscht}}",
|
||||
"bulkDeleteError": "Fehler beim Löschen der ausgewählten Dateien"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Dateitabelle",
|
||||
"ariaLabel": "Dateien-Tabelle",
|
||||
"selectAll": "Alle auswählen",
|
||||
"selectFile": "Datei {fileName} auswählen",
|
||||
"columns": {
|
||||
"name": "NAME",
|
||||
"description": "BESCHREIBUNG",
|
||||
@@ -111,6 +120,13 @@
|
||||
"share": "Teilen",
|
||||
"download": "Herunterladen",
|
||||
"delete": "Löschen"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {1 Datei ausgewählt} other {# Dateien ausgewählt}}",
|
||||
"actions": "Aktionen",
|
||||
"download": "Ausgewählte Herunterladen",
|
||||
"share": "Ausgewählte Teilen",
|
||||
"delete": "Ausgewählte Löschen"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -456,44 +472,55 @@
|
||||
"pageTitle": "Freigabe"
|
||||
},
|
||||
"shareActions": {
|
||||
"deleteTitle": "Freigabe löschen",
|
||||
"deleteTitle": "Freigabe Löschen",
|
||||
"deleteConfirmation": "Sind Sie sicher, dass Sie diese Freigabe löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
"editTitle": "Freigabe bearbeiten",
|
||||
"nameLabel": "Freigabename",
|
||||
"editTitle": "Freigabe Bearbeiten",
|
||||
"nameLabel": "Freigabe-Name",
|
||||
"descriptionLabel": "Beschreibung",
|
||||
"descriptionPlaceholder": "Geben Sie eine Beschreibung ein (optional)",
|
||||
"expirationLabel": "Ablaufdatum",
|
||||
"expirationPlaceholder": "MM/TT/JJJJ SS:MM",
|
||||
"expirationPlaceholder": "TT.MM.JJJJ HH:MM",
|
||||
"maxViewsLabel": "Maximale Ansichten",
|
||||
"maxViewsPlaceholder": "Leer lassen für unbegrenzt",
|
||||
"passwordProtection": "Passwortgeschützt",
|
||||
"passwordProtection": "Passwort-geschützt",
|
||||
"passwordLabel": "Passwort",
|
||||
"passwordPlaceholder": "Passwort eingeben",
|
||||
"newPasswordLabel": "Neues Passwort (leer lassen, um das aktuelle zu behalten)",
|
||||
"newPasswordPlaceholder": "Neues Passwort eingeben",
|
||||
"manageFilesTitle": "Dateien verwalten",
|
||||
"manageRecipientsTitle": "Empfänger verwalten",
|
||||
"manageFilesTitle": "Dateien Verwalten",
|
||||
"manageRecipientsTitle": "Empfänger Verwalten",
|
||||
"editSuccess": "Freigabe erfolgreich aktualisiert",
|
||||
"editError": "Fehler beim Aktualisieren der Freigabe"
|
||||
},
|
||||
"shareDetails": {
|
||||
"title": "Freigabedetails",
|
||||
"subtitle": "Detaillierte Informationen zu dieser Freigabe",
|
||||
"title": "Freigabe-Details",
|
||||
"subtitle": "Detaillierte Informationen über diese Freigabe",
|
||||
"basicInfo": "Grundinformationen",
|
||||
"name": "Name",
|
||||
"untitled": "Ohne Titel",
|
||||
"views": "Aufrufe",
|
||||
"description": "Beschreibung",
|
||||
"noDescription": "Keine Beschreibung angegeben",
|
||||
"untitled": "Unbenannt",
|
||||
"shareLink": "Freigabe-Link",
|
||||
"editLink": "Link Bearbeiten",
|
||||
"generateLink": "Link Generieren",
|
||||
"noLink": "Noch kein Link generiert",
|
||||
"copyLink": "Link kopieren",
|
||||
"openLink": "In neuem Tab öffnen",
|
||||
"linkCopied": "Link in Zwischenablage kopiert",
|
||||
"views": "Ansichten",
|
||||
"dates": "Daten",
|
||||
"created": "Erstellt",
|
||||
"expires": "Läuft ab",
|
||||
"never": "Nie",
|
||||
"never": "Niemals",
|
||||
"security": "Sicherheit",
|
||||
"passwordProtected": "Passwortgeschützt",
|
||||
"publicAccess": "Öffentlicher Zugriff",
|
||||
"maxViews": "Maximale Ansichten:",
|
||||
"passwordProtected": "Passwort-geschützt",
|
||||
"publicAccess": "Öffentlicher Zugang",
|
||||
"maxViews": "Max. Ansichten:",
|
||||
"files": "Dateien",
|
||||
"recipients": "Empfänger",
|
||||
"notAvailable": "N/V",
|
||||
"invalidDate": "Ungültiges Datum",
|
||||
"loadError": "Fehler beim Laden der Freigabedetails"
|
||||
"loadError": "Fehler beim Laden der Freigabe-Details"
|
||||
},
|
||||
"shareManager": {
|
||||
"deleteSuccess": "Freigabe erfolgreich gelöscht",
|
||||
@@ -537,12 +564,13 @@
|
||||
"pageTitle": "Freigaben"
|
||||
},
|
||||
"sharesTable": {
|
||||
"ariaLabel": "Freigabetabelle",
|
||||
"never": "Nie",
|
||||
"ariaLabel": "Freigaben-Tabelle",
|
||||
"never": "Niemals",
|
||||
"columns": {
|
||||
"name": "NAME",
|
||||
"description": "BESCHREIBUNG",
|
||||
"createdAt": "ERSTELLT AM",
|
||||
"expiresAt": "LÄUFT AB",
|
||||
"expiresAt": "LÄUFT AB AM",
|
||||
"status": "STATUS",
|
||||
"security": "SICHERHEIT",
|
||||
"files": "DATEIEN",
|
||||
@@ -550,7 +578,7 @@
|
||||
"actions": "AKTIONEN"
|
||||
},
|
||||
"status": {
|
||||
"neverExpires": "Läuft nie ab",
|
||||
"neverExpires": "Läuft Nie Ab",
|
||||
"active": "Aktiv",
|
||||
"expired": "Abgelaufen"
|
||||
},
|
||||
@@ -561,15 +589,15 @@
|
||||
"filesCount": "Dateien",
|
||||
"recipientsCount": "Empfänger",
|
||||
"actions": {
|
||||
"menu": "Freigabeaktionsmenü",
|
||||
"menu": "Freigabe-Aktionen-Menü",
|
||||
"edit": "Bearbeiten",
|
||||
"manageFiles": "Dateien verwalten",
|
||||
"manageRecipients": "Empfänger verwalten",
|
||||
"viewDetails": "Details anzeigen",
|
||||
"generateLink": "Link generieren",
|
||||
"editLink": "Link bearbeiten",
|
||||
"copyLink": "Link kopieren",
|
||||
"notifyRecipients": "Empfänger benachrichtigen",
|
||||
"manageFiles": "Dateien Verwalten",
|
||||
"manageRecipients": "Empfänger Verwalten",
|
||||
"viewDetails": "Details Anzeigen",
|
||||
"generateLink": "Link Generieren",
|
||||
"editLink": "Link Bearbeiten",
|
||||
"copyLink": "Link Kopieren",
|
||||
"notifyRecipients": "Empfänger Benachrichtigen",
|
||||
"delete": "Löschen"
|
||||
}
|
||||
},
|
||||
@@ -694,23 +722,47 @@
|
||||
"passwordRequired": "Passwort ist erforderlich"
|
||||
},
|
||||
"shareFile": {
|
||||
"title": "Datei teilen",
|
||||
"linkTitle": "Link generieren",
|
||||
"title": "Datei Freigeben",
|
||||
"linkTitle": "Link Generieren",
|
||||
"nameLabel": "Freigabe-Name",
|
||||
"namePlaceholder": "Freigabe-Name eingeben",
|
||||
"namePlaceholder": "Freigabe-Namen eingeben",
|
||||
"descriptionLabel": "Beschreibung",
|
||||
"descriptionPlaceholder": "Geben Sie eine Beschreibung ein (optional)",
|
||||
"expirationLabel": "Ablaufdatum",
|
||||
"expirationPlaceholder": "TT.MM.JJJJ HH:MM",
|
||||
"maxViewsLabel": "Maximale Aufrufe",
|
||||
"maxViewsLabel": "Maximale Ansichten",
|
||||
"maxViewsPlaceholder": "Leer lassen für unbegrenzt",
|
||||
"passwordProtection": "Passwort geschützt",
|
||||
"passwordProtection": "Passwort-geschützt",
|
||||
"passwordLabel": "Passwort",
|
||||
"passwordPlaceholder": "Passwort eingeben",
|
||||
"linkDescription": "Generieren Sie einen benutzerdefinierten Link zum Teilen der Datei",
|
||||
"linkDescription": "Einen benutzerdefinierten Link zum Teilen der Datei generieren",
|
||||
"aliasLabel": "Link-Alias",
|
||||
"aliasPlaceholder": "Benutzerdefinierten Alias eingeben",
|
||||
"linkReady": "Ihr Freigabe-Link ist bereit:",
|
||||
"createShare": "Freigabe erstellen",
|
||||
"generateLink": "Link generieren",
|
||||
"copyLink": "Link kopieren"
|
||||
"createShare": "Freigabe Erstellen",
|
||||
"generateLink": "Link Generieren",
|
||||
"copyLink": "Link Kopieren"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "Massen-Download",
|
||||
"zipNameLabel": "ZIP-Dateiname",
|
||||
"zipNamePlaceholder": "Dateiname eingeben",
|
||||
"description": "{count, plural, =1 {1 Datei wird komprimiert} other {# Dateien werden komprimiert}}",
|
||||
"download": "ZIP Herunterladen"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "Zu löschende Dateien"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "Mehrere Dateien Teilen",
|
||||
"shareNameLabel": "Freigabe-Name",
|
||||
"shareNamePlaceholder": "Freigabe-Name eingeben",
|
||||
"descriptionLabel": "Beschreibung",
|
||||
"descriptionPlaceholder": "Geben Sie eine Beschreibung ein (optional)",
|
||||
"filesToShare": "Zu teilende Dateien",
|
||||
"files": "Dateien",
|
||||
"totalSize": "Gesamtgröße",
|
||||
"creating": "Erstellen...",
|
||||
"create": "Freigabe Erstellen"
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
"createShare": {
|
||||
"title": "Create Share",
|
||||
"nameLabel": "Share Name",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Enter a description (optional)",
|
||||
"expirationLabel": "Expiration Date",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"maxViewsLabel": "Max Views",
|
||||
@@ -92,10 +94,17 @@
|
||||
"updateSuccess": "File updated successfully",
|
||||
"updateError": "Failed to update file",
|
||||
"deleteSuccess": "File deleted successfully",
|
||||
"deleteError": "Failed to delete file"
|
||||
"deleteError": "Failed to delete file",
|
||||
"bulkDownloadSuccess": "Files download started successfully",
|
||||
"bulkDownloadError": "Error creating ZIP file",
|
||||
"bulkDownloadFileError": "Error downloading file {fileName}",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {1 file deleted successfully} other {# files deleted successfully}}",
|
||||
"bulkDeleteError": "Error deleting selected files"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Files table",
|
||||
"selectAll": "Select all",
|
||||
"selectFile": "Select file {fileName}",
|
||||
"columns": {
|
||||
"name": "NAME",
|
||||
"description": "DESCRIPTION",
|
||||
@@ -111,6 +120,13 @@
|
||||
"share": "Share",
|
||||
"download": "Download",
|
||||
"delete": "Delete"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {1 file selected} other {# files selected}}",
|
||||
"actions": "Actions",
|
||||
"download": "Download Selected",
|
||||
"share": "Share Selected",
|
||||
"delete": "Delete Selected"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -460,6 +476,8 @@
|
||||
"deleteConfirmation": "Are you sure you want to delete this share? This action cannot be undone.",
|
||||
"editTitle": "Edit Share",
|
||||
"nameLabel": "Share Name",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Enter a description (optional)",
|
||||
"expirationLabel": "Expiration Date",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"maxViewsLabel": "Max Views",
|
||||
@@ -479,7 +497,16 @@
|
||||
"subtitle": "Detailed information about this share",
|
||||
"basicInfo": "Basic Information",
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"noDescription": "No description provided",
|
||||
"untitled": "Untitled",
|
||||
"shareLink": "Share Link",
|
||||
"editLink": "Edit Link",
|
||||
"generateLink": "Generate Link",
|
||||
"noLink": "No link generated yet",
|
||||
"copyLink": "Copy link",
|
||||
"openLink": "Open in new tab",
|
||||
"linkCopied": "Link copied to clipboard",
|
||||
"views": "Views",
|
||||
"dates": "Dates",
|
||||
"created": "Created",
|
||||
@@ -541,6 +568,7 @@
|
||||
"never": "Never",
|
||||
"columns": {
|
||||
"name": "NAME",
|
||||
"description": "DESCRIPTION",
|
||||
"createdAt": "CREATED AT",
|
||||
"expiresAt": "EXPIRES AT",
|
||||
"status": "STATUS",
|
||||
@@ -697,6 +725,8 @@
|
||||
"linkTitle": "Generate Link",
|
||||
"nameLabel": "Share Name",
|
||||
"namePlaceholder": "Enter share name",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Enter a description (optional)",
|
||||
"expirationLabel": "Expiration Date",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"maxViewsLabel": "Maximum Views",
|
||||
@@ -711,5 +741,27 @@
|
||||
"createShare": "Create Share",
|
||||
"generateLink": "Generate Link",
|
||||
"copyLink": "Copy Link"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "Bulk Download",
|
||||
"zipNameLabel": "ZIP file name",
|
||||
"zipNamePlaceholder": "Enter file name",
|
||||
"description": "{count, plural, =1 {1 file will be compressed} other {# files will be compressed}}",
|
||||
"download": "Download ZIP"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "Files to be deleted"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "Share Multiple Files",
|
||||
"shareNameLabel": "Share Name",
|
||||
"shareNamePlaceholder": "Enter share name",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Enter a description (optional)",
|
||||
"filesToShare": "Files to share",
|
||||
"files": "files",
|
||||
"totalSize": "Total size",
|
||||
"creating": "Creating...",
|
||||
"create": "Create Share"
|
||||
}
|
||||
}
|
||||
@@ -13,17 +13,19 @@
|
||||
"back": "Volver"
|
||||
},
|
||||
"createShare": {
|
||||
"title": "Crear compartición",
|
||||
"nameLabel": "Nombre de la compartición",
|
||||
"expirationLabel": "Fecha de expiración",
|
||||
"expirationPlaceholder": "MM/DD/AAAA HH:MM",
|
||||
"maxViewsLabel": "Máximo de visualizaciones",
|
||||
"maxViewsPlaceholder": "Dejar vacío para ilimitado",
|
||||
"passwordProtection": "Protección por contraseña",
|
||||
"title": "Crear Compartir",
|
||||
"nameLabel": "Nombre del Compartir",
|
||||
"descriptionLabel": "Descripción",
|
||||
"descriptionPlaceholder": "Ingrese una descripción (opcional)",
|
||||
"expirationLabel": "Fecha de Expiración",
|
||||
"expirationPlaceholder": "DD/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Vistas Máximas",
|
||||
"maxViewsPlaceholder": "Deje vacío para ilimitado",
|
||||
"passwordProtection": "Protegido por Contraseña",
|
||||
"passwordLabel": "Contraseña",
|
||||
"create": "Crear compartición",
|
||||
"success": "Compartición creada exitosamente",
|
||||
"error": "Error al crear la compartición"
|
||||
"create": "Crear Compartir",
|
||||
"success": "Compartir creado exitosamente",
|
||||
"error": "Error al crear compartir"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Error al cargar los datos del tablero",
|
||||
@@ -82,35 +84,49 @@
|
||||
"saveChanges": "Guardar cambios"
|
||||
},
|
||||
"files": {
|
||||
"title": "Todos los archivos",
|
||||
"uploadFile": "Subir archivo",
|
||||
"loadError": "Error al cargar los archivos",
|
||||
"pageTitle": "Mis archivos",
|
||||
"breadcrumb": "Mis archivos",
|
||||
"title": "Todos los Archivos",
|
||||
"uploadFile": "Subir Archivo",
|
||||
"loadError": "Error al cargar archivos",
|
||||
"pageTitle": "Mis Archivos",
|
||||
"breadcrumb": "Mis Archivos",
|
||||
"downloadStart": "Descarga iniciada",
|
||||
"downloadError": "Error al descargar el archivo",
|
||||
"downloadError": "Error al descargar archivo",
|
||||
"updateSuccess": "Archivo actualizado exitosamente",
|
||||
"updateError": "Error al actualizar el archivo",
|
||||
"updateError": "Error al actualizar archivo",
|
||||
"deleteSuccess": "Archivo eliminado exitosamente",
|
||||
"deleteError": "Error al eliminar el archivo"
|
||||
"deleteError": "Error al eliminar archivo",
|
||||
"bulkDownloadSuccess": "Descarga de archivos iniciada exitosamente",
|
||||
"bulkDownloadError": "Error al crear archivo ZIP",
|
||||
"bulkDownloadFileError": "Error al descargar archivo {fileName}",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {1 archivo eliminado exitosamente} other {# archivos eliminados exitosamente}}",
|
||||
"bulkDeleteError": "Error al eliminar archivos seleccionados"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Tabla de archivos",
|
||||
"selectAll": "Seleccionar todo",
|
||||
"selectFile": "Seleccionar archivo {fileName}",
|
||||
"columns": {
|
||||
"name": "NOMBRE",
|
||||
"description": "DESCRIPCIÓN",
|
||||
"size": "TAMAÑO",
|
||||
"createdAt": "CREADO",
|
||||
"updatedAt": "ACTUALIZADO",
|
||||
"createdAt": "CREADO EN",
|
||||
"updatedAt": "ACTUALIZADO EN",
|
||||
"actions": "ACCIONES"
|
||||
},
|
||||
"actions": {
|
||||
"menu": "Menú de acciones del archivo",
|
||||
"menu": "Menú de acciones de archivo",
|
||||
"preview": "Vista previa",
|
||||
"edit": "Editar",
|
||||
"share": "Compartir",
|
||||
"download": "Descargar",
|
||||
"delete": "Eliminar"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {1 archivo seleccionado} other {# archivos seleccionados}}",
|
||||
"actions": "Acciones",
|
||||
"download": "Descargar Seleccionados",
|
||||
"share": "Compartir Seleccionados",
|
||||
"delete": "Eliminar Seleccionados"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -456,44 +472,55 @@
|
||||
"pageTitle": "Compartición"
|
||||
},
|
||||
"shareActions": {
|
||||
"deleteTitle": "Eliminar compartición",
|
||||
"deleteTitle": "Eliminar Compartir",
|
||||
"deleteConfirmation": "¿Estás seguro de que deseas eliminar esta compartición? Esta acción no se puede deshacer.",
|
||||
"editTitle": "Editar compartición",
|
||||
"nameLabel": "Nombre de la compartición",
|
||||
"expirationLabel": "Fecha de expiración",
|
||||
"expirationPlaceholder": "MM/DD/AAAA HH:MM",
|
||||
"maxViewsLabel": "Máximo de visualizaciones",
|
||||
"maxViewsPlaceholder": "Dejar vacío para ilimitado",
|
||||
"passwordProtection": "Protegido con contraseña",
|
||||
"editTitle": "Editar Compartir",
|
||||
"nameLabel": "Nombre del Compartir",
|
||||
"descriptionLabel": "Descripción",
|
||||
"descriptionPlaceholder": "Ingrese una descripción (opcional)",
|
||||
"expirationLabel": "Fecha de Expiración",
|
||||
"expirationPlaceholder": "DD/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Vistas Máximas",
|
||||
"maxViewsPlaceholder": "Deje vacío para ilimitado",
|
||||
"passwordProtection": "Protegido por Contraseña",
|
||||
"passwordLabel": "Contraseña",
|
||||
"passwordPlaceholder": "Introduce la contraseña",
|
||||
"newPasswordLabel": "Nueva contraseña (dejar vacío para mantener la actual)",
|
||||
"newPasswordPlaceholder": "Introduce la nueva contraseña",
|
||||
"manageFilesTitle": "Gestionar archivos",
|
||||
"manageRecipientsTitle": "Gestionar destinatarios",
|
||||
"editSuccess": "Compartición actualizada exitosamente",
|
||||
"editError": "Error al actualizar la compartición"
|
||||
"passwordPlaceholder": "Ingrese contraseña",
|
||||
"newPasswordLabel": "Nueva Contraseña (deje vacío para mantener la actual)",
|
||||
"newPasswordPlaceholder": "Ingrese nueva contraseña",
|
||||
"manageFilesTitle": "Administrar Archivos",
|
||||
"manageRecipientsTitle": "Administrar Destinatarios",
|
||||
"editSuccess": "Compartir actualizado exitosamente",
|
||||
"editError": "Error al actualizar compartir"
|
||||
},
|
||||
"shareDetails": {
|
||||
"title": "Detalles de la compartición",
|
||||
"subtitle": "Información detallada sobre esta compartición",
|
||||
"basicInfo": "Información básica",
|
||||
"title": "Detalles del Compartir",
|
||||
"subtitle": "Información detallada sobre este compartir",
|
||||
"basicInfo": "Información Básica",
|
||||
"name": "Nombre",
|
||||
"description": "Descripción",
|
||||
"noDescription": "Sin descripción proporcionada",
|
||||
"untitled": "Sin título",
|
||||
"shareLink": "Enlace de Compartir",
|
||||
"editLink": "Editar Enlace",
|
||||
"generateLink": "Generar Enlace",
|
||||
"noLink": "Ningún enlace generado aún",
|
||||
"copyLink": "Copiar enlace",
|
||||
"openLink": "Abrir en nueva pestaña",
|
||||
"linkCopied": "Enlace copiado al portapapeles",
|
||||
"views": "Visualizaciones",
|
||||
"dates": "Fechas",
|
||||
"created": "Creada",
|
||||
"created": "Creado",
|
||||
"expires": "Expira",
|
||||
"never": "Nunca",
|
||||
"security": "Seguridad",
|
||||
"passwordProtected": "Protegido con contraseña",
|
||||
"publicAccess": "Acceso público",
|
||||
"maxViews": "Máximo de visualizaciones:",
|
||||
"passwordProtected": "Protegido por Contraseña",
|
||||
"publicAccess": "Acceso Público",
|
||||
"maxViews": "Vistas Máx.:",
|
||||
"files": "Archivos",
|
||||
"recipients": "Destinatarios",
|
||||
"notAvailable": "N/D",
|
||||
"notAvailable": "N/A",
|
||||
"invalidDate": "Fecha inválida",
|
||||
"loadError": "Error al cargar los detalles de la compartición"
|
||||
"loadError": "Error al cargar detalles del compartir"
|
||||
},
|
||||
"shareManager": {
|
||||
"deleteSuccess": "Compartición eliminada exitosamente",
|
||||
@@ -541,8 +568,9 @@
|
||||
"never": "Nunca",
|
||||
"columns": {
|
||||
"name": "NOMBRE",
|
||||
"createdAt": "CREADO",
|
||||
"expiresAt": "EXPIRA",
|
||||
"description": "DESCRIPCIÓN",
|
||||
"createdAt": "CREADO EN",
|
||||
"expiresAt": "EXPIRA EN",
|
||||
"status": "ESTADO",
|
||||
"security": "SEGURIDAD",
|
||||
"files": "ARCHIVOS",
|
||||
@@ -550,26 +578,26 @@
|
||||
"actions": "ACCIONES"
|
||||
},
|
||||
"status": {
|
||||
"neverExpires": "Nunca expira",
|
||||
"active": "Activa",
|
||||
"expired": "Expirada"
|
||||
"neverExpires": "Nunca Expira",
|
||||
"active": "Activo",
|
||||
"expired": "Expirado"
|
||||
},
|
||||
"security": {
|
||||
"protected": "Protegida",
|
||||
"public": "Pública"
|
||||
"protected": "Protegido",
|
||||
"public": "Público"
|
||||
},
|
||||
"filesCount": "archivos",
|
||||
"recipientsCount": "destinatarios",
|
||||
"actions": {
|
||||
"menu": "Menú de acciones de la compartición",
|
||||
"menu": "Menú de acciones de compartir",
|
||||
"edit": "Editar",
|
||||
"manageFiles": "Gestionar archivos",
|
||||
"manageRecipients": "Gestionar destinatarios",
|
||||
"viewDetails": "Ver detalles",
|
||||
"generateLink": "Generar enlace",
|
||||
"editLink": "Editar enlace",
|
||||
"copyLink": "Copiar enlace",
|
||||
"notifyRecipients": "Notificar destinatarios",
|
||||
"manageFiles": "Administrar Archivos",
|
||||
"manageRecipients": "Administrar Destinatarios",
|
||||
"viewDetails": "Ver Detalles",
|
||||
"generateLink": "Generar Enlace",
|
||||
"editLink": "Editar Enlace",
|
||||
"copyLink": "Copiar Enlace",
|
||||
"notifyRecipients": "Notificar Destinatarios",
|
||||
"delete": "Eliminar"
|
||||
}
|
||||
},
|
||||
@@ -694,23 +722,47 @@
|
||||
"passwordRequired": "Se requiere la contraseña"
|
||||
},
|
||||
"shareFile": {
|
||||
"title": "Compartir archivo",
|
||||
"linkTitle": "Generar enlace",
|
||||
"nameLabel": "Nombre del compartir",
|
||||
"title": "Compartir Archivo",
|
||||
"linkTitle": "Generar Enlace",
|
||||
"nameLabel": "Nombre del Compartir",
|
||||
"namePlaceholder": "Ingrese nombre del compartir",
|
||||
"expirationLabel": "Fecha de expiración",
|
||||
"descriptionLabel": "Descripción",
|
||||
"descriptionPlaceholder": "Ingrese una descripción (opcional)",
|
||||
"expirationLabel": "Fecha de Expiración",
|
||||
"expirationPlaceholder": "DD/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Máximo de visualizaciones",
|
||||
"maxViewsLabel": "Vistas Máximas",
|
||||
"maxViewsPlaceholder": "Deje vacío para ilimitado",
|
||||
"passwordProtection": "Protegido con contraseña",
|
||||
"passwordProtection": "Protegido por Contraseña",
|
||||
"passwordLabel": "Contraseña",
|
||||
"passwordPlaceholder": "Ingrese contraseña",
|
||||
"linkDescription": "Genere un enlace personalizado para compartir el archivo",
|
||||
"aliasLabel": "Alias del enlace",
|
||||
"aliasLabel": "Alias del Enlace",
|
||||
"aliasPlaceholder": "Ingrese alias personalizado",
|
||||
"linkReady": "Su enlace de compartir está listo:",
|
||||
"createShare": "Crear compartir",
|
||||
"generateLink": "Generar enlace",
|
||||
"copyLink": "Copiar enlace"
|
||||
"createShare": "Crear Compartir",
|
||||
"generateLink": "Generar Enlace",
|
||||
"copyLink": "Copiar Enlace"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "Descarga Masiva",
|
||||
"zipNameLabel": "Nombre del archivo ZIP",
|
||||
"zipNamePlaceholder": "Ingrese nombre del archivo",
|
||||
"description": "{count, plural, =1 {1 archivo será comprimido} other {# archivos serán comprimidos}}",
|
||||
"download": "Descargar ZIP"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "Archivos que serán eliminados"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "Compartir Múltiples Archivos",
|
||||
"shareNameLabel": "Nombre del Compartir",
|
||||
"shareNamePlaceholder": "Ingrese nombre del compartir",
|
||||
"descriptionLabel": "Descripción",
|
||||
"descriptionPlaceholder": "Ingrese una descripción (opcional)",
|
||||
"filesToShare": "Archivos para compartir",
|
||||
"files": "archivos",
|
||||
"totalSize": "Tamaño total",
|
||||
"creating": "Creando...",
|
||||
"create": "Crear Compartir"
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,15 @@
|
||||
"createShare": {
|
||||
"title": "Créer un Partage",
|
||||
"nameLabel": "Nom du Partage",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Entrez une description (optionnel)",
|
||||
"expirationLabel": "Date d'Expiration",
|
||||
"expirationPlaceholder": "JJ/MM/AAAA HH:MM",
|
||||
"expirationPlaceholder": "DD/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Vues Maximales",
|
||||
"maxViewsPlaceholder": "Laisser vide pour illimité",
|
||||
"maxViewsPlaceholder": "Laissez vide pour illimité",
|
||||
"passwordProtection": "Protégé par Mot de Passe",
|
||||
"passwordLabel": "Mot de Passe",
|
||||
"create": "Créer le Partage",
|
||||
"create": "Créer un Partage",
|
||||
"success": "Partage créé avec succès",
|
||||
"error": "Échec de la création du partage"
|
||||
},
|
||||
@@ -83,34 +85,48 @@
|
||||
},
|
||||
"files": {
|
||||
"title": "Tous les Fichiers",
|
||||
"uploadFile": "Envoyer un Fichier",
|
||||
"uploadFile": "Télécharger un Fichier",
|
||||
"loadError": "Échec du chargement des fichiers",
|
||||
"pageTitle": "Mes Fichiers",
|
||||
"breadcrumb": "Mes Fichiers",
|
||||
"downloadStart": "Téléchargement commencé",
|
||||
"downloadStart": "Téléchargement démarré",
|
||||
"downloadError": "Échec du téléchargement du fichier",
|
||||
"updateSuccess": "Fichier mis à jour avec succès",
|
||||
"updateError": "Échec de la mise à jour du fichier",
|
||||
"deleteSuccess": "Fichier supprimé avec succès",
|
||||
"deleteError": "Échec de la suppression du fichier"
|
||||
"deleteError": "Échec de la suppression du fichier",
|
||||
"bulkDownloadSuccess": "Téléchargement des fichiers démarré avec succès",
|
||||
"bulkDownloadError": "Erreur lors de la création du fichier ZIP",
|
||||
"bulkDownloadFileError": "Erreur lors du téléchargement du fichier {fileName}",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {1 fichier supprimé avec succès} other {# fichiers supprimés avec succès}}",
|
||||
"bulkDeleteError": "Erreur lors de la suppression des fichiers sélectionnés"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Tableau des fichiers",
|
||||
"selectAll": "Tout sélectionner",
|
||||
"selectFile": "Sélectionner le fichier {fileName}",
|
||||
"columns": {
|
||||
"name": "NOM",
|
||||
"description": "DESCRIPTION",
|
||||
"size": "TAILLE",
|
||||
"createdAt": "CRÉÉ LE",
|
||||
"updatedAt": "MODIFIÉ LE",
|
||||
"updatedAt": "MIS À JOUR LE",
|
||||
"actions": "ACTIONS"
|
||||
},
|
||||
"actions": {
|
||||
"menu": "Menu d'actions de fichier",
|
||||
"menu": "Menu des actions de fichier",
|
||||
"preview": "Aperçu",
|
||||
"edit": "Modifier",
|
||||
"share": "Partager",
|
||||
"download": "Télécharger",
|
||||
"delete": "Supprimer"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {1 fichier sélectionné} other {# fichiers sélectionnés}}",
|
||||
"actions": "Actions",
|
||||
"download": "Télécharger les Sélectionnés",
|
||||
"share": "Partager les Sélectionnés",
|
||||
"delete": "Supprimer les Sélectionnés"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -460,14 +476,16 @@
|
||||
"deleteConfirmation": "Êtes-vous sûr de vouloir supprimer ce partage ? Cette action ne peut pas être annulée.",
|
||||
"editTitle": "Modifier le Partage",
|
||||
"nameLabel": "Nom du Partage",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Entrez une description (optionnel)",
|
||||
"expirationLabel": "Date d'Expiration",
|
||||
"expirationPlaceholder": "JJ/MM/AAAA HH:MM",
|
||||
"expirationPlaceholder": "DD/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Vues Maximales",
|
||||
"maxViewsPlaceholder": "Laisser vide pour illimité",
|
||||
"maxViewsPlaceholder": "Laissez vide pour illimité",
|
||||
"passwordProtection": "Protégé par Mot de Passe",
|
||||
"passwordLabel": "Mot de Passe",
|
||||
"passwordPlaceholder": "Entrez le mot de passe",
|
||||
"newPasswordLabel": "Nouveau Mot de Passe (laisser vide pour garder l'actuel)",
|
||||
"newPasswordLabel": "Nouveau Mot de Passe (laissez vide pour conserver l'actuel)",
|
||||
"newPasswordPlaceholder": "Entrez le nouveau mot de passe",
|
||||
"manageFilesTitle": "Gérer les Fichiers",
|
||||
"manageRecipientsTitle": "Gérer les Destinataires",
|
||||
@@ -479,19 +497,28 @@
|
||||
"subtitle": "Informations détaillées sur ce partage",
|
||||
"basicInfo": "Informations de Base",
|
||||
"name": "Nom",
|
||||
"untitled": "Partage sans titre",
|
||||
"description": "Description",
|
||||
"noDescription": "Aucune description fournie",
|
||||
"untitled": "Sans titre",
|
||||
"shareLink": "Lien de Partage",
|
||||
"editLink": "Modifier le Lien",
|
||||
"generateLink": "Générer un Lien",
|
||||
"noLink": "Aucun lien généré pour le moment",
|
||||
"copyLink": "Copier le lien",
|
||||
"openLink": "Ouvrir dans un nouvel onglet",
|
||||
"linkCopied": "Lien copié dans le presse-papiers",
|
||||
"views": "Vues",
|
||||
"dates": "Dates",
|
||||
"created": "Créé le: {date}",
|
||||
"expires": "Expire le: {date}",
|
||||
"created": "Créé",
|
||||
"expires": "Expire",
|
||||
"never": "Jamais",
|
||||
"security": "Sécurité",
|
||||
"passwordProtected": "Protégé par Mot de Passe",
|
||||
"publicAccess": "Accès Public",
|
||||
"maxViews": "Vues Maximales:",
|
||||
"maxViews": "Vues Max.:",
|
||||
"files": "Fichiers",
|
||||
"recipients": "Destinataires",
|
||||
"notAvailable": "N/D",
|
||||
"notAvailable": "N/A",
|
||||
"invalidDate": "Date invalide",
|
||||
"loadError": "Échec du chargement des détails du partage"
|
||||
},
|
||||
@@ -541,6 +568,7 @@
|
||||
"never": "Jamais",
|
||||
"columns": {
|
||||
"name": "NOM",
|
||||
"description": "DESCRIPTION",
|
||||
"createdAt": "CRÉÉ LE",
|
||||
"expiresAt": "EXPIRE LE",
|
||||
"status": "STATUT",
|
||||
@@ -561,7 +589,7 @@
|
||||
"filesCount": "fichiers",
|
||||
"recipientsCount": "destinataires",
|
||||
"actions": {
|
||||
"menu": "Menu d'actions du partage",
|
||||
"menu": "Menu d'actions de partage",
|
||||
"edit": "Modifier",
|
||||
"manageFiles": "Gérer les Fichiers",
|
||||
"manageRecipients": "Gérer les Destinataires",
|
||||
@@ -694,23 +722,47 @@
|
||||
"passwordRequired": "Le mot de passe est requis"
|
||||
},
|
||||
"shareFile": {
|
||||
"title": "Partager le fichier",
|
||||
"linkTitle": "Générer le lien",
|
||||
"nameLabel": "Nom du partage",
|
||||
"title": "Partager un Fichier",
|
||||
"linkTitle": "Générer un Lien",
|
||||
"nameLabel": "Nom du Partage",
|
||||
"namePlaceholder": "Entrez le nom du partage",
|
||||
"expirationLabel": "Date d'expiration",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Entrez une description (optionnel)",
|
||||
"expirationLabel": "Date d'Expiration",
|
||||
"expirationPlaceholder": "DD/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Nombre maximum de vues",
|
||||
"maxViewsLabel": "Vues Maximales",
|
||||
"maxViewsPlaceholder": "Laissez vide pour illimité",
|
||||
"passwordProtection": "Protégé par mot de passe",
|
||||
"passwordLabel": "Mot de passe",
|
||||
"passwordProtection": "Protégé par Mot de Passe",
|
||||
"passwordLabel": "Mot de Passe",
|
||||
"passwordPlaceholder": "Entrez le mot de passe",
|
||||
"linkDescription": "Générez un lien personnalisé pour partager le fichier",
|
||||
"aliasLabel": "Alias du lien",
|
||||
"aliasLabel": "Alias du Lien",
|
||||
"aliasPlaceholder": "Entrez un alias personnalisé",
|
||||
"linkReady": "Votre lien de partage est prêt :",
|
||||
"createShare": "Créer le partage",
|
||||
"generateLink": "Générer le lien",
|
||||
"copyLink": "Copier le lien"
|
||||
"createShare": "Créer un Partage",
|
||||
"generateLink": "Générer un Lien",
|
||||
"copyLink": "Copier le Lien"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "Téléchargement en Masse",
|
||||
"zipNameLabel": "Nom du fichier ZIP",
|
||||
"zipNamePlaceholder": "Entrez le nom du fichier",
|
||||
"description": "{count, plural, =1 {1 fichier sera compressé} other {# fichiers seront compressés}}",
|
||||
"download": "Télécharger le ZIP"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "Fichiers à supprimer"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "Partager Plusieurs Fichiers",
|
||||
"shareNameLabel": "Nom du Partage",
|
||||
"shareNamePlaceholder": "Entrez le nom du partage",
|
||||
"descriptionLabel": "Description",
|
||||
"descriptionPlaceholder": "Entrez une description (optionnel)",
|
||||
"filesToShare": "Fichiers à partager",
|
||||
"files": "fichiers",
|
||||
"totalSize": "Taille totale",
|
||||
"creating": "Création...",
|
||||
"create": "Créer un Partage"
|
||||
}
|
||||
}
|
||||
@@ -13,15 +13,17 @@
|
||||
"back": "वापस"
|
||||
},
|
||||
"createShare": {
|
||||
"title": "साझा करें बनाएं",
|
||||
"nameLabel": "साझाकरण का नाम",
|
||||
"title": "साझाकरण बनाएं",
|
||||
"nameLabel": "साझाकरण नाम",
|
||||
"descriptionLabel": "विवरण",
|
||||
"descriptionPlaceholder": "विवरण दर्ज करें (वैकल्पिक)",
|
||||
"expirationLabel": "समाप्ति तिथि",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"maxViewsLabel": "अधिकतम दृश्य",
|
||||
"maxViewsPlaceholder": "अनलिमिटेड के लिए खाली छोड़ें",
|
||||
"passwordProtection": "पासवर्ड सुरक्षा",
|
||||
"maxViewsPlaceholder": "असीमित के लिए खाली छोड़ें",
|
||||
"passwordProtection": "पासवर्ड सुरक्षित",
|
||||
"passwordLabel": "पासवर्ड",
|
||||
"create": "साझा करें बनाएं",
|
||||
"create": "साझाकरण बनाएं",
|
||||
"success": "साझाकरण सफलतापूर्वक बनाया गया",
|
||||
"error": "साझाकरण बनाने में विफल"
|
||||
},
|
||||
@@ -84,18 +86,25 @@
|
||||
"files": {
|
||||
"title": "सभी फाइलें",
|
||||
"uploadFile": "फाइल अपलोड करें",
|
||||
"loadError": "फाइलें लोड करने में त्रुटि",
|
||||
"loadError": "फाइलें लोड करने में विफल",
|
||||
"pageTitle": "मेरी फाइलें",
|
||||
"breadcrumb": "मेरी फाइलें",
|
||||
"downloadStart": "डाउनलोड शुरू हुआ",
|
||||
"downloadError": "फाइल डाउनलोड करने में त्रुटि",
|
||||
"updateSuccess": "फाइल सफलतापूर्वक अपडेट हुई",
|
||||
"updateError": "फाइल अपडेट करने में त्रुटि",
|
||||
"downloadError": "फाइल डाउनलोड करने में विफल",
|
||||
"updateSuccess": "फाइल सफलतापूर्वक अपडेट की गई",
|
||||
"updateError": "फाइल अपडेट करने में विफल",
|
||||
"deleteSuccess": "फाइल सफलतापूर्वक हटाई गई",
|
||||
"deleteError": "फाइल हटाने में त्रुटि"
|
||||
"deleteError": "फाइल हटाने में विफल",
|
||||
"bulkDownloadSuccess": "फाइलों का डाउनलोड सफलतापूर्वक शुरू हुआ",
|
||||
"bulkDownloadError": "ZIP फाइल बनाने में त्रुटि",
|
||||
"bulkDownloadFileError": "फाइल {fileName} डाउनलोड करने में त्रुटि",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {1 फाइल सफलतापूर्वक हटाई गई} other {# फाइलें सफलतापूर्वक हटाई गईं}}",
|
||||
"bulkDeleteError": "चयनित फाइलों को हटाने में त्रुटि"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "फाइल टेबल",
|
||||
"ariaLabel": "फाइल तालिका",
|
||||
"selectAll": "सभी चुनें",
|
||||
"selectFile": "फाइल {fileName} चुनें",
|
||||
"columns": {
|
||||
"name": "नाम",
|
||||
"description": "विवरण",
|
||||
@@ -105,12 +114,19 @@
|
||||
"actions": "क्रियाएं"
|
||||
},
|
||||
"actions": {
|
||||
"menu": "फ़ाइल एक्शन मेनू",
|
||||
"menu": "फाइल क्रिया मेनू",
|
||||
"preview": "पूर्वावलोकन",
|
||||
"edit": "संपादित करें",
|
||||
"share": "साझा करें",
|
||||
"download": "डाउनलोड करें",
|
||||
"delete": "डिलीट करें"
|
||||
"delete": "हटाएं"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {1 फाइल चयनित} other {# फाइलें चयनित}}",
|
||||
"actions": "क्रियाएं",
|
||||
"download": "चयनित डाउनलोड करें",
|
||||
"share": "चयनित साझा करें",
|
||||
"delete": "चयनित हटाएं"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -457,41 +473,52 @@
|
||||
},
|
||||
"shareActions": {
|
||||
"deleteTitle": "साझाकरण हटाएं",
|
||||
"deleteConfirmation": "क्या आप वाकई इस साझाकरण को हटाना चाहते हैं? यह क्रिया अपरिवर्तनीय है।",
|
||||
"deleteConfirmation": "क्या आप वाकई इस साझाकरण को हटाना चाहते हैं? यह क्रिया पूर्ववत नहीं की जा सकती।",
|
||||
"editTitle": "साझाकरण संपादित करें",
|
||||
"nameLabel": "साझाकरण का नाम",
|
||||
"nameLabel": "साझाकरण नाम",
|
||||
"descriptionLabel": "विवरण",
|
||||
"descriptionPlaceholder": "विवरण दर्ज करें (वैकल्पिक)",
|
||||
"expirationLabel": "समाप्ति तिथि",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"expirationPlaceholder": "DD/MM/YYYY HH:MM",
|
||||
"maxViewsLabel": "अधिकतम दृश्य",
|
||||
"maxViewsPlaceholder": "अनलिमिटेड के लिए खाली छोड़ें",
|
||||
"passwordProtection": "पासवर्ड सुरक्षा",
|
||||
"maxViewsPlaceholder": "असीमित के लिए खाली छोड़ें",
|
||||
"passwordProtection": "पासवर्ड संरक्षित",
|
||||
"passwordLabel": "पासवर्ड",
|
||||
"passwordPlaceholder": "पासवर्ड दर्ज करें",
|
||||
"newPasswordLabel": "नया पासवर्ड (वर्तमान रखने के लिए खाली छोड़ें)",
|
||||
"newPasswordPlaceholder": "नया पासवर्ड दर्ज करें",
|
||||
"manageFilesTitle": "फाइलें प्रबंधित करें",
|
||||
"manageRecipientsTitle": "प्राप्तकर्ताओं का प्रबंधन करें",
|
||||
"editSuccess": "साझाकरण सफलतापूर्वक अपडेट हुआ",
|
||||
"manageRecipientsTitle": "प्राप्तकर्ता प्रबंधित करें",
|
||||
"editSuccess": "साझाकरण सफलतापूर्वक अपडेट किया गया",
|
||||
"editError": "साझाकरण अपडेट करने में विफल"
|
||||
},
|
||||
"shareDetails": {
|
||||
"title": "साझाकरण विवरण",
|
||||
"subtitle": "इस साझाकरण की विस्तृत जानकारी",
|
||||
"basicInfo": "मूल जानकारी",
|
||||
"subtitle": "इस साझाकरण के बारे में विस्तृत जानकारी",
|
||||
"basicInfo": "मूलभूत जानकारी",
|
||||
"name": "नाम",
|
||||
"untitled": "बिना शीर्षक के",
|
||||
"description": "विवरण",
|
||||
"noDescription": "कोई विवरण प्रदान नहीं किया गया",
|
||||
"untitled": "बिना शीर्षक",
|
||||
"shareLink": "साझाकरण लिंक",
|
||||
"editLink": "लिंक संपादित करें",
|
||||
"generateLink": "लिंक जेनरेट करें",
|
||||
"noLink": "अभी तक कोई लिंक जेनरेट नहीं किया गया",
|
||||
"copyLink": "लिंक कॉपी करें",
|
||||
"openLink": "नए टैब में खोलें",
|
||||
"linkCopied": "लिंक क्लिपबोर्ड में कॉपी कर दिया गया",
|
||||
"views": "दृश्य",
|
||||
"dates": "तिथियाँ",
|
||||
"dates": "तिथियां",
|
||||
"created": "बनाया गया",
|
||||
"expires": "समाप्त होता है",
|
||||
"never": "कभी नहीं",
|
||||
"security": "सुरक्षा",
|
||||
"passwordProtected": "पासवर्ड द्वारा संरक्षित",
|
||||
"publicAccess": "सार्वजनिक पहुँच",
|
||||
"passwordProtected": "पासवर्ड संरक्षित",
|
||||
"publicAccess": "सार्वजनिक पहुंच",
|
||||
"maxViews": "अधिकतम दृश्य:",
|
||||
"files": "फाइलें",
|
||||
"recipients": "प्राप्तकर्ता",
|
||||
"notAvailable": "एन/ए",
|
||||
"notAvailable": "उप/नहीं",
|
||||
"invalidDate": "अमान्य तिथि",
|
||||
"loadError": "साझाकरण विवरण लोड करने में विफल"
|
||||
},
|
||||
@@ -537,10 +564,11 @@
|
||||
"pageTitle": "साझाकरण"
|
||||
},
|
||||
"sharesTable": {
|
||||
"ariaLabel": "साझाकरण टेबल",
|
||||
"ariaLabel": "साझाकरण तालिका",
|
||||
"never": "कभी नहीं",
|
||||
"columns": {
|
||||
"name": "नाम",
|
||||
"description": "विवरण",
|
||||
"createdAt": "बनाया गया",
|
||||
"expiresAt": "समाप्त होता है",
|
||||
"status": "स्थिति",
|
||||
@@ -550,7 +578,7 @@
|
||||
"actions": "क्रियाएं"
|
||||
},
|
||||
"status": {
|
||||
"neverExpires": "कभी समाप्त नहीं",
|
||||
"neverExpires": "कभी समाप्त नहीं होता",
|
||||
"active": "सक्रिय",
|
||||
"expired": "समाप्त"
|
||||
},
|
||||
@@ -564,9 +592,9 @@
|
||||
"menu": "साझाकरण क्रिया मेनू",
|
||||
"edit": "संपादित करें",
|
||||
"manageFiles": "फाइलें प्रबंधित करें",
|
||||
"manageRecipients": "प्राप्तकर्ताओं का प्रबंधन करें",
|
||||
"manageRecipients": "प्राप्तकर्ता प्रबंधित करें",
|
||||
"viewDetails": "विवरण देखें",
|
||||
"generateLink": "लिंक उत्पन्न करें",
|
||||
"generateLink": "लिंक जेनरेट करें",
|
||||
"editLink": "लिंक संपादित करें",
|
||||
"copyLink": "लिंक कॉपी करें",
|
||||
"notifyRecipients": "प्राप्तकर्ताओं को सूचित करें",
|
||||
@@ -694,23 +722,47 @@
|
||||
"passwordRequired": "पासवर्ड आवश्यक है"
|
||||
},
|
||||
"shareFile": {
|
||||
"title": "फ़ाइल साझा करें",
|
||||
"title": "फाइल साझा करें",
|
||||
"linkTitle": "लिंक जेनरेट करें",
|
||||
"nameLabel": "शेयर का नाम",
|
||||
"namePlaceholder": "शेयर का नाम दर्ज करें",
|
||||
"expirationLabel": "समाप्ति तारीख",
|
||||
"nameLabel": "साझाकरण नाम",
|
||||
"namePlaceholder": "साझाकरण नाम दर्ज करें",
|
||||
"descriptionLabel": "विवरण",
|
||||
"descriptionPlaceholder": "विवरण दर्ज करें (वैकल्पिक)",
|
||||
"expirationLabel": "समाप्ति तिथि",
|
||||
"expirationPlaceholder": "DD/MM/YYYY HH:MM",
|
||||
"maxViewsLabel": "अधिकतम व्यू",
|
||||
"maxViewsLabel": "अधिकतम दृश्य",
|
||||
"maxViewsPlaceholder": "असीमित के लिए खाली छोड़ें",
|
||||
"passwordProtection": "पासवर्ड सुरक्षित",
|
||||
"passwordProtection": "पासवर्ड संरक्षित",
|
||||
"passwordLabel": "पासवर्ड",
|
||||
"passwordPlaceholder": "पासवर्ड दर्ज करें",
|
||||
"linkDescription": "फ़ाइल साझा करने के लिए एक कस्टम लिंक जेनरेट करें",
|
||||
"aliasLabel": "लिंक एलियास",
|
||||
"aliasPlaceholder": "कस्टम एलियास दर्ज करें",
|
||||
"linkReady": "आपका शेयर लिंक तैयार है:",
|
||||
"createShare": "शेयर बनाएं",
|
||||
"linkDescription": "फाइल साझा करने के लिए कस्टम लिंक जेनरेट करें",
|
||||
"aliasLabel": "लिंक उपनाम",
|
||||
"aliasPlaceholder": "कस्टम उपनाम दर्ज करें",
|
||||
"linkReady": "आपका साझाकरण लिंक तैयार है:",
|
||||
"createShare": "साझाकरण बनाएं",
|
||||
"generateLink": "लिंक जेनरेट करें",
|
||||
"copyLink": "लिंक कॉपी करें"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "बल्क डाउनलोड",
|
||||
"zipNameLabel": "ZIP फाइल नाम",
|
||||
"zipNamePlaceholder": "फाइल नाम दर्ज करें",
|
||||
"description": "{count, plural, =1 {1 फाइल संपीड़ित की जाएगी} other {# फाइलें संपीड़ित की जाएंगी}}",
|
||||
"download": "ZIP डाउनलोड करें"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "हटाई जाने वाली फाइलें"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "कई फाइलें साझा करें",
|
||||
"shareNameLabel": "साझाकरण नाम",
|
||||
"shareNamePlaceholder": "साझाकरण नाम दर्ज करें",
|
||||
"descriptionLabel": "विवरण",
|
||||
"descriptionPlaceholder": "विवरण दर्ज करें (वैकल्पिक)",
|
||||
"filesToShare": "साझा करने के लिए फाइलें",
|
||||
"files": "फाइलें",
|
||||
"totalSize": "कुल आकार",
|
||||
"creating": "बनाया जा रहा है...",
|
||||
"create": "साझाकरण बनाएं"
|
||||
}
|
||||
}
|
||||
@@ -15,15 +15,17 @@
|
||||
"createShare": {
|
||||
"title": "Crea Condivisione",
|
||||
"nameLabel": "Nome Condivisione",
|
||||
"descriptionLabel": "Descrizione",
|
||||
"descriptionPlaceholder": "Inserisci una descrizione (opzionale)",
|
||||
"expirationLabel": "Data di Scadenza",
|
||||
"expirationPlaceholder": "GG/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Visualizzazioni Massime",
|
||||
"maxViewsPlaceholder": "Lascia vuoto per illimitate",
|
||||
"passwordProtection": "Protetto da Parola d'accesso",
|
||||
"passwordLabel": "Parola d'accesso",
|
||||
"maxViewsPlaceholder": "Lascia vuoto per illimitato",
|
||||
"passwordProtection": "Protetto da Password",
|
||||
"passwordLabel": "Password",
|
||||
"create": "Crea Condivisione",
|
||||
"success": "Condivisione creata con successo",
|
||||
"error": "Errore durante la creazione della condivisione"
|
||||
"error": "Errore nella creazione della condivisione"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Errore durante il caricamento dei dati del pannello di controllo",
|
||||
@@ -84,18 +86,25 @@
|
||||
"files": {
|
||||
"title": "Tutti i File",
|
||||
"uploadFile": "Carica File",
|
||||
"loadError": "Errore durante il caricamento dei file",
|
||||
"loadError": "Errore nel caricamento dei file",
|
||||
"pageTitle": "I Miei File",
|
||||
"breadcrumb": "I Miei File",
|
||||
"downloadStart": "Download iniziato",
|
||||
"downloadError": "Errore durante il download del file",
|
||||
"downloadStart": "Download avviato",
|
||||
"downloadError": "Errore nel download del file",
|
||||
"updateSuccess": "File aggiornato con successo",
|
||||
"updateError": "Errore durante l'aggiornamento del file",
|
||||
"updateError": "Errore nell'aggiornamento del file",
|
||||
"deleteSuccess": "File eliminato con successo",
|
||||
"deleteError": "Errore durante l'eliminazione del file"
|
||||
"deleteError": "Errore nell'eliminazione del file",
|
||||
"bulkDownloadSuccess": "Download dei file avviato con successo",
|
||||
"bulkDownloadError": "Errore nella creazione del file ZIP",
|
||||
"bulkDownloadFileError": "Errore nel download del file {fileName}",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {1 file eliminato con successo} other {# file eliminati con successo}}",
|
||||
"bulkDeleteError": "Errore nell'eliminazione dei file selezionati"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Tabella dei file",
|
||||
"selectAll": "Seleziona tutto",
|
||||
"selectFile": "Seleziona file {fileName}",
|
||||
"columns": {
|
||||
"name": "NOME",
|
||||
"description": "DESCRIZIONE",
|
||||
@@ -111,6 +120,13 @@
|
||||
"share": "Condividi",
|
||||
"download": "Scarica",
|
||||
"delete": "Elimina"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {1 file selezionato} other {# file selezionati}}",
|
||||
"actions": "Azioni",
|
||||
"download": "Scarica Selezionati",
|
||||
"share": "Condividi Selezionati",
|
||||
"delete": "Elimina Selezionati"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -460,40 +476,51 @@
|
||||
"deleteConfirmation": "Sei sicuro di voler eliminare questa condivisione? Questa azione non può essere annullata.",
|
||||
"editTitle": "Modifica Condivisione",
|
||||
"nameLabel": "Nome Condivisione",
|
||||
"descriptionLabel": "Descrizione",
|
||||
"descriptionPlaceholder": "Inserisci una descrizione (opzionale)",
|
||||
"expirationLabel": "Data di Scadenza",
|
||||
"expirationPlaceholder": "GG/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Visualizzazioni Massime",
|
||||
"maxViewsPlaceholder": "Lascia vuoto per illimitate",
|
||||
"passwordProtection": "Protetto da Parola d'accesso",
|
||||
"passwordLabel": "Parola d'accesso",
|
||||
"passwordPlaceholder": "Inserisci parola d'accesso",
|
||||
"newPasswordLabel": "Nuova Parola d'accesso (lascia vuoto per mantenere quella attuale)",
|
||||
"newPasswordPlaceholder": "Inserisci nuova parola d'accesso",
|
||||
"maxViewsPlaceholder": "Lasciare vuoto per illimitato",
|
||||
"passwordProtection": "Protetto da Password",
|
||||
"passwordLabel": "Password",
|
||||
"passwordPlaceholder": "Inserisci password",
|
||||
"newPasswordLabel": "Nuova Password (lasciare vuoto per mantenere quella attuale)",
|
||||
"newPasswordPlaceholder": "Inserisci nuova password",
|
||||
"manageFilesTitle": "Gestisci File",
|
||||
"manageRecipientsTitle": "Gestisci Destinatari",
|
||||
"editSuccess": "Condivisione aggiornata con successo",
|
||||
"editError": "Errore durante l'aggiornamento della condivisione"
|
||||
"editError": "Errore nell'aggiornamento della condivisione"
|
||||
},
|
||||
"shareDetails": {
|
||||
"title": "Dettagli Condivisione",
|
||||
"subtitle": "Informazioni dettagliate su questa condivisione",
|
||||
"basicInfo": "Informazioni Base",
|
||||
"name": "Nome",
|
||||
"description": "Descrizione",
|
||||
"noDescription": "Nessuna descrizione fornita",
|
||||
"untitled": "Senza titolo",
|
||||
"shareLink": "Link di Condivisione",
|
||||
"editLink": "Modifica Link",
|
||||
"generateLink": "Genera Link",
|
||||
"noLink": "Nessun link generato ancora",
|
||||
"copyLink": "Copia link",
|
||||
"openLink": "Apri in nuova scheda",
|
||||
"linkCopied": "Link copiato negli appunti",
|
||||
"views": "Visualizzazioni",
|
||||
"dates": "Date",
|
||||
"created": "Creata",
|
||||
"created": "Creato",
|
||||
"expires": "Scade",
|
||||
"never": "Mai",
|
||||
"security": "Sicurezza",
|
||||
"passwordProtected": "Protetta da Parola d'accesso",
|
||||
"passwordProtected": "Protetto da Password",
|
||||
"publicAccess": "Accesso Pubblico",
|
||||
"maxViews": "Visualizzazioni Massime:",
|
||||
"maxViews": "Visualizzazioni Max:",
|
||||
"files": "File",
|
||||
"recipients": "Destinatari",
|
||||
"notAvailable": "N/D",
|
||||
"invalidDate": "Data non valida",
|
||||
"loadError": "Errore durante il caricamento dei dettagli della condivisione"
|
||||
"loadError": "Errore nel caricamento dei dettagli della condivisione"
|
||||
},
|
||||
"shareManager": {
|
||||
"deleteSuccess": "Condivisione eliminata con successo",
|
||||
@@ -541,7 +568,8 @@
|
||||
"never": "Mai",
|
||||
"columns": {
|
||||
"name": "NOME",
|
||||
"createdAt": "CREATA IL",
|
||||
"description": "DESCRIZIONE",
|
||||
"createdAt": "CREATO IL",
|
||||
"expiresAt": "SCADE IL",
|
||||
"status": "STATO",
|
||||
"security": "SICUREZZA",
|
||||
@@ -551,12 +579,12 @@
|
||||
},
|
||||
"status": {
|
||||
"neverExpires": "Non Scade Mai",
|
||||
"active": "Attiva",
|
||||
"expired": "Scaduta"
|
||||
"active": "Attivo",
|
||||
"expired": "Scaduto"
|
||||
},
|
||||
"security": {
|
||||
"protected": "Protetta",
|
||||
"public": "Pubblica"
|
||||
"protected": "Protetto",
|
||||
"public": "Pubblico"
|
||||
},
|
||||
"filesCount": "file",
|
||||
"recipientsCount": "destinatari",
|
||||
@@ -693,23 +721,47 @@
|
||||
"passwordRequired": "La parola d'accesso è obbligatoria"
|
||||
},
|
||||
"shareFile": {
|
||||
"title": "Condividi file",
|
||||
"linkTitle": "Genera link",
|
||||
"nameLabel": "Nome condivisione",
|
||||
"title": "Condividi File",
|
||||
"linkTitle": "Genera Link",
|
||||
"nameLabel": "Nome Condivisione",
|
||||
"namePlaceholder": "Inserisci nome condivisione",
|
||||
"expirationLabel": "Data di scadenza",
|
||||
"descriptionLabel": "Descrizione",
|
||||
"descriptionPlaceholder": "Inserisci una descrizione (opzionale)",
|
||||
"expirationLabel": "Data di Scadenza",
|
||||
"expirationPlaceholder": "GG/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Visualizzazioni massime",
|
||||
"maxViewsPlaceholder": "Lascia vuoto per illimitato",
|
||||
"passwordProtection": "Protetto da password",
|
||||
"maxViewsLabel": "Visualizzazioni Massime",
|
||||
"maxViewsPlaceholder": "Lasciare vuoto per illimitato",
|
||||
"passwordProtection": "Protetto da Password",
|
||||
"passwordLabel": "Password",
|
||||
"passwordPlaceholder": "Inserisci password",
|
||||
"linkDescription": "Genera un link personalizzato per condividere il file",
|
||||
"aliasLabel": "Alias del link",
|
||||
"aliasLabel": "Alias Link",
|
||||
"aliasPlaceholder": "Inserisci alias personalizzato",
|
||||
"linkReady": "Il tuo link di condivisione è pronto:",
|
||||
"createShare": "Crea condivisione",
|
||||
"generateLink": "Genera link",
|
||||
"copyLink": "Copia link"
|
||||
"createShare": "Crea Condivisione",
|
||||
"generateLink": "Genera Link",
|
||||
"copyLink": "Copia Link"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "Download di Massa",
|
||||
"zipNameLabel": "Nome file ZIP",
|
||||
"zipNamePlaceholder": "Inserisci nome file",
|
||||
"description": "{count, plural, =1 {1 file sarà compresso} other {# file saranno compressi}}",
|
||||
"download": "Scarica ZIP"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "File da eliminare"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "Condividi File Multipli",
|
||||
"shareNameLabel": "Nome Condivisione",
|
||||
"shareNamePlaceholder": "Inserisci nome condivisione",
|
||||
"descriptionLabel": "Descrizione",
|
||||
"descriptionPlaceholder": "Inserisci una descrizione (opzionale)",
|
||||
"filesToShare": "File da condividere",
|
||||
"files": "file",
|
||||
"totalSize": "Dimensione totale",
|
||||
"creating": "Creazione...",
|
||||
"create": "Crea Condivisione"
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,12 @@
|
||||
"createShare": {
|
||||
"title": "共有を作成",
|
||||
"nameLabel": "共有名",
|
||||
"descriptionLabel": "説明",
|
||||
"descriptionPlaceholder": "説明を入力してください(任意)",
|
||||
"expirationLabel": "有効期限",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"maxViewsLabel": "最大ビュー数",
|
||||
"maxViewsPlaceholder": "無制限の場合は空白のままにする",
|
||||
"maxViewsLabel": "最大表示回数",
|
||||
"maxViewsPlaceholder": "無制限の場合は空のままにしてください",
|
||||
"passwordProtection": "パスワード保護",
|
||||
"passwordLabel": "パスワード",
|
||||
"create": "共有を作成",
|
||||
@@ -87,22 +89,29 @@
|
||||
"loadError": "ファイルの読み込みに失敗しました",
|
||||
"pageTitle": "マイファイル",
|
||||
"breadcrumb": "マイファイル",
|
||||
"downloadStart": "ダウンロードが開始されました",
|
||||
"downloadStart": "ダウンロードを開始しました",
|
||||
"downloadError": "ファイルのダウンロードに失敗しました",
|
||||
"updateSuccess": "ファイルが正常に更新されました",
|
||||
"updateError": "ファイルの更新に失敗しました",
|
||||
"deleteSuccess": "ファイルが正常に削除されました",
|
||||
"deleteError": "ファイルの削除に失敗しました"
|
||||
"deleteError": "ファイルの削除に失敗しました",
|
||||
"bulkDownloadSuccess": "ファイルのダウンロードが正常に開始されました",
|
||||
"bulkDownloadError": "ZIPファイルの作成エラー",
|
||||
"bulkDownloadFileError": "ファイル {fileName} のダウンロードエラー",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {1つのファイルが正常に削除されました} other {#つのファイルが正常に削除されました}}",
|
||||
"bulkDeleteError": "選択されたファイルの削除エラー"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "ファイルテーブル",
|
||||
"selectAll": "すべて選択",
|
||||
"selectFile": "ファイル {fileName} を選択",
|
||||
"columns": {
|
||||
"name": "名前",
|
||||
"description": "説明",
|
||||
"size": "サイズ",
|
||||
"createdAt": "作成日",
|
||||
"updatedAt": "更新日",
|
||||
"actions": "操作"
|
||||
"createdAt": "作成日時",
|
||||
"updatedAt": "更新日時",
|
||||
"actions": "アクション"
|
||||
},
|
||||
"actions": {
|
||||
"menu": "ファイルアクションメニュー",
|
||||
@@ -111,6 +120,13 @@
|
||||
"share": "共有",
|
||||
"download": "ダウンロード",
|
||||
"delete": "削除"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {1つのファイルが選択されています} other {#つのファイルが選択されています}}",
|
||||
"actions": "アクション",
|
||||
"download": "選択済みをダウンロード",
|
||||
"share": "選択済みを共有",
|
||||
"delete": "選択済みを削除"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -457,43 +473,54 @@
|
||||
},
|
||||
"shareActions": {
|
||||
"deleteTitle": "共有を削除",
|
||||
"deleteConfirmation": "この共有を削除してもよろしいですか?この操作は元に戻せません。",
|
||||
"deleteConfirmation": "この共有を削除しますか?この操作は元に戻すことができません。",
|
||||
"editTitle": "共有を編集",
|
||||
"nameLabel": "共有名",
|
||||
"descriptionLabel": "説明",
|
||||
"descriptionPlaceholder": "説明を入力してください(任意)",
|
||||
"expirationLabel": "有効期限",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"maxViewsLabel": "最大ビュー数",
|
||||
"maxViewsPlaceholder": "無制限の場合は空白のままにする",
|
||||
"expirationPlaceholder": "YYYY/MM/DD HH:MM",
|
||||
"maxViewsLabel": "最大表示回数",
|
||||
"maxViewsPlaceholder": "無制限の場合は空白にしてください",
|
||||
"passwordProtection": "パスワード保護",
|
||||
"passwordLabel": "パスワード",
|
||||
"passwordPlaceholder": "パスワードを入力してください",
|
||||
"newPasswordLabel": "新しいパスワード(現在のパスワードを保持する場合は空白のまま)",
|
||||
"newPasswordLabel": "新しいパスワード(現在のパスワードを保持する場合は空白)",
|
||||
"newPasswordPlaceholder": "新しいパスワードを入力してください",
|
||||
"manageFilesTitle": "ファイルの管理",
|
||||
"manageRecipientsTitle": "受信者の管理",
|
||||
"manageFilesTitle": "ファイル管理",
|
||||
"manageRecipientsTitle": "受信者管理",
|
||||
"editSuccess": "共有が正常に更新されました",
|
||||
"editError": "共有の更新に失敗しました"
|
||||
},
|
||||
"shareDetails": {
|
||||
"title": "共有の詳細",
|
||||
"subtitle": "この共有の詳細情報",
|
||||
"title": "共有詳細",
|
||||
"subtitle": "この共有に関する詳細情報",
|
||||
"basicInfo": "基本情報",
|
||||
"name": "名前",
|
||||
"description": "説明",
|
||||
"noDescription": "説明が提供されていません",
|
||||
"untitled": "無題",
|
||||
"views": "ビュー数",
|
||||
"shareLink": "共有リンク",
|
||||
"editLink": "リンクを編集",
|
||||
"generateLink": "リンクを生成",
|
||||
"noLink": "まだリンクが生成されていません",
|
||||
"copyLink": "リンクをコピー",
|
||||
"openLink": "新しいタブで開く",
|
||||
"linkCopied": "リンクがクリップボードにコピーされました",
|
||||
"views": "表示回数",
|
||||
"dates": "日付",
|
||||
"created": "作成日",
|
||||
"expires": "有効期限",
|
||||
"never": "なし",
|
||||
"security": "セキュリティ",
|
||||
"passwordProtected": "パスワード保護",
|
||||
"publicAccess": "公開アクセス",
|
||||
"maxViews": "最大ビュー数:",
|
||||
"publicAccess": "パブリックアクセス",
|
||||
"maxViews": "最大表示回数:",
|
||||
"files": "ファイル",
|
||||
"recipients": "受信者",
|
||||
"notAvailable": "該当なし",
|
||||
"invalidDate": "無効な日付です",
|
||||
"loadError": "共有の詳細の読み込みに失敗しました"
|
||||
"notAvailable": "N/A",
|
||||
"invalidDate": "無効な日付",
|
||||
"loadError": "共有詳細の読み込みに失敗しました"
|
||||
},
|
||||
"shareManager": {
|
||||
"deleteSuccess": "共有が正常に削除されました",
|
||||
@@ -541,16 +568,17 @@
|
||||
"never": "なし",
|
||||
"columns": {
|
||||
"name": "名前",
|
||||
"description": "説明",
|
||||
"createdAt": "作成日",
|
||||
"expiresAt": "有効期限",
|
||||
"status": "ステータス",
|
||||
"security": "セキュリティ",
|
||||
"files": "ファイル",
|
||||
"recipients": "受信者",
|
||||
"actions": "操作"
|
||||
"actions": "アクション"
|
||||
},
|
||||
"status": {
|
||||
"neverExpires": "無期限",
|
||||
"neverExpires": "期限なし",
|
||||
"active": "アクティブ",
|
||||
"expired": "期限切れ"
|
||||
},
|
||||
@@ -561,14 +589,14 @@
|
||||
"filesCount": "ファイル",
|
||||
"recipientsCount": "受信者",
|
||||
"actions": {
|
||||
"menu": "共有操作メニュー",
|
||||
"menu": "共有アクションメニュー",
|
||||
"edit": "編集",
|
||||
"manageFiles": "ファイル管理",
|
||||
"manageRecipients": "受信者管理",
|
||||
"viewDetails": "詳細を表示",
|
||||
"generateLink": "リンクを生成",
|
||||
"editLink": "リンクを編集",
|
||||
"copyLink": "リンクをコピー",
|
||||
"viewDetails": "詳細表示",
|
||||
"generateLink": "リンク生成",
|
||||
"editLink": "リンク編集",
|
||||
"copyLink": "リンクコピー",
|
||||
"notifyRecipients": "受信者に通知",
|
||||
"delete": "削除"
|
||||
}
|
||||
@@ -679,23 +707,47 @@
|
||||
"passwordRequired": "パスワードは必須です"
|
||||
},
|
||||
"shareFile": {
|
||||
"title": "ファイルを共有",
|
||||
"linkTitle": "リンクを生成",
|
||||
"title": "ファイル共有",
|
||||
"linkTitle": "リンク生成",
|
||||
"nameLabel": "共有名",
|
||||
"namePlaceholder": "共有名を入力",
|
||||
"namePlaceholder": "共有名を入力してください",
|
||||
"descriptionLabel": "説明",
|
||||
"descriptionPlaceholder": "説明を入力してください(任意)",
|
||||
"expirationLabel": "有効期限",
|
||||
"expirationPlaceholder": "YYYY/MM/DD HH:MM",
|
||||
"maxViewsLabel": "最大閲覧回数",
|
||||
"maxViewsPlaceholder": "無制限の場合は空欄",
|
||||
"maxViewsLabel": "最大表示回数",
|
||||
"maxViewsPlaceholder": "無制限の場合は空白にしてください",
|
||||
"passwordProtection": "パスワード保護",
|
||||
"passwordLabel": "パスワード",
|
||||
"passwordPlaceholder": "パスワードを入力",
|
||||
"linkDescription": "ファイルを共有するためのカスタムリンクを生成します",
|
||||
"passwordPlaceholder": "パスワードを入力してください",
|
||||
"linkDescription": "ファイルを共有するためのカスタムリンクを生成する",
|
||||
"aliasLabel": "リンクエイリアス",
|
||||
"aliasPlaceholder": "カスタムエイリアスを入力",
|
||||
"aliasPlaceholder": "カスタムエイリアスを入力してください",
|
||||
"linkReady": "共有リンクの準備ができました:",
|
||||
"createShare": "共有を作成",
|
||||
"generateLink": "リンクを生成",
|
||||
"copyLink": "リンクをコピー"
|
||||
"generateLink": "リンク生成",
|
||||
"copyLink": "リンクコピー"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "一括ダウンロード",
|
||||
"zipNameLabel": "ZIPファイル名",
|
||||
"zipNamePlaceholder": "ファイル名を入力してください",
|
||||
"description": "{count, plural, =1 {1つのファイルが圧縮されます} other {#つのファイルが圧縮されます}}",
|
||||
"download": "ZIPをダウンロード"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "削除するファイル"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "複数ファイルを共有",
|
||||
"shareNameLabel": "共有名",
|
||||
"shareNamePlaceholder": "共有名を入力してください",
|
||||
"descriptionLabel": "説明",
|
||||
"descriptionPlaceholder": "説明を入力してください(任意)",
|
||||
"filesToShare": "共有するファイル",
|
||||
"files": "ファイル",
|
||||
"totalSize": "合計サイズ",
|
||||
"creating": "作成中...",
|
||||
"create": "共有を作成"
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,12 @@
|
||||
"createShare": {
|
||||
"title": "공유 생성",
|
||||
"nameLabel": "공유 이름",
|
||||
"descriptionLabel": "설명",
|
||||
"descriptionPlaceholder": "설명을 입력하세요 (선택사항)",
|
||||
"expirationLabel": "만료 날짜",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"maxViewsLabel": "최대 조회수",
|
||||
"maxViewsPlaceholder": "무제한인 경우 비워두세요",
|
||||
"maxViewsPlaceholder": "무제한은 비워두세요",
|
||||
"passwordProtection": "비밀번호 보호",
|
||||
"passwordLabel": "비밀번호",
|
||||
"create": "공유 생성",
|
||||
@@ -92,10 +94,17 @@
|
||||
"updateSuccess": "파일이 성공적으로 업데이트되었습니다",
|
||||
"updateError": "파일 업데이트에 실패했습니다",
|
||||
"deleteSuccess": "파일이 성공적으로 삭제되었습니다",
|
||||
"deleteError": "파일 삭제에 실패했습니다"
|
||||
"deleteError": "파일 삭제에 실패했습니다",
|
||||
"bulkDownloadSuccess": "파일 다운로드가 성공적으로 시작되었습니다",
|
||||
"bulkDownloadError": "ZIP 파일 생성 오류",
|
||||
"bulkDownloadFileError": "파일 {fileName} 다운로드 오류",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {1개 파일이 성공적으로 삭제되었습니다} other {#개 파일이 성공적으로 삭제되었습니다}}",
|
||||
"bulkDeleteError": "선택된 파일 삭제 오류"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "파일 테이블",
|
||||
"selectAll": "모두 선택",
|
||||
"selectFile": "파일 {fileName} 선택",
|
||||
"columns": {
|
||||
"name": "이름",
|
||||
"description": "설명",
|
||||
@@ -111,6 +120,13 @@
|
||||
"share": "공유",
|
||||
"download": "다운로드",
|
||||
"delete": "삭제"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {1개 파일이 선택됨} other {#개 파일이 선택됨}}",
|
||||
"actions": "작업",
|
||||
"download": "선택된 항목 다운로드",
|
||||
"share": "선택된 항목 공유",
|
||||
"delete": "선택된 항목 삭제"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -460,40 +476,51 @@
|
||||
"deleteConfirmation": "이 공유를 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.",
|
||||
"editTitle": "공유 편집",
|
||||
"nameLabel": "공유 이름",
|
||||
"descriptionLabel": "설명",
|
||||
"descriptionPlaceholder": "설명을 입력하세요 (선택사항)",
|
||||
"expirationLabel": "만료 날짜",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"expirationPlaceholder": "YYYY/MM/DD HH:MM",
|
||||
"maxViewsLabel": "최대 조회수",
|
||||
"maxViewsPlaceholder": "무제한인 경우 비워두세요",
|
||||
"maxViewsPlaceholder": "무제한은 공백으로 두세요",
|
||||
"passwordProtection": "비밀번호 보호",
|
||||
"passwordLabel": "비밀번호",
|
||||
"passwordPlaceholder": "비밀번호를 입력하세요",
|
||||
"newPasswordLabel": "새 비밀번호 (현재 비밀번호 유지 시 비워두세요)",
|
||||
"newPasswordLabel": "새 비밀번호 (현재 비밀번호를 유지하려면 공백으로 두세요)",
|
||||
"newPasswordPlaceholder": "새 비밀번호를 입력하세요",
|
||||
"manageFilesTitle": "파일 관리",
|
||||
"manageRecipientsTitle": "수신자 관리",
|
||||
"manageRecipientsTitle": "받는 사람 관리",
|
||||
"editSuccess": "공유가 성공적으로 업데이트되었습니다",
|
||||
"editError": "공유 업데이트에 실패했습니다"
|
||||
},
|
||||
"shareDetails": {
|
||||
"title": "공유 세부 정보",
|
||||
"subtitle": "이 공유에 대한 자세한 정보",
|
||||
"subtitle": "이 공유에 대한 상세 정보",
|
||||
"basicInfo": "기본 정보",
|
||||
"name": "이름",
|
||||
"description": "설명",
|
||||
"noDescription": "설명이 제공되지 않았습니다",
|
||||
"untitled": "제목 없음",
|
||||
"shareLink": "공유 링크",
|
||||
"editLink": "링크 편집",
|
||||
"generateLink": "링크 생성",
|
||||
"noLink": "아직 링크가 생성되지 않았습니다",
|
||||
"copyLink": "링크 복사",
|
||||
"openLink": "새 탭에서 열기",
|
||||
"linkCopied": "링크가 클립보드에 복사되었습니다",
|
||||
"views": "조회수",
|
||||
"dates": "날짜",
|
||||
"created": "생성됨",
|
||||
"expires": "만료됨",
|
||||
"expires": "만료",
|
||||
"never": "없음",
|
||||
"security": "보안",
|
||||
"passwordProtected": "비밀번호 보호됨",
|
||||
"publicAccess": "공개 접근",
|
||||
"passwordProtected": "비밀번호 보호",
|
||||
"publicAccess": "공개 액세스",
|
||||
"maxViews": "최대 조회수:",
|
||||
"files": "파일",
|
||||
"recipients": "수신자",
|
||||
"notAvailable": "해당 없음",
|
||||
"invalidDate": "유효하지 않은 날짜입니다",
|
||||
"loadError": "공유 세부 정보를 불러오는데 실패했습니다"
|
||||
"recipients": "받는 사람",
|
||||
"notAvailable": "N/A",
|
||||
"invalidDate": "잘못된 날짜",
|
||||
"loadError": "공유 세부 정보 로드에 실패했습니다"
|
||||
},
|
||||
"shareManager": {
|
||||
"deleteSuccess": "공유가 성공적으로 삭제되었습니다",
|
||||
@@ -541,12 +568,13 @@
|
||||
"never": "없음",
|
||||
"columns": {
|
||||
"name": "이름",
|
||||
"description": "설명",
|
||||
"createdAt": "생성일",
|
||||
"expiresAt": "만료일",
|
||||
"status": "상태",
|
||||
"security": "보안",
|
||||
"files": "파일",
|
||||
"recipients": "수신자",
|
||||
"recipients": "받는 사람",
|
||||
"actions": "작업"
|
||||
},
|
||||
"status": {
|
||||
@@ -558,18 +586,18 @@
|
||||
"protected": "보호됨",
|
||||
"public": "공개"
|
||||
},
|
||||
"filesCount": "개의 파일",
|
||||
"recipientsCount": "명의 수신자",
|
||||
"filesCount": "파일",
|
||||
"recipientsCount": "받는 사람",
|
||||
"actions": {
|
||||
"menu": "공유 작업 메뉴",
|
||||
"edit": "편집",
|
||||
"manageFiles": "파일 관리",
|
||||
"manageRecipients": "수신자 관리",
|
||||
"manageRecipients": "받는 사람 관리",
|
||||
"viewDetails": "세부 정보 보기",
|
||||
"generateLink": "링크 생성",
|
||||
"editLink": "링크 편집",
|
||||
"copyLink": "링크 복사",
|
||||
"notifyRecipients": "수신자 알림",
|
||||
"notifyRecipients": "받는 사람에게 알림",
|
||||
"delete": "삭제"
|
||||
}
|
||||
},
|
||||
@@ -697,20 +725,44 @@
|
||||
"title": "파일 공유",
|
||||
"linkTitle": "링크 생성",
|
||||
"nameLabel": "공유 이름",
|
||||
"namePlaceholder": "공유 이름 입력",
|
||||
"namePlaceholder": "공유 이름을 입력하세요",
|
||||
"descriptionLabel": "설명",
|
||||
"descriptionPlaceholder": "설명을 입력하세요 (선택사항)",
|
||||
"expirationLabel": "만료 날짜",
|
||||
"expirationPlaceholder": "YYYY.MM.DD HH:MM",
|
||||
"expirationPlaceholder": "YYYY/MM/DD HH:MM",
|
||||
"maxViewsLabel": "최대 조회수",
|
||||
"maxViewsPlaceholder": "무제한은 비워두세요",
|
||||
"maxViewsPlaceholder": "무제한은 공백으로 두세요",
|
||||
"passwordProtection": "비밀번호 보호",
|
||||
"passwordLabel": "비밀번호",
|
||||
"passwordPlaceholder": "비밀번호 입력",
|
||||
"linkDescription": "파일을 공유할 맞춤 링크를 생성합니다",
|
||||
"passwordPlaceholder": "비밀번호를 입력하세요",
|
||||
"linkDescription": "파일을 공유할 맞춤 링크를 생성하세요",
|
||||
"aliasLabel": "링크 별칭",
|
||||
"aliasPlaceholder": "맞춤 별칭 입력",
|
||||
"aliasPlaceholder": "맞춤 별칭을 입력하세요",
|
||||
"linkReady": "공유 링크가 준비되었습니다:",
|
||||
"createShare": "공유 생성",
|
||||
"generateLink": "링크 생성",
|
||||
"copyLink": "링크 복사"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "일괄 다운로드",
|
||||
"zipNameLabel": "ZIP 파일명",
|
||||
"zipNamePlaceholder": "파일명을 입력하세요",
|
||||
"description": "{count, plural, =1 {1개 파일이 압축됩니다} other {#개 파일이 압축됩니다}}",
|
||||
"download": "ZIP 다운로드"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "삭제할 파일"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "여러 파일 공유",
|
||||
"shareNameLabel": "공유 이름",
|
||||
"shareNamePlaceholder": "공유 이름을 입력하세요",
|
||||
"descriptionLabel": "설명",
|
||||
"descriptionPlaceholder": "설명을 입력하세요 (선택사항)",
|
||||
"filesToShare": "공유할 파일",
|
||||
"files": "파일",
|
||||
"totalSize": "전체 크기",
|
||||
"creating": "생성 중...",
|
||||
"create": "공유 생성"
|
||||
}
|
||||
}
|
||||
@@ -14,16 +14,18 @@
|
||||
},
|
||||
"createShare": {
|
||||
"title": "Delen Maken",
|
||||
"nameLabel": "Naam van Delen",
|
||||
"nameLabel": "Delen Naam",
|
||||
"descriptionLabel": "Beschrijving",
|
||||
"descriptionPlaceholder": "Voer een beschrijving in (optioneel)",
|
||||
"expirationLabel": "Vervaldatum",
|
||||
"expirationPlaceholder": "DD/MM/JJJJ UU:MM",
|
||||
"maxViewsLabel": "Maximum Weergaven",
|
||||
"maxViewsLabel": "Maximale Weergaves",
|
||||
"maxViewsPlaceholder": "Laat leeg voor onbeperkt",
|
||||
"passwordProtection": "Wachtwoord Beveiligd",
|
||||
"passwordLabel": "Wachtwoord",
|
||||
"create": "Delen Maken",
|
||||
"success": "Delen succesvol aangemaakt",
|
||||
"error": "Fout bij het maken van delen"
|
||||
"error": "Fout bij het aanmaken van delen"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "Fout bij het laden van controlepaneel gegevens",
|
||||
@@ -92,10 +94,17 @@
|
||||
"updateSuccess": "Bestand succesvol bijgewerkt",
|
||||
"updateError": "Fout bij het bijwerken van bestand",
|
||||
"deleteSuccess": "Bestand succesvol verwijderd",
|
||||
"deleteError": "Fout bij het verwijderen van bestand"
|
||||
"deleteError": "Fout bij het verwijderen van bestand",
|
||||
"bulkDownloadSuccess": "Bestanden download succesvol gestart",
|
||||
"bulkDownloadError": "Fout bij het maken van ZIP-bestand",
|
||||
"bulkDownloadFileError": "Fout bij het downloaden van bestand {fileName}",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {1 bestand succesvol verwijderd} other {# bestanden succesvol verwijderd}}",
|
||||
"bulkDeleteError": "Fout bij het verwijderen van geselecteerde bestanden"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Bestanden tabel",
|
||||
"selectAll": "Alles selecteren",
|
||||
"selectFile": "Bestand {fileName} selecteren",
|
||||
"columns": {
|
||||
"name": "NAAM",
|
||||
"description": "BESCHRIJVING",
|
||||
@@ -111,6 +120,13 @@
|
||||
"share": "Delen",
|
||||
"download": "Downloaden",
|
||||
"delete": "Verwijderen"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {1 bestand geselecteerd} other {# bestanden geselecteerd}}",
|
||||
"actions": "Acties",
|
||||
"download": "Geselecteerde Downloaden",
|
||||
"share": "Geselecteerde Delen",
|
||||
"delete": "Geselecteerde Verwijderen"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -459,41 +475,52 @@
|
||||
"deleteTitle": "Delen Verwijderen",
|
||||
"deleteConfirmation": "Weet je zeker dat je dit delen wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.",
|
||||
"editTitle": "Delen Bewerken",
|
||||
"nameLabel": "Deel Naam",
|
||||
"nameLabel": "Delen Naam",
|
||||
"descriptionLabel": "Beschrijving",
|
||||
"descriptionPlaceholder": "Voer een beschrijving in (optioneel)",
|
||||
"expirationLabel": "Vervaldatum",
|
||||
"expirationPlaceholder": "DD/MM/JJJJ UU:MM",
|
||||
"maxViewsLabel": "Maximum Weergaven",
|
||||
"maxViewsLabel": "Maximale Weergaven",
|
||||
"maxViewsPlaceholder": "Laat leeg voor onbeperkt",
|
||||
"passwordProtection": "Wachtwoord Beveiligd",
|
||||
"passwordLabel": "Wachtwoord",
|
||||
"passwordPlaceholder": "Voer wachtwoord in",
|
||||
"newPasswordLabel": "Nieuw Wachtwoord (laat leeg om huidige te behouden)",
|
||||
"newPasswordLabel": "Nieuw Wachtwoord (laat leeg om huidig te behouden)",
|
||||
"newPasswordPlaceholder": "Voer nieuw wachtwoord in",
|
||||
"manageFilesTitle": "Bestanden Beheren",
|
||||
"manageRecipientsTitle": "Ontvangers Beheren",
|
||||
"editSuccess": "Delen succesvol bijgewerkt",
|
||||
"editError": "Fout bij het bijwerken van delen"
|
||||
"editError": "Fout bij bijwerken van delen"
|
||||
},
|
||||
"shareDetails": {
|
||||
"title": "Deel Details",
|
||||
"title": "Delen Details",
|
||||
"subtitle": "Gedetailleerde informatie over dit delen",
|
||||
"basicInfo": "Basis Informatie",
|
||||
"basicInfo": "Basisinformatie",
|
||||
"name": "Naam",
|
||||
"description": "Beschrijving",
|
||||
"noDescription": "Geen beschrijving opgegeven",
|
||||
"untitled": "Naamloos",
|
||||
"shareLink": "Delen Link",
|
||||
"editLink": "Link Bewerken",
|
||||
"generateLink": "Link Genereren",
|
||||
"noLink": "Nog geen link gegenereerd",
|
||||
"copyLink": "Link kopiëren",
|
||||
"openLink": "Openen in nieuw tabblad",
|
||||
"linkCopied": "Link gekopieerd naar klembord",
|
||||
"views": "Weergaven",
|
||||
"dates": "Datums",
|
||||
"dates": "Data",
|
||||
"created": "Aangemaakt",
|
||||
"expires": "Verloopt",
|
||||
"never": "Nooit",
|
||||
"security": "Beveiliging",
|
||||
"passwordProtected": "Wachtwoord Beveiligd",
|
||||
"publicAccess": "Publieke Toegang",
|
||||
"maxViews": "Maximum Weergaven:",
|
||||
"publicAccess": "Openbare Toegang",
|
||||
"maxViews": "Max. Weergaven:",
|
||||
"files": "Bestanden",
|
||||
"recipients": "Ontvangers",
|
||||
"notAvailable": "N.v.t.",
|
||||
"notAvailable": "N/B",
|
||||
"invalidDate": "Ongeldige datum",
|
||||
"loadError": "Fout bij het laden van deel details"
|
||||
"loadError": "Fout bij laden van delen details"
|
||||
},
|
||||
"shareManager": {
|
||||
"deleteSuccess": "Delen succesvol verwijderd",
|
||||
@@ -537,10 +564,11 @@
|
||||
"pageTitle": "Delingen"
|
||||
},
|
||||
"sharesTable": {
|
||||
"ariaLabel": "Delingen tabel",
|
||||
"ariaLabel": "Delen tabel",
|
||||
"never": "Nooit",
|
||||
"columns": {
|
||||
"name": "NAAM",
|
||||
"description": "BESCHRIJVING",
|
||||
"createdAt": "AANGEMAAKT OP",
|
||||
"expiresAt": "VERLOOPT OP",
|
||||
"status": "STATUS",
|
||||
@@ -556,12 +584,12 @@
|
||||
},
|
||||
"security": {
|
||||
"protected": "Beveiligd",
|
||||
"public": "Publiek"
|
||||
"public": "Openbaar"
|
||||
},
|
||||
"filesCount": "bestanden",
|
||||
"recipientsCount": "ontvangers",
|
||||
"actions": {
|
||||
"menu": "Deel acties menu",
|
||||
"menu": "Delen acties menu",
|
||||
"edit": "Bewerken",
|
||||
"manageFiles": "Bestanden Beheren",
|
||||
"manageRecipients": "Ontvangers Beheren",
|
||||
@@ -569,7 +597,7 @@
|
||||
"generateLink": "Link Genereren",
|
||||
"editLink": "Link Bewerken",
|
||||
"copyLink": "Link Kopiëren",
|
||||
"notifyRecipients": "Ontvangers Waarschuwen",
|
||||
"notifyRecipients": "Ontvangers Informeren",
|
||||
"delete": "Verwijderen"
|
||||
}
|
||||
},
|
||||
@@ -693,23 +721,47 @@
|
||||
"passwordRequired": "Wachtwoord is verplicht"
|
||||
},
|
||||
"shareFile": {
|
||||
"title": "Bestand delen",
|
||||
"linkTitle": "Link genereren",
|
||||
"nameLabel": "Deel naam",
|
||||
"namePlaceholder": "Voer deel naam in",
|
||||
"title": "Bestand Delen",
|
||||
"linkTitle": "Link Genereren",
|
||||
"nameLabel": "Delen Naam",
|
||||
"namePlaceholder": "Voer delen naam in",
|
||||
"descriptionLabel": "Beschrijving",
|
||||
"descriptionPlaceholder": "Voer een beschrijving in (optioneel)",
|
||||
"expirationLabel": "Vervaldatum",
|
||||
"expirationPlaceholder": "DD/MM/JJJJ UU:MM",
|
||||
"maxViewsLabel": "Maximum weergaven",
|
||||
"maxViewsLabel": "Maximale Weergaven",
|
||||
"maxViewsPlaceholder": "Laat leeg voor onbeperkt",
|
||||
"passwordProtection": "Wachtwoord beschermd",
|
||||
"passwordProtection": "Wachtwoord Beveiligd",
|
||||
"passwordLabel": "Wachtwoord",
|
||||
"passwordPlaceholder": "Voer wachtwoord in",
|
||||
"linkDescription": "Genereer een aangepaste link om het bestand te delen",
|
||||
"aliasLabel": "Link alias",
|
||||
"aliasLabel": "Link Alias",
|
||||
"aliasPlaceholder": "Voer aangepaste alias in",
|
||||
"linkReady": "Uw deel-link is klaar:",
|
||||
"createShare": "Deel maken",
|
||||
"generateLink": "Link genereren",
|
||||
"copyLink": "Link kopiëren"
|
||||
"linkReady": "Jouw delen link is klaar:",
|
||||
"createShare": "Delen Aanmaken",
|
||||
"generateLink": "Link Genereren",
|
||||
"copyLink": "Link Kopiëren"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "Bulk Download",
|
||||
"zipNameLabel": "ZIP bestandsnaam",
|
||||
"zipNamePlaceholder": "Voer bestandsnaam in",
|
||||
"description": "{count, plural, =1 {1 bestand wordt gecomprimeerd} other {# bestanden worden gecomprimeerd}}",
|
||||
"download": "ZIP Downloaden"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "Te verwijderen bestanden"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "Meerdere Bestanden Delen",
|
||||
"shareNameLabel": "Delen Naam",
|
||||
"shareNamePlaceholder": "Voer delen naam in",
|
||||
"descriptionLabel": "Beschrijving",
|
||||
"descriptionPlaceholder": "Voer een beschrijving in (optioneel)",
|
||||
"filesToShare": "Bestanden om te delen",
|
||||
"files": "bestanden",
|
||||
"totalSize": "Totale grootte",
|
||||
"creating": "Aanmaken...",
|
||||
"create": "Delen Maken"
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,8 @@
|
||||
"createShare": {
|
||||
"title": "Criar Compartilhamento",
|
||||
"nameLabel": "Nome do Compartilhamento",
|
||||
"descriptionLabel": "Descrição",
|
||||
"descriptionPlaceholder": "Digite uma descrição (opcional)",
|
||||
"expirationLabel": "Data de Expiração",
|
||||
"expirationPlaceholder": "DD/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Máximo de Visualizações",
|
||||
@@ -92,10 +94,17 @@
|
||||
"updateSuccess": "Arquivo atualizado com sucesso",
|
||||
"updateError": "Erro ao atualizar o arquivo",
|
||||
"deleteSuccess": "Arquivo excluído com sucesso",
|
||||
"deleteError": "Erro ao excluir o arquivo"
|
||||
"deleteError": "Erro ao excluir o arquivo",
|
||||
"bulkDownloadSuccess": "Download dos arquivos iniciado com sucesso",
|
||||
"bulkDownloadError": "Erro ao criar arquivo ZIP",
|
||||
"bulkDownloadFileError": "Erro ao baixar arquivo {fileName}",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {1 arquivo excluído com sucesso} other {# arquivos excluídos com sucesso}}",
|
||||
"bulkDeleteError": "Erro ao excluir arquivos selecionados"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Tabela de arquivos",
|
||||
"selectAll": "Selecionar todos",
|
||||
"selectFile": "Selecionar arquivo {fileName}",
|
||||
"columns": {
|
||||
"name": "NOME",
|
||||
"description": "DESCRIÇÃO",
|
||||
@@ -111,6 +120,13 @@
|
||||
"share": "Compartilhar",
|
||||
"download": "Baixar",
|
||||
"delete": "Excluir"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {1 arquivo selecionado} other {# arquivos selecionados}}",
|
||||
"actions": "Ações",
|
||||
"download": "Baixar Selecionados",
|
||||
"share": "Compartilhar Selecionados",
|
||||
"delete": "Excluir Selecionados"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -147,6 +163,8 @@
|
||||
"linkTitle": "Gerar Link",
|
||||
"nameLabel": "Nome do Compartilhamento",
|
||||
"namePlaceholder": "Digite o nome do compartilhamento",
|
||||
"descriptionLabel": "Descrição",
|
||||
"descriptionPlaceholder": "Digite uma descrição (opcional)",
|
||||
"expirationLabel": "Data de Expiração",
|
||||
"expirationPlaceholder": "DD/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Máximo de Visualizações",
|
||||
@@ -480,6 +498,8 @@
|
||||
"deleteConfirmation": "Tem certeza que deseja excluir este compartilhamento? Esta ação não pode ser desfeita.",
|
||||
"editTitle": "Editar Compartilhamento",
|
||||
"nameLabel": "Nome do Compartilhamento",
|
||||
"descriptionLabel": "Descrição",
|
||||
"descriptionPlaceholder": "Digite uma descrição (opcional)",
|
||||
"expirationLabel": "Data de Expiração",
|
||||
"expirationPlaceholder": "DD/MM/AAAA HH:MM",
|
||||
"maxViewsLabel": "Máximo de Visualizações",
|
||||
@@ -499,16 +519,25 @@
|
||||
"subtitle": "Informações detalhadas sobre este compartilhamento",
|
||||
"basicInfo": "Informações Básicas",
|
||||
"name": "Nome",
|
||||
"untitled": "Compartilhamento sem título",
|
||||
"description": "Descrição",
|
||||
"noDescription": "Nenhuma descrição fornecida",
|
||||
"untitled": "Sem título",
|
||||
"shareLink": "Link de Compartilhamento",
|
||||
"editLink": "Editar Link",
|
||||
"generateLink": "Gerar Link",
|
||||
"noLink": "Nenhum link gerado ainda",
|
||||
"copyLink": "Copiar link",
|
||||
"openLink": "Abrir em nova guia",
|
||||
"linkCopied": "Link copiado para a área de transferência",
|
||||
"views": "Visualizações",
|
||||
"dates": "Datas",
|
||||
"created": "Criado em: {date}",
|
||||
"expires": "Expira em: {date}",
|
||||
"created": "Criado",
|
||||
"expires": "Expira",
|
||||
"never": "Nunca",
|
||||
"security": "Segurança",
|
||||
"passwordProtected": "Protegido por Senha",
|
||||
"publicAccess": "Acesso Público",
|
||||
"maxViews": "Máximo de Visualizações:",
|
||||
"maxViews": "Máx. Visualizações:",
|
||||
"files": "Arquivos",
|
||||
"recipients": "Destinatários",
|
||||
"notAvailable": "N/A",
|
||||
@@ -561,6 +590,7 @@
|
||||
"never": "Nunca",
|
||||
"columns": {
|
||||
"name": "NOME",
|
||||
"description": "DESCRIÇÃO",
|
||||
"createdAt": "CRIADO EM",
|
||||
"expiresAt": "EXPIRA EM",
|
||||
"status": "STATUS",
|
||||
@@ -712,5 +742,27 @@
|
||||
"passwordsMatch": "As senhas não coincidem",
|
||||
"emailRequired": "Email é obrigatório",
|
||||
"passwordRequired": "Senha é obrigatória"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "Download em Lote",
|
||||
"zipNameLabel": "Nome do arquivo ZIP",
|
||||
"zipNamePlaceholder": "Digite o nome do arquivo",
|
||||
"description": "{count, plural, =1 {1 arquivo será compactado} other {# arquivos serão compactados}}",
|
||||
"download": "Baixar ZIP"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "Arquivos que serão excluídos"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "Compartilhar Múltiplos Arquivos",
|
||||
"shareNameLabel": "Nome do Compartilhamento",
|
||||
"shareNamePlaceholder": "Digite o nome do compartilhamento",
|
||||
"descriptionLabel": "Descrição",
|
||||
"descriptionPlaceholder": "Digite uma descrição (opcional)",
|
||||
"filesToShare": "Arquivos para compartilhar",
|
||||
"files": "arquivos",
|
||||
"totalSize": "Tamanho total",
|
||||
"creating": "Criando...",
|
||||
"create": "Criar Compartilhamento"
|
||||
}
|
||||
}
|
||||
@@ -456,39 +456,50 @@
|
||||
"pageTitle": "Общий доступ"
|
||||
},
|
||||
"shareActions": {
|
||||
"deleteTitle": "Удалить общий доступ",
|
||||
"deleteConfirmation": "Вы уверены, что хотите удалить этот общий доступ? Это действие необратимо.",
|
||||
"editTitle": "Редактировать общий доступ",
|
||||
"nameLabel": "Название общего доступа",
|
||||
"expirationLabel": "Дата истечения",
|
||||
"expirationPlaceholder": "MM/DD/YYYY ЧЧ:ММ",
|
||||
"maxViewsLabel": "Максимальное количество просмотров",
|
||||
"maxViewsPlaceholder": "Оставьте пустым для неограниченного доступа",
|
||||
"passwordProtection": "Защищено паролем",
|
||||
"deleteTitle": "Удалить Общий Доступ",
|
||||
"deleteConfirmation": "Вы уверены, что хотите удалить этот общий доступ? Это действие нельзя отменить.",
|
||||
"editTitle": "Редактировать Общий Доступ",
|
||||
"nameLabel": "Название Общего Доступа",
|
||||
"descriptionLabel": "Описание",
|
||||
"descriptionPlaceholder": "Введите описание (опционально)",
|
||||
"expirationLabel": "Дата Истечения",
|
||||
"expirationPlaceholder": "ДД.ММ.ГГГГ ЧЧ:ММ",
|
||||
"maxViewsLabel": "Максимальные Просмотры",
|
||||
"maxViewsPlaceholder": "Оставьте пустым для неограниченного",
|
||||
"passwordProtection": "Защищено Паролем",
|
||||
"passwordLabel": "Пароль",
|
||||
"passwordPlaceholder": "Введите пароль",
|
||||
"newPasswordLabel": "Новый пароль (оставьте пустым, чтобы сохранить текущий)",
|
||||
"newPasswordLabel": "Новый Пароль (оставьте пустым, чтобы сохранить текущий)",
|
||||
"newPasswordPlaceholder": "Введите новый пароль",
|
||||
"manageFilesTitle": "Управление файлами",
|
||||
"manageRecipientsTitle": "Управление получателями",
|
||||
"editSuccess": "Общий доступ успешно обновлён",
|
||||
"editError": "Ошибка при обновлении общего доступа"
|
||||
"manageFilesTitle": "Управление Файлами",
|
||||
"manageRecipientsTitle": "Управление Получателями",
|
||||
"editSuccess": "Общий доступ успешно обновлен",
|
||||
"editError": "Ошибка обновления общего доступа"
|
||||
},
|
||||
"shareDetails": {
|
||||
"title": "Детали общего доступа",
|
||||
"title": "Детали Общего Доступа",
|
||||
"subtitle": "Подробная информация об этом общем доступе",
|
||||
"basicInfo": "Основная информация",
|
||||
"basicInfo": "Основная Информация",
|
||||
"name": "Название",
|
||||
"description": "Описание",
|
||||
"noDescription": "Описание не предоставлено",
|
||||
"untitled": "Без названия",
|
||||
"shareLink": "Ссылка Общего Доступа",
|
||||
"editLink": "Редактировать Ссылку",
|
||||
"generateLink": "Сгенерировать Ссылку",
|
||||
"noLink": "Ссылка еще не сгенерирована",
|
||||
"copyLink": "Скопировать ссылку",
|
||||
"openLink": "Открыть в новой вкладке",
|
||||
"linkCopied": "Ссылка скопирована в буфер обмена",
|
||||
"views": "Просмотры",
|
||||
"dates": "Даты",
|
||||
"created": "Создано",
|
||||
"expires": "Истекает",
|
||||
"never": "Никогда",
|
||||
"security": "Безопасность",
|
||||
"passwordProtected": "Защищено паролем",
|
||||
"publicAccess": "Публичный доступ",
|
||||
"maxViews": "Максимум просмотров:",
|
||||
"passwordProtected": "Защищено Паролем",
|
||||
"publicAccess": "Публичный Доступ",
|
||||
"maxViews": "Макс. Просмотры:",
|
||||
"files": "Файлы",
|
||||
"recipients": "Получатели",
|
||||
"notAvailable": "Н/Д",
|
||||
@@ -537,10 +548,11 @@
|
||||
"pageTitle": "Общие доступы"
|
||||
},
|
||||
"sharesTable": {
|
||||
"ariaLabel": "Таблица общего доступа",
|
||||
"ariaLabel": "Таблица общих доступов",
|
||||
"never": "Никогда",
|
||||
"columns": {
|
||||
"name": "ИМЯ",
|
||||
"name": "НАЗВАНИЕ",
|
||||
"description": "ОПИСАНИЕ",
|
||||
"createdAt": "СОЗДАНО",
|
||||
"expiresAt": "ИСТЕКАЕТ",
|
||||
"status": "СТАТУС",
|
||||
@@ -550,26 +562,26 @@
|
||||
"actions": "ДЕЙСТВИЯ"
|
||||
},
|
||||
"status": {
|
||||
"neverExpires": "Не истекает",
|
||||
"active": "Активно",
|
||||
"expired": "Истекло"
|
||||
"neverExpires": "Никогда Не Истекает",
|
||||
"active": "Активный",
|
||||
"expired": "Истекший"
|
||||
},
|
||||
"security": {
|
||||
"protected": "Защищено",
|
||||
"public": "Публично"
|
||||
"protected": "Защищен",
|
||||
"public": "Публичный"
|
||||
},
|
||||
"filesCount": "файлов",
|
||||
"recipientsCount": "получателей",
|
||||
"actions": {
|
||||
"menu": "Меню действий общего доступа",
|
||||
"edit": "Редактировать",
|
||||
"manageFiles": "Управление файлами",
|
||||
"manageRecipients": "Управление получателями",
|
||||
"viewDetails": "Просмотреть детали",
|
||||
"generateLink": "Создать ссылку",
|
||||
"editLink": "Редактировать ссылку",
|
||||
"copyLink": "Скопировать ссылку",
|
||||
"notifyRecipients": "Уведомить получателей",
|
||||
"manageFiles": "Управление Файлами",
|
||||
"manageRecipients": "Управление Получателями",
|
||||
"viewDetails": "Просмотр Деталей",
|
||||
"generateLink": "Сгенерировать Ссылку",
|
||||
"editLink": "Редактировать Ссылку",
|
||||
"copyLink": "Скопировать Ссылку",
|
||||
"notifyRecipients": "Уведомить Получателей",
|
||||
"delete": "Удалить"
|
||||
}
|
||||
},
|
||||
@@ -679,23 +691,25 @@
|
||||
"passwordRequired": "Требуется пароль"
|
||||
},
|
||||
"shareFile": {
|
||||
"title": "Поделиться файлом",
|
||||
"linkTitle": "Создать ссылку",
|
||||
"nameLabel": "Название обмена",
|
||||
"namePlaceholder": "Введите название обмена",
|
||||
"expirationLabel": "Дата истечения",
|
||||
"title": "Поделиться Файлом",
|
||||
"linkTitle": "Сгенерировать Ссылку",
|
||||
"nameLabel": "Название Общего Доступа",
|
||||
"namePlaceholder": "Введите название общего доступа",
|
||||
"descriptionLabel": "Описание",
|
||||
"descriptionPlaceholder": "Введите описание (опционально)",
|
||||
"expirationLabel": "Дата Истечения",
|
||||
"expirationPlaceholder": "ДД.ММ.ГГГГ ЧЧ:ММ",
|
||||
"maxViewsLabel": "Максимум просмотров",
|
||||
"maxViewsLabel": "Максимальные Просмотры",
|
||||
"maxViewsPlaceholder": "Оставьте пустым для неограниченного",
|
||||
"passwordProtection": "Защищено паролем",
|
||||
"passwordProtection": "Защищено Паролем",
|
||||
"passwordLabel": "Пароль",
|
||||
"passwordPlaceholder": "Введите пароль",
|
||||
"linkDescription": "Создайте персональную ссылку для обмена файлом",
|
||||
"aliasLabel": "Псевдоним ссылки",
|
||||
"aliasPlaceholder": "Введите персональный псевдоним",
|
||||
"linkReady": "Ваша ссылка для обмена готова:",
|
||||
"createShare": "Создать обмен",
|
||||
"generateLink": "Создать ссылку",
|
||||
"copyLink": "Копировать ссылку"
|
||||
"linkDescription": "Сгенерируйте пользовательскую ссылку для отправки файла",
|
||||
"aliasLabel": "Псевдоним Ссылки",
|
||||
"aliasPlaceholder": "Введите пользовательский псевдоним",
|
||||
"linkReady": "Ваша ссылка для общего доступа готова:",
|
||||
"createShare": "Создать Общий Доступ",
|
||||
"generateLink": "Сгенерировать Ссылку",
|
||||
"copyLink": "Скопировать Ссылку"
|
||||
}
|
||||
}
|
||||
@@ -457,43 +457,54 @@
|
||||
},
|
||||
"shareActions": {
|
||||
"deleteTitle": "Paylaşımı Sil",
|
||||
"deleteConfirmation": "Bu paylaşımı silmek istediğinize emin misiniz? Bu işlem geri alınamaz.",
|
||||
"deleteConfirmation": "Bu paylaşımı silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.",
|
||||
"editTitle": "Paylaşımı Düzenle",
|
||||
"nameLabel": "Paylaşım Adı",
|
||||
"descriptionLabel": "Açıklama",
|
||||
"descriptionPlaceholder": "Açıklama girin (isteğe bağlı)",
|
||||
"expirationLabel": "Son Kullanma Tarihi",
|
||||
"expirationPlaceholder": "MM/GG/YYYY SS:DD",
|
||||
"expirationPlaceholder": "DD/MM/YYYY HH:MM",
|
||||
"maxViewsLabel": "Maksimum Görüntüleme",
|
||||
"maxViewsPlaceholder": "Sınırsız için boş bırakın",
|
||||
"passwordProtection": "Şifre Korumalı",
|
||||
"passwordLabel": "Şifre",
|
||||
"passwordPlaceholder": "Şifreyi girin",
|
||||
"passwordPlaceholder": "Şifre girin",
|
||||
"newPasswordLabel": "Yeni Şifre (mevcut şifreyi korumak için boş bırakın)",
|
||||
"newPasswordPlaceholder": "Yeni şifreyi girin",
|
||||
"newPasswordPlaceholder": "Yeni şifre girin",
|
||||
"manageFilesTitle": "Dosyaları Yönet",
|
||||
"manageRecipientsTitle": "Alıcıları Yönet",
|
||||
"editSuccess": "Paylaşım başarıyla güncellendi",
|
||||
"editError": "Paylaşım güncellenemedi"
|
||||
"editError": "Paylaşım güncelleme başarısız"
|
||||
},
|
||||
"shareDetails": {
|
||||
"title": "Paylaşım Detayları",
|
||||
"subtitle": "Bu paylaşım hakkında ayrıntılı bilgi",
|
||||
"subtitle": "Bu paylaşım hakkında detaylı bilgi",
|
||||
"basicInfo": "Temel Bilgiler",
|
||||
"name": "Ad",
|
||||
"untitled": "Adsız",
|
||||
"description": "Açıklama",
|
||||
"noDescription": "Açıklama sağlanmadı",
|
||||
"untitled": "Başlıksız",
|
||||
"shareLink": "Paylaşım Bağlantısı",
|
||||
"editLink": "Bağlantıyı Düzenle",
|
||||
"generateLink": "Bağlantı Oluştur",
|
||||
"noLink": "Henüz bağlantı oluşturulmadı",
|
||||
"copyLink": "Bağlantıyı kopyala",
|
||||
"openLink": "Yeni sekmede aç",
|
||||
"linkCopied": "Bağlantı panoya kopyalandı",
|
||||
"views": "Görüntüleme",
|
||||
"dates": "Tarihler",
|
||||
"created": "Oluşturuldu",
|
||||
"expires": "Sona Eriyor",
|
||||
"never": "Asla",
|
||||
"security": "Güvenlik",
|
||||
"passwordProtected": "Şifre ile Korunuyor",
|
||||
"passwordProtected": "Şifre Korumalı",
|
||||
"publicAccess": "Genel Erişim",
|
||||
"maxViews": "Maksimum Görüntüleme:",
|
||||
"files": "Dosyala",
|
||||
"recipients": "Alıcıla",
|
||||
"notAvailable": "Mevcut Değil",
|
||||
"maxViews": "Maks. Görüntüleme:",
|
||||
"files": "Dosyalar",
|
||||
"recipients": "Alıcılar",
|
||||
"notAvailable": "M/D",
|
||||
"invalidDate": "Geçersiz tarih",
|
||||
"loadError": "Paylaşım detayları yüklenemedi"
|
||||
"loadError": "Paylaşım detaylarını yükleme başarısız"
|
||||
},
|
||||
"shareManager": {
|
||||
"deleteSuccess": "Paylaşım başarıyla silindi",
|
||||
@@ -537,31 +548,32 @@
|
||||
"pageTitle": "Paylaşımlar"
|
||||
},
|
||||
"sharesTable": {
|
||||
"ariaLabel": "Paylaşım Tablosu",
|
||||
"ariaLabel": "Paylaşımlar tablosu",
|
||||
"never": "Asla",
|
||||
"columns": {
|
||||
"name": "Ad",
|
||||
"createdAt": "Oluşturulma Tarihi",
|
||||
"expiresAt": "Son Kullanma Tarihi",
|
||||
"status": "Durum",
|
||||
"security": "Güvenlik",
|
||||
"files": "Dosyalar",
|
||||
"recipients": "Alıcılar",
|
||||
"actions": "İşlemler"
|
||||
"name": "AD",
|
||||
"description": "AÇIKLAMA",
|
||||
"createdAt": "OLUŞTURULMA TARİHİ",
|
||||
"expiresAt": "SON KULLANMA TARİHİ",
|
||||
"status": "DURUM",
|
||||
"security": "GÜVENLİK",
|
||||
"files": "DOSYALAR",
|
||||
"recipients": "ALICILAR",
|
||||
"actions": "İŞLEMLER"
|
||||
},
|
||||
"status": {
|
||||
"neverExpires": "Sona Ermez",
|
||||
"neverExpires": "Asla Sona Ermez",
|
||||
"active": "Aktif",
|
||||
"expired": "Sona Erdi"
|
||||
"expired": "Süresi Dolmuş"
|
||||
},
|
||||
"security": {
|
||||
"protected": "Korunuyor",
|
||||
"protected": "Korumalı",
|
||||
"public": "Genel"
|
||||
},
|
||||
"filesCount": "dosya",
|
||||
"recipientsCount": "alıcı",
|
||||
"actions": {
|
||||
"menu": "Paylaşım İşlem Menüsü",
|
||||
"menu": "Paylaşım işlemleri menüsü",
|
||||
"edit": "Düzenle",
|
||||
"manageFiles": "Dosyaları Yönet",
|
||||
"manageRecipients": "Alıcıları Yönet",
|
||||
@@ -569,7 +581,7 @@
|
||||
"generateLink": "Bağlantı Oluştur",
|
||||
"editLink": "Bağlantıyı Düzenle",
|
||||
"copyLink": "Bağlantıyı Kopyala",
|
||||
"notifyRecipients": "Alıcıları Bildir",
|
||||
"notifyRecipients": "Alıcıları Bilgilendir",
|
||||
"delete": "Sil"
|
||||
}
|
||||
},
|
||||
@@ -694,23 +706,25 @@
|
||||
"passwordRequired": "Şifre gerekli"
|
||||
},
|
||||
"shareFile": {
|
||||
"title": "Dosyayı paylaş",
|
||||
"linkTitle": "Bağlantı oluştur",
|
||||
"nameLabel": "Paylaşım adı",
|
||||
"namePlaceholder": "Paylaşım adını girin",
|
||||
"expirationLabel": "Son kullanma tarihi",
|
||||
"expirationPlaceholder": "GG.AA.YYYY SS:DD",
|
||||
"maxViewsLabel": "Maksimum görüntüleme",
|
||||
"title": "Dosya Paylaş",
|
||||
"linkTitle": "Bağlantı Oluştur",
|
||||
"nameLabel": "Paylaşım Adı",
|
||||
"namePlaceholder": "Paylaşım adı girin",
|
||||
"descriptionLabel": "Açıklama",
|
||||
"descriptionPlaceholder": "Açıklama girin (isteğe bağlı)",
|
||||
"expirationLabel": "Son Kullanma Tarihi",
|
||||
"expirationPlaceholder": "DD/MM/YYYY HH:MM",
|
||||
"maxViewsLabel": "Maksimum Görüntüleme",
|
||||
"maxViewsPlaceholder": "Sınırsız için boş bırakın",
|
||||
"passwordProtection": "Şifre korumalı",
|
||||
"passwordProtection": "Şifre Korumalı",
|
||||
"passwordLabel": "Şifre",
|
||||
"passwordPlaceholder": "Şifre girin",
|
||||
"linkDescription": "Dosyayı paylaşmak için özel bir bağlantı oluşturun",
|
||||
"aliasLabel": "Bağlantı takma adı",
|
||||
"linkDescription": "Dosyayı paylaşmak için özel bağlantı oluşturun",
|
||||
"aliasLabel": "Bağlantı Takma Adı",
|
||||
"aliasPlaceholder": "Özel takma ad girin",
|
||||
"linkReady": "Paylaşım bağlantınız hazır:",
|
||||
"createShare": "Paylaşım oluştur",
|
||||
"generateLink": "Bağlantı oluştur",
|
||||
"copyLink": "Bağlantıyı kopyala"
|
||||
"createShare": "Paylaşım Oluştur",
|
||||
"generateLink": "Bağlantı Oluştur",
|
||||
"copyLink": "Bağlantıyı Kopyala"
|
||||
}
|
||||
}
|
||||
@@ -13,17 +13,19 @@
|
||||
"back": "返回"
|
||||
},
|
||||
"createShare": {
|
||||
"title": "创建共享",
|
||||
"nameLabel": "共享名称",
|
||||
"title": "创建分享",
|
||||
"nameLabel": "分享名称",
|
||||
"descriptionLabel": "描述",
|
||||
"descriptionPlaceholder": "输入描述(可选)",
|
||||
"expirationLabel": "过期日期",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"maxViewsLabel": "最大查看次数",
|
||||
"maxViewsPlaceholder": "留空表示无限",
|
||||
"maxViewsPlaceholder": "留空为无限制",
|
||||
"passwordProtection": "密码保护",
|
||||
"passwordLabel": "密码",
|
||||
"create": "创建共享",
|
||||
"success": "共享创建成功",
|
||||
"error": "创建共享失败"
|
||||
"create": "创建分享",
|
||||
"success": "分享创建成功",
|
||||
"error": "创建分享失败"
|
||||
},
|
||||
"dashboard": {
|
||||
"loadError": "加载仪表盘数据失败",
|
||||
@@ -90,12 +92,19 @@
|
||||
"downloadStart": "下载已开始",
|
||||
"downloadError": "下载文件失败",
|
||||
"updateSuccess": "文件更新成功",
|
||||
"updateError": "文件更新失败",
|
||||
"updateError": "更新文件失败",
|
||||
"deleteSuccess": "文件删除成功",
|
||||
"deleteError": "文件删除失败"
|
||||
"deleteError": "删除文件失败",
|
||||
"bulkDownloadSuccess": "文件下载成功开始",
|
||||
"bulkDownloadError": "创建ZIP文件错误",
|
||||
"bulkDownloadFileError": "下载文件 {fileName} 错误",
|
||||
"bulkDeleteSuccess": "{count, plural, =1 {1个文件删除成功} other {#个文件删除成功}}",
|
||||
"bulkDeleteError": "删除选中文件错误"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "文件表格",
|
||||
"selectAll": "全选",
|
||||
"selectFile": "选择文件 {fileName}",
|
||||
"columns": {
|
||||
"name": "名称",
|
||||
"description": "描述",
|
||||
@@ -111,6 +120,13 @@
|
||||
"share": "分享",
|
||||
"download": "下载",
|
||||
"delete": "删除"
|
||||
},
|
||||
"bulkActions": {
|
||||
"selected": "{count, plural, =1 {已选择1个文件} other {已选择#个文件}}",
|
||||
"actions": "操作",
|
||||
"download": "下载选中项",
|
||||
"share": "分享选中项",
|
||||
"delete": "删除选中项"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
@@ -699,9 +715,9 @@
|
||||
"nameLabel": "分享名称",
|
||||
"namePlaceholder": "输入分享名称",
|
||||
"expirationLabel": "过期日期",
|
||||
"expirationPlaceholder": "年/月/日 时:分",
|
||||
"expirationPlaceholder": "MM/DD/YYYY HH:MM",
|
||||
"maxViewsLabel": "最大查看次数",
|
||||
"maxViewsPlaceholder": "留空表示无限制",
|
||||
"maxViewsPlaceholder": "留空为无限制",
|
||||
"passwordProtection": "密码保护",
|
||||
"passwordLabel": "密码",
|
||||
"passwordPlaceholder": "输入密码",
|
||||
@@ -712,5 +728,27 @@
|
||||
"createShare": "创建分享",
|
||||
"generateLink": "生成链接",
|
||||
"copyLink": "复制链接"
|
||||
},
|
||||
"bulkDownload": {
|
||||
"title": "批量下载",
|
||||
"zipNameLabel": "ZIP文件名",
|
||||
"zipNamePlaceholder": "输入文件名",
|
||||
"description": "{count, plural, =1 {将压缩1个文件} other {将压缩#个文件}}",
|
||||
"download": "下载ZIP"
|
||||
},
|
||||
"deleteConfirmation": {
|
||||
"filesToDelete": "要删除的文件"
|
||||
},
|
||||
"shareMultipleFiles": {
|
||||
"title": "分享多个文件",
|
||||
"shareNameLabel": "分享名称",
|
||||
"shareNamePlaceholder": "输入分享名称",
|
||||
"descriptionLabel": "描述",
|
||||
"descriptionPlaceholder": "输入描述(可选)",
|
||||
"filesToShare": "要分享的文件",
|
||||
"files": "文件",
|
||||
"totalSize": "总大小",
|
||||
"creating": "创建中...",
|
||||
"create": "创建分享"
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.3",
|
||||
"@radix-ui/react-avatar": "^1.1.4",
|
||||
"@radix-ui/react-checkbox": "^1.3.2",
|
||||
"@radix-ui/react-dialog": "^1.1.6",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.6",
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
@@ -41,6 +42,7 @@
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^12.6.3",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.487.0",
|
||||
"nanoid": "^5.1.5",
|
||||
"next": "15.2.4",
|
||||
|
||||
4200
apps/web/pnpm-lock.yaml
generated
4200
apps/web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,8 @@
|
||||
import { IconDownload } from "@tabler/icons-react";
|
||||
import { useState } from "react";
|
||||
import { IconDownload, IconEye } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { FilePreviewModal } from "@/components/modals/file-preview-modal";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { getFileIcon } from "@/utils/file-icons";
|
||||
@@ -9,6 +11,8 @@ import { ShareFilesTableProps } from "../types";
|
||||
|
||||
export function ShareFilesTable({ files, onDownload }: ShareFilesTableProps) {
|
||||
const t = useTranslations();
|
||||
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
||||
const [selectedFile, setSelectedFile] = useState<{ name: string; objectName: string; type?: string } | null>(null);
|
||||
|
||||
const formatDateTime = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
@@ -22,6 +26,16 @@ export function ShareFilesTable({ files, onDownload }: ShareFilesTableProps) {
|
||||
}).format(date);
|
||||
};
|
||||
|
||||
const handlePreview = (file: { name: string; objectName: string }) => {
|
||||
setSelectedFile(file);
|
||||
setIsPreviewOpen(true);
|
||||
};
|
||||
|
||||
const handleClosePreview = () => {
|
||||
setIsPreviewOpen(false);
|
||||
setSelectedFile(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="rounded-lg border shadow-sm overflow-hidden">
|
||||
@@ -37,7 +51,7 @@ export function ShareFilesTable({ files, onDownload }: ShareFilesTableProps) {
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.createdAt")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 w-[70px] text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
<TableHead className="h-10 w-[110px] text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.actions")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
@@ -57,15 +71,26 @@ export function ShareFilesTable({ files, onDownload }: ShareFilesTableProps) {
|
||||
<TableCell className="h-12 px-4">{formatFileSize(Number(file.size))}</TableCell>
|
||||
<TableCell className="h-12 px-4">{formatDateTime(file.createdAt)}</TableCell>
|
||||
<TableCell className="h-12 px-4">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-8 w-8 hover:bg-muted"
|
||||
onClick={() => onDownload(file.objectName, file.name)}
|
||||
>
|
||||
<IconDownload className="h-4 w-4" />
|
||||
<span className="sr-only">{t("filesTable.actions.download")}</span>
|
||||
</Button>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-8 w-8 hover:bg-muted"
|
||||
onClick={() => handlePreview({ name: file.name, objectName: file.objectName })}
|
||||
>
|
||||
<IconEye className="h-4 w-4" />
|
||||
<span className="sr-only">{t("filesTable.actions.preview")}</span>
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-8 w-8 hover:bg-muted"
|
||||
onClick={() => onDownload(file.objectName, file.name)}
|
||||
>
|
||||
<IconDownload className="h-4 w-4" />
|
||||
<span className="sr-only">{t("filesTable.actions.download")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
@@ -73,6 +98,8 @@ export function ShareFilesTable({ files, onDownload }: ShareFilesTableProps) {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
{selectedFile && <FilePreviewModal isOpen={isPreviewOpen} onClose={handleClosePreview} file={selectedFile} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { CreateShareModal } from "@/components/modals/create-share-modal";
|
||||
import { GenerateShareLinkModal } from "@/components/modals/generate-share-link-modal";
|
||||
import { ShareActionsModals } from "@/components/modals/share-actions-modals";
|
||||
@@ -10,13 +12,21 @@ export function SharesModals({
|
||||
shareToViewDetails,
|
||||
shareToGenerateLink,
|
||||
shareManager,
|
||||
fileManager,
|
||||
onSuccess,
|
||||
onCloseViewDetails,
|
||||
onCloseGenerateLink,
|
||||
}: SharesModalsProps) {
|
||||
const [shareDetailsRefresh, setShareDetailsRefresh] = useState(0);
|
||||
|
||||
const handleShareSuccess = () => {
|
||||
setShareDetailsRefresh((prev) => prev + 1);
|
||||
onSuccess();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<CreateShareModal isOpen={isCreateModalOpen} onClose={onCloseCreateModal} onSuccess={onSuccess} />
|
||||
<CreateShareModal isOpen={isCreateModalOpen} onClose={onCloseCreateModal} onSuccess={handleShareSuccess} />
|
||||
|
||||
<ShareActionsModals
|
||||
shareToDelete={shareManager.shareToDelete}
|
||||
@@ -31,17 +41,27 @@ export function SharesModals({
|
||||
onEdit={shareManager.handleEdit}
|
||||
onManageFiles={shareManager.handleManageFiles}
|
||||
onManageRecipients={shareManager.handleManageRecipients}
|
||||
onSuccess={onSuccess}
|
||||
onSuccess={handleShareSuccess}
|
||||
onEditFile={fileManager.handleRename}
|
||||
/>
|
||||
|
||||
<ShareDetailsModal shareId={shareToViewDetails?.id || null} onClose={onCloseViewDetails} />
|
||||
<ShareDetailsModal
|
||||
shareId={shareToViewDetails?.id || null}
|
||||
onClose={onCloseViewDetails}
|
||||
onUpdateName={shareManager.handleUpdateName}
|
||||
onUpdateDescription={shareManager.handleUpdateDescription}
|
||||
onGenerateLink={shareManager.handleGenerateLink}
|
||||
onManageFiles={shareManager.setShareToManageFiles}
|
||||
refreshTrigger={shareDetailsRefresh}
|
||||
onSuccess={handleShareSuccess}
|
||||
/>
|
||||
|
||||
<GenerateShareLinkModal
|
||||
share={shareToGenerateLink}
|
||||
shareId={shareToGenerateLink?.id || null}
|
||||
onClose={onCloseGenerateLink}
|
||||
onGenerate={shareManager.handleGenerateLink}
|
||||
onSuccess={onSuccess}
|
||||
onSuccess={handleShareSuccess}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ export function SharesTableContainer({ shares, onCopyLink, onCreateShare, shareM
|
||||
onDelete={shareManager.setShareToDelete}
|
||||
onEdit={shareManager.setShareToEdit}
|
||||
onUpdateName={shareManager.handleUpdateName}
|
||||
onUpdateDescription={shareManager.handleUpdateDescription}
|
||||
onGenerateLink={shareManager.setShareToGenerateLink}
|
||||
onManageFiles={shareManager.setShareToManageFiles}
|
||||
onManageRecipients={shareManager.setShareToManageRecipients}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Navbar } from "@/components/layout/navbar";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { DefaultFooter } from "@/components/ui/default-footer";
|
||||
import { useDisclosure } from "@/hooks/use-disclosure";
|
||||
import { useFileManager } from "@/hooks/use-file-manager";
|
||||
import { useShareManager } from "@/hooks/use-share-manager";
|
||||
import { SharesHeader } from "./components/shares-header";
|
||||
import { SharesModals } from "./components/shares-modals";
|
||||
@@ -31,6 +32,7 @@ export default function SharesPage() {
|
||||
|
||||
const { isOpen: isCreateModalOpen, onOpen: onOpenCreateModal, onClose: onCloseCreateModal } = useDisclosure();
|
||||
const shareManager = useShareManager(loadShares);
|
||||
const fileManager = useFileManager(loadShares);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingScreen />;
|
||||
@@ -67,6 +69,7 @@ export default function SharesPage() {
|
||||
<SharesModals
|
||||
isCreateModalOpen={isCreateModalOpen}
|
||||
shareManager={shareManager}
|
||||
fileManager={fileManager}
|
||||
shareToGenerateLink={shareToGenerateLink}
|
||||
shareToViewDetails={shareToViewDetails}
|
||||
smtpEnabled={smtpEnabled}
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface SharesModalsProps {
|
||||
shareToViewDetails: any;
|
||||
shareToGenerateLink: any;
|
||||
shareManager: any;
|
||||
fileManager: any;
|
||||
onSuccess: () => void;
|
||||
onCloseViewDetails: () => void;
|
||||
onCloseGenerateLink: () => void;
|
||||
|
||||
@@ -50,6 +50,10 @@ export function RecentFiles({ files, fileManager, onOpenUploadModal }: RecentFil
|
||||
onPreview={fileManager.setPreviewFile}
|
||||
onRename={fileManager.setFileToRename}
|
||||
onShare={fileManager.setFileToShare}
|
||||
onBulkDelete={fileManager.handleBulkDelete}
|
||||
onBulkShare={fileManager.handleBulkShare}
|
||||
onBulkDownload={fileManager.handleBulkDownload}
|
||||
setClearSelectionCallback={fileManager.setClearSelectionCallback}
|
||||
onUpdateName={(fileId, newName) => {
|
||||
const file = files.find((f) => f.id === fileId);
|
||||
if (file) {
|
||||
|
||||
@@ -51,6 +51,7 @@ export function RecentShares({ shares, shareManager, onOpenCreateModal, onCopyLi
|
||||
onDelete={shareManager.setShareToDelete}
|
||||
onEdit={shareManager.setShareToEdit}
|
||||
onUpdateName={shareManager.handleUpdateName}
|
||||
onUpdateDescription={shareManager.handleUpdateDescription}
|
||||
onGenerateLink={shareManager.setShareToGenerateLink}
|
||||
onManageFiles={shareManager.setShareToManageFiles}
|
||||
onManageRecipients={shareManager.setShareToManageRecipients}
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
import { useState } from "react";
|
||||
|
||||
import { BulkDownloadModal } from "@/components/modals/bulk-download-modal";
|
||||
import { CreateShareModal } from "@/components/modals/create-share-modal";
|
||||
import { DeleteConfirmationModal } from "@/components/modals/delete-confirmation-modal";
|
||||
import { FileActionsModals } from "@/components/modals/file-actions-modals";
|
||||
import { FilePreviewModal } from "@/components/modals/file-preview-modal";
|
||||
import { GenerateShareLinkModal } from "@/components/modals/generate-share-link-modal";
|
||||
import { ShareActionsModals } from "@/components/modals/share-actions-modals";
|
||||
import { ShareDetailsModal } from "@/components/modals/share-details-modal";
|
||||
import { ShareFileModal } from "@/components/modals/share-file-modal";
|
||||
import { ShareMultipleFilesModal } from "@/components/modals/share-multiple-files-modal";
|
||||
import { UploadFileModal } from "@/components/modals/upload-file-modal";
|
||||
import { DashboardModalsProps } from "../types";
|
||||
|
||||
export function DashboardModals({ modals, fileManager, shareManager, onSuccess }: DashboardModalsProps) {
|
||||
const [shareDetailsRefresh, setShareDetailsRefresh] = useState(0);
|
||||
|
||||
const handleShareSuccess = () => {
|
||||
setShareDetailsRefresh((prev) => prev + 1);
|
||||
onSuccess();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<UploadFileModal isOpen={modals.isUploadModalOpen} onClose={modals.onCloseUploadModal} onSuccess={onSuccess} />
|
||||
@@ -35,6 +47,36 @@ export function DashboardModals({ modals, fileManager, shareManager, onSuccess }
|
||||
onRename={fileManager.handleRename}
|
||||
/>
|
||||
|
||||
<BulkDownloadModal
|
||||
isOpen={fileManager.isBulkDownloadModalOpen}
|
||||
onClose={() => fileManager.setBulkDownloadModalOpen(false)}
|
||||
onDownload={(zipName) => {
|
||||
if (fileManager.filesToDownload) {
|
||||
fileManager.handleBulkDownloadWithZip(fileManager.filesToDownload, zipName);
|
||||
}
|
||||
}}
|
||||
fileCount={fileManager.filesToDownload?.length || 0}
|
||||
/>
|
||||
|
||||
<DeleteConfirmationModal
|
||||
isOpen={!!fileManager.filesToDelete}
|
||||
onClose={() => fileManager.setFilesToDelete(null)}
|
||||
onConfirm={fileManager.handleDeleteBulk}
|
||||
title="Excluir Arquivos Selecionados"
|
||||
description={`Tem certeza que deseja excluir ${fileManager.filesToDelete?.length || 0} arquivo(s)? Esta ação não pode ser desfeita.`}
|
||||
files={fileManager.filesToDelete?.map((f) => f.name) || []}
|
||||
/>
|
||||
|
||||
<ShareMultipleFilesModal
|
||||
files={fileManager.filesToShare}
|
||||
isOpen={!!fileManager.filesToShare}
|
||||
onClose={() => fileManager.setFilesToShare(null)}
|
||||
onSuccess={() => {
|
||||
fileManager.handleShareBulkSuccess();
|
||||
onSuccess();
|
||||
}}
|
||||
/>
|
||||
|
||||
<CreateShareModal isOpen={modals.isCreateModalOpen} onClose={modals.onCloseCreateModal} onSuccess={onSuccess} />
|
||||
|
||||
<ShareActionsModals
|
||||
@@ -50,12 +92,19 @@ export function DashboardModals({ modals, fileManager, shareManager, onSuccess }
|
||||
onEdit={shareManager.handleEdit}
|
||||
onManageFiles={shareManager.handleManageFiles}
|
||||
onManageRecipients={shareManager.handleManageRecipients}
|
||||
onSuccess={onSuccess}
|
||||
onEditFile={fileManager.handleRename}
|
||||
onSuccess={handleShareSuccess}
|
||||
/>
|
||||
|
||||
<ShareDetailsModal
|
||||
shareId={shareManager.shareToViewDetails?.id || null}
|
||||
onClose={() => shareManager.setShareToViewDetails(null)}
|
||||
onUpdateName={shareManager.handleUpdateName}
|
||||
onUpdateDescription={shareManager.handleUpdateDescription}
|
||||
onGenerateLink={shareManager.handleGenerateLink}
|
||||
onManageFiles={shareManager.setShareToManageFiles}
|
||||
refreshTrigger={shareDetailsRefresh}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
|
||||
<GenerateShareLinkModal
|
||||
|
||||
@@ -26,6 +26,10 @@ export function FileList({ files, filteredFiles, fileManager, searchQuery, onSea
|
||||
onPreview={fileManager.setPreviewFile}
|
||||
onRename={fileManager.setFileToRename}
|
||||
onShare={fileManager.setFileToShare}
|
||||
onBulkDelete={fileManager.handleBulkDelete}
|
||||
onBulkShare={fileManager.handleBulkShare}
|
||||
onBulkDownload={fileManager.handleBulkDownload}
|
||||
setClearSelectionCallback={fileManager.setClearSelectionCallback}
|
||||
onUpdateName={(fileId, newName) => {
|
||||
const file = filteredFiles.find((f) => f.id === fileId);
|
||||
if (file) {
|
||||
|
||||
@@ -13,6 +13,7 @@ export function useFiles() {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
|
||||
const [clearSelectionCallback, setClearSelectionCallback] = useState<(() => void) | undefined>();
|
||||
|
||||
const loadFiles = async () => {
|
||||
try {
|
||||
@@ -31,7 +32,7 @@ export function useFiles() {
|
||||
}
|
||||
};
|
||||
|
||||
const fileManager = useFileManager(loadFiles);
|
||||
const fileManager = useFileManager(loadFiles, clearSelectionCallback);
|
||||
const filteredFiles = files.filter((file) => file.name.toLowerCase().includes(searchQuery.toLowerCase()));
|
||||
|
||||
useEffect(() => {
|
||||
@@ -47,7 +48,10 @@ export function useFiles() {
|
||||
onOpenUploadModal: () => setIsUploadModalOpen(true),
|
||||
onCloseUploadModal: () => setIsUploadModalOpen(false),
|
||||
},
|
||||
fileManager,
|
||||
fileManager: {
|
||||
...fileManager,
|
||||
setClearSelectionCallback,
|
||||
} as typeof fileManager & { setClearSelectionCallback: typeof setClearSelectionCallback },
|
||||
filteredFiles,
|
||||
handleSearch: setSearchQuery,
|
||||
loadFiles,
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { BulkDownloadModal } from "@/components/modals/bulk-download-modal";
|
||||
import { DeleteConfirmationModal } from "@/components/modals/delete-confirmation-modal";
|
||||
import { FileActionsModals } from "@/components/modals/file-actions-modals";
|
||||
import { FilePreviewModal } from "@/components/modals/file-preview-modal";
|
||||
import { ShareFileModal } from "@/components/modals/share-file-modal";
|
||||
import { ShareMultipleFilesModal } from "@/components/modals/share-multiple-files-modal";
|
||||
import { UploadFileModal } from "@/components/modals/upload-file-modal";
|
||||
import type { FilesModalsProps } from "../types";
|
||||
|
||||
export function FilesModals({ fileManager, modals, onSuccess }: FilesModalsProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<>
|
||||
<UploadFileModal isOpen={modals.isUploadModalOpen} onClose={modals.onCloseUploadModal} onSuccess={onSuccess} />
|
||||
@@ -30,6 +37,37 @@ export function FilesModals({ fileManager, modals, onSuccess }: FilesModalsProps
|
||||
onDelete={fileManager.handleDelete}
|
||||
onRename={fileManager.handleRename}
|
||||
/>
|
||||
|
||||
{/* Bulk Actions Modals */}
|
||||
<BulkDownloadModal
|
||||
isOpen={fileManager.isBulkDownloadModalOpen}
|
||||
onClose={() => fileManager.setBulkDownloadModalOpen(false)}
|
||||
onDownload={(zipName) => {
|
||||
if (fileManager.filesToDownload) {
|
||||
fileManager.handleBulkDownloadWithZip(fileManager.filesToDownload, zipName);
|
||||
}
|
||||
}}
|
||||
fileCount={fileManager.filesToDownload?.length || 0}
|
||||
/>
|
||||
|
||||
<DeleteConfirmationModal
|
||||
isOpen={!!fileManager.filesToDelete}
|
||||
onClose={() => fileManager.setFilesToDelete(null)}
|
||||
onConfirm={fileManager.handleDeleteBulk}
|
||||
title="Excluir Arquivos Selecionados"
|
||||
description={`Tem certeza que deseja excluir ${fileManager.filesToDelete?.length || 0} arquivo(s)? Esta ação não pode ser desfeita.`}
|
||||
files={fileManager.filesToDelete?.map((f) => f.name) || []}
|
||||
/>
|
||||
|
||||
<ShareMultipleFilesModal
|
||||
files={fileManager.filesToShare}
|
||||
isOpen={!!fileManager.filesToShare}
|
||||
onClose={() => fileManager.setFilesToShare(null)}
|
||||
onSuccess={() => {
|
||||
fileManager.handleShareBulkSuccess();
|
||||
onSuccess();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconArrowLeft, IconArrowRight, IconFile } from "@tabler/icons-react";
|
||||
import { IconCheck, IconEdit, IconEye, IconMinus, IconPlus, IconSearch } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { FileActionsModals } from "@/components/modals/file-actions-modals";
|
||||
import { FilePreviewModal } from "@/components/modals/file-preview-modal";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { addFiles, listFiles, removeFiles } from "@/http/endpoints";
|
||||
import { getFileIcon } from "@/utils/file-icons";
|
||||
|
||||
interface FileSelectorProps {
|
||||
shareId: string;
|
||||
selectedFiles: string[];
|
||||
onSave: (files: string[]) => Promise<void>;
|
||||
onEditFile?: (fileId: string, newName: string, description?: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export function FileSelector({ shareId, selectedFiles, onSave }: FileSelectorProps) {
|
||||
export function FileSelector({ shareId, selectedFiles, onSave, onEditFile }: FileSelectorProps) {
|
||||
const t = useTranslations();
|
||||
const [availableFiles, setAvailableFiles] = useState<any[]>([]);
|
||||
const [shareFiles, setShareFiles] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [availableFilter, setAvailableFilter] = useState("");
|
||||
const [shareFilter, setShareFilter] = useState("");
|
||||
const [searchFilter, setSearchFilter] = useState("");
|
||||
const [shareSearchFilter, setShareSearchFilter] = useState("");
|
||||
const [previewFile, setPreviewFile] = useState<any>(null);
|
||||
const [fileToEdit, setFileToEdit] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadFiles();
|
||||
@@ -40,9 +47,8 @@ export function FileSelector({ shareId, selectedFiles, onSave }: FileSelectorPro
|
||||
}
|
||||
};
|
||||
|
||||
const moveToShare = (fileId: string) => {
|
||||
const addToShare = (fileId: string) => {
|
||||
const file = availableFiles.find((f) => f.id === fileId);
|
||||
|
||||
if (file) {
|
||||
setShareFiles([...shareFiles, file]);
|
||||
setAvailableFiles(availableFiles.filter((f) => f.id !== fileId));
|
||||
@@ -51,7 +57,6 @@ export function FileSelector({ shareId, selectedFiles, onSave }: FileSelectorPro
|
||||
|
||||
const removeFromShare = (fileId: string) => {
|
||||
const file = shareFiles.find((f) => f.id === fileId);
|
||||
|
||||
if (file) {
|
||||
setAvailableFiles([...availableFiles, file]);
|
||||
setShareFiles(shareFiles.filter((f) => f.id !== fileId));
|
||||
@@ -63,7 +68,6 @@ export function FileSelector({ shareId, selectedFiles, onSave }: FileSelectorPro
|
||||
setIsLoading(true);
|
||||
|
||||
const filesToAdd = shareFiles.filter((file) => !selectedFiles.includes(file.id)).map((file) => file.id);
|
||||
|
||||
const filesToRemove = selectedFiles.filter((fileId) => !shareFiles.find((f) => f.id === fileId));
|
||||
|
||||
if (filesToAdd.length > 0) {
|
||||
@@ -75,7 +79,6 @@ export function FileSelector({ shareId, selectedFiles, onSave }: FileSelectorPro
|
||||
}
|
||||
|
||||
await onSave(shareFiles.map((f) => f.id));
|
||||
toast.success("Files updated successfully");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Failed to update files");
|
||||
@@ -84,106 +87,219 @@ export function FileSelector({ shareId, selectedFiles, onSave }: FileSelectorPro
|
||||
}
|
||||
};
|
||||
|
||||
const handleEditFile = async (fileId: string, newName: string, description?: string) => {
|
||||
if (onEditFile) {
|
||||
await onEditFile(fileId, newName, description);
|
||||
setFileToEdit(null);
|
||||
// Recarregar arquivos para mostrar as mudanças
|
||||
await loadFiles();
|
||||
}
|
||||
};
|
||||
|
||||
const filteredAvailableFiles = availableFiles.filter((file) =>
|
||||
file.name.toLowerCase().includes(availableFilter.toLowerCase())
|
||||
file.name.toLowerCase().includes(searchFilter.toLowerCase())
|
||||
);
|
||||
|
||||
const filteredShareFiles = shareFiles.filter((file) => file.name.toLowerCase().includes(shareFilter.toLowerCase()));
|
||||
const filteredShareFiles = shareFiles.filter((file) =>
|
||||
file.name.toLowerCase().includes(shareSearchFilter.toLowerCase())
|
||||
);
|
||||
|
||||
const formatFileSize = (bytes: number) => {
|
||||
if (bytes === 0) return "0 B";
|
||||
const k = 1024;
|
||||
const sizes = ["B", "KB", "MB", "GB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
||||
};
|
||||
|
||||
const FileCard = ({ file, isInShare }: { file: any; isInShare: boolean }) => {
|
||||
const { icon: FileIcon, color } = getFileIcon(file.name);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 p-3 bg-background rounded-lg border group hover:border-muted-foreground/20 transition-colors">
|
||||
<FileIcon className={`h-5 w-5 ${color} flex-shrink-0`} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-sm truncate max-w-[260px]" title={file.name}>
|
||||
{file.name}
|
||||
</div>
|
||||
{file.description && (
|
||||
<div className="text-xs text-muted-foreground truncate max-w-[260px]" title={file.description}>
|
||||
{file.description}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-xs text-muted-foreground">{formatFileSize(file.size)}</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Botões de ações secundárias */}
|
||||
<div className="flex gap-1">
|
||||
{onEditFile && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 hover:bg-muted transition-colors"
|
||||
onClick={() => setFileToEdit(file)}
|
||||
title="Editar arquivo"
|
||||
>
|
||||
<IconEdit className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-7 w-7 hover:bg-muted transition-colors"
|
||||
onClick={() => setPreviewFile(file)}
|
||||
title="Visualizar arquivo"
|
||||
>
|
||||
<IconEye className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Botão de ação principal destacado */}
|
||||
<div className="ml-1">
|
||||
<Button
|
||||
size="icon"
|
||||
variant={isInShare ? "destructive" : "default"}
|
||||
className="h-8 w-8 transition-all"
|
||||
onClick={() => (isInShare ? removeFromShare(file.id) : addToShare(file.id))}
|
||||
title={isInShare ? "Remover do compartilhamento" : "Adicionar ao compartilhamento"}
|
||||
>
|
||||
{isInShare ? <IconMinus className="h-4 w-4" /> : <IconPlus className="h-4 w-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex gap-4 h-[500px]">
|
||||
<div className="flex-1 border rounded-lg">
|
||||
<div className="p-4 border-b">
|
||||
<h3 className="font-medium">
|
||||
{t("fileSelector.availableFiles", { count: filteredAvailableFiles.length })}
|
||||
</h3>
|
||||
<Input
|
||||
className="mt-2"
|
||||
placeholder={t("fileSelector.searchPlaceholder")}
|
||||
type="search"
|
||||
value={availableFilter}
|
||||
onChange={(e) => setAvailableFilter(e.target.value)}
|
||||
/>
|
||||
<>
|
||||
<div className="space-y-6">
|
||||
{/* Current Files in Share */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">{t("fileSelector.shareFiles", { count: shareFiles.length })}</h3>
|
||||
<p className="text-sm text-muted-foreground">Arquivos atualmente no compartilhamento</p>
|
||||
</div>
|
||||
<Badge variant="secondary" className="bg-blue-500/20 text-blue-700">
|
||||
{shareFiles.length} {shareFiles.length === 1 ? "arquivo" : "arquivos"}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="p-4 h-[calc(100%-115px)] overflow-y-auto">
|
||||
{filteredAvailableFiles.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
{availableFilter ? t("fileSelector.noMatchingFiles") : t("fileSelector.noAvailableFiles")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
{filteredAvailableFiles.map((file) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center justify-between p-3 rounded-lg border border-transparent hover:border-primary-500 cursor-pointer"
|
||||
onClick={() => moveToShare(file.id)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<IconFile className="text-gray-400" size={20} />
|
||||
<span className="truncate max-w-[150px]" title={file.name}>
|
||||
{file.name}
|
||||
</span>
|
||||
</div>
|
||||
<IconArrowRight className="text-gray-400" size={20} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 border rounded-lg">
|
||||
<div className="p-4 border-b">
|
||||
<h3 className="font-medium">{t("fileSelector.shareFiles", { count: filteredShareFiles.length })}</h3>
|
||||
<Input
|
||||
className="mt-2"
|
||||
placeholder={t("fileSelector.searchPlaceholder")}
|
||||
type="search"
|
||||
value={shareFilter}
|
||||
onChange={(e) => setShareFilter(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4 h-[calc(100%-115px)] overflow-y-auto">
|
||||
{filteredShareFiles.length === 0 ? (
|
||||
<div className="text-center py-8 text-gray-500">
|
||||
{shareFilter ? t("fileSelector.noMatchingFiles") : t("fileSelector.noFilesInShare")}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2">
|
||||
{filteredShareFiles.map((file) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center justify-between p-3 rounded-lg border border-transparent hover:border-primary-500 cursor-pointer"
|
||||
onClick={() => removeFromShare(file.id)}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<IconFile className="text-gray-400" size={20} />
|
||||
<span className="truncate max-w-[150px]" title={file.name}>
|
||||
{file.name}
|
||||
</span>
|
||||
</div>
|
||||
<IconArrowLeft className="text-gray-400" size={20} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Search for selected files - only show if there are files */}
|
||||
{shareFiles.length > 0 && (
|
||||
<div className="relative">
|
||||
<IconSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Buscar nos arquivos selecionados..."
|
||||
value={shareSearchFilter}
|
||||
onChange={(e) => setShareSearchFilter(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-end">
|
||||
<Button variant="default" disabled={isLoading} onClick={handleSave}>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="animate-spin">⠋</div>
|
||||
{t("fileSelector.saveChanges")}
|
||||
{shareFiles.length > 0 ? (
|
||||
<div className="grid gap-2 max-h-40 overflow-y-auto border rounded-lg p-3 bg-muted/30">
|
||||
{filteredShareFiles.map((file) => (
|
||||
<FileCard key={file.id} file={file} isInShare={true} />
|
||||
))}
|
||||
{filteredShareFiles.length === 0 && shareSearchFilter && (
|
||||
<div className="text-center py-4 text-muted-foreground">
|
||||
<p className="text-sm">Nenhum arquivo encontrado com "{shareSearchFilter}"</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
t("fileSelector.saveChanges")
|
||||
<div className="text-center py-8 text-muted-foreground border rounded-lg bg-muted/20">
|
||||
<div className="text-4xl mb-2">📁</div>
|
||||
<p className="font-medium">Nenhum arquivo no compartilhamento</p>
|
||||
<p className="text-sm">Adicione arquivos da lista abaixo</p>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Available Files to Add */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">
|
||||
{t("fileSelector.availableFiles", { count: filteredAvailableFiles.length })}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">Selecione arquivos para adicionar ao compartilhamento</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative">
|
||||
<IconSearch className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder={t("fileSelector.searchPlaceholder")}
|
||||
value={searchFilter}
|
||||
onChange={(e) => setSearchFilter(e.target.value)}
|
||||
className="pl-10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Available Files Grid */}
|
||||
{filteredAvailableFiles.length > 0 ? (
|
||||
<div className="grid gap-2 max-h-60 overflow-y-auto border rounded-lg p-3 bg-muted/10">
|
||||
{filteredAvailableFiles.map((file) => (
|
||||
<FileCard key={file.id} file={file} isInShare={false} />
|
||||
))}
|
||||
</div>
|
||||
) : searchFilter ? (
|
||||
<div className="text-center py-8 text-muted-foreground border rounded-lg bg-muted/20">
|
||||
<div className="text-4xl mb-2">🔍</div>
|
||||
<p className="font-medium">Nenhum arquivo encontrado</p>
|
||||
<p className="text-sm">Tente usar outros termos de busca</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8 text-muted-foreground border rounded-lg bg-muted/20">
|
||||
<div className="text-4xl mb-2">📄</div>
|
||||
<p className="font-medium">Todos os arquivos já estão no compartilhamento</p>
|
||||
<p className="text-sm">Faça upload de novos arquivos para adicioná-los</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex items-center justify-between pt-4 border-t">
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{shareFiles.length} {shareFiles.length === 1 ? "arquivo" : "arquivos"} selecionado(s)
|
||||
</div>
|
||||
<Button onClick={handleSave} disabled={isLoading} className="gap-2">
|
||||
{isLoading ? (
|
||||
<>
|
||||
<div className="animate-spin h-4 w-4 border-2 border-current border-t-transparent rounded-full" />
|
||||
Salvando...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IconCheck className="h-4 w-4" />
|
||||
{t("fileSelector.saveChanges")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* File Preview Dialog */}
|
||||
<FilePreviewModal
|
||||
isOpen={!!previewFile}
|
||||
onClose={() => setPreviewFile(null)}
|
||||
file={previewFile || { name: "", objectName: "" }}
|
||||
/>
|
||||
|
||||
{/* File Edit Dialog */}
|
||||
<FileActionsModals
|
||||
fileToRename={fileToEdit}
|
||||
fileToDelete={null}
|
||||
onRename={handleEditFile}
|
||||
onDelete={async () => {}}
|
||||
onCloseRename={() => setFileToEdit(null)}
|
||||
onCloseDelete={() => {}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
74
apps/web/src/components/modals/bulk-download-modal.tsx
Normal file
74
apps/web/src/components/modals/bulk-download-modal.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { IconDownload, IconX } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
interface BulkDownloadModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onDownload: (zipName: string) => void;
|
||||
fileCount: number;
|
||||
}
|
||||
|
||||
export function BulkDownloadModal({ isOpen, onClose, onDownload, fileCount }: BulkDownloadModalProps) {
|
||||
const t = useTranslations();
|
||||
const [zipName, setZipName] = useState("");
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (zipName.trim()) {
|
||||
onDownload(zipName.trim());
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
setZipName("");
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<IconDownload size={20} />
|
||||
{t("bulkDownload.title")}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="zipName">{t("bulkDownload.zipNameLabel")}</Label>
|
||||
<Input
|
||||
id="zipName"
|
||||
value={zipName}
|
||||
onChange={(e) => setZipName(e.target.value)}
|
||||
placeholder={t("bulkDownload.zipNamePlaceholder")}
|
||||
className="w-full"
|
||||
autoFocus
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">{t("bulkDownload.description", { count: fileCount })}</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={handleClose}>
|
||||
<IconX className="h-4 w-4 mr-2" />
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleSubmit} disabled={!zipName.trim()}>
|
||||
<IconDownload className="h-4 w-4 mr-2" />
|
||||
{t("bulkDownload.download")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -22,6 +22,7 @@ export function CreateShareModal({ isOpen, onClose, onSuccess }: CreateShareModa
|
||||
const t = useTranslations();
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
description: "",
|
||||
password: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
@@ -34,6 +35,7 @@ export function CreateShareModal({ isOpen, onClose, onSuccess }: CreateShareModa
|
||||
setIsLoading(true);
|
||||
await createShare({
|
||||
name: formData.name,
|
||||
description: formData.description || undefined,
|
||||
password: formData.isPasswordProtected ? formData.password : undefined,
|
||||
expiration: formData.expiresAt ? new Date(formData.expiresAt).toISOString() : undefined,
|
||||
maxViews: formData.maxViews ? parseInt(formData.maxViews) : undefined,
|
||||
@@ -44,6 +46,7 @@ export function CreateShareModal({ isOpen, onClose, onSuccess }: CreateShareModa
|
||||
onClose();
|
||||
setFormData({
|
||||
name: "",
|
||||
description: "",
|
||||
password: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
@@ -72,6 +75,15 @@ export function CreateShareModal({ isOpen, onClose, onSuccess }: CreateShareModa
|
||||
<Input value={formData.name} onChange={(e) => setFormData({ ...formData, name: e.target.value })} />
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>{t("createShare.descriptionLabel")}</Label>
|
||||
<Input
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
placeholder={t("createShare.descriptionPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="flex items-center gap-2">
|
||||
<IconCalendar size={16} />
|
||||
|
||||
76
apps/web/src/components/modals/delete-confirmation-modal.tsx
Normal file
76
apps/web/src/components/modals/delete-confirmation-modal.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
"use client";
|
||||
|
||||
import { IconTrash, IconX } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
|
||||
interface DeleteConfirmationModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
title: string;
|
||||
description: string;
|
||||
files: string[];
|
||||
}
|
||||
|
||||
export function DeleteConfirmationModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
onConfirm,
|
||||
title,
|
||||
description,
|
||||
files,
|
||||
}: DeleteConfirmationModalProps) {
|
||||
const t = useTranslations();
|
||||
|
||||
const handleConfirm = () => {
|
||||
onConfirm();
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-destructive">
|
||||
<IconTrash size={20} />
|
||||
{title}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">{description}</p>
|
||||
|
||||
{files.length > 0 && (
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm font-medium">{t("deleteConfirmation.filesToDelete")}:</p>
|
||||
<ScrollArea className="h-32 w-full rounded-md border p-2">
|
||||
<div className="space-y-1">
|
||||
{files.map((fileName, index) => (
|
||||
<div key={index} className="text-sm text-muted-foreground truncate">
|
||||
• {fileName}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter className="flex gap-2">
|
||||
<Button variant="outline" onClick={onClose}>
|
||||
<IconX className="h-4 w-4 mr-2" />
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={handleConfirm}>
|
||||
<IconTrash className="h-4 w-4 mr-2" />
|
||||
{t("common.delete")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -33,6 +33,7 @@ export interface ShareActionsModalsProps {
|
||||
onEdit: (shareId: string, data: any) => Promise<void>;
|
||||
onManageFiles: (shareId: string, files: string[]) => Promise<void>;
|
||||
onManageRecipients: (shareId: string, recipients: string[]) => Promise<void>;
|
||||
onEditFile?: (fileId: string, newName: string, description?: string) => Promise<void>;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
@@ -48,12 +49,14 @@ export function ShareActionsModals({
|
||||
onDelete,
|
||||
onEdit,
|
||||
onManageFiles,
|
||||
onEditFile,
|
||||
onSuccess,
|
||||
}: ShareActionsModalsProps) {
|
||||
const t = useTranslations();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [editForm, setEditForm] = useState({
|
||||
name: "",
|
||||
description: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
password: "",
|
||||
@@ -64,6 +67,7 @@ export function ShareActionsModals({
|
||||
if (shareToEdit) {
|
||||
setEditForm({
|
||||
name: shareToEdit.name || "",
|
||||
description: shareToEdit.description || "",
|
||||
expiresAt: shareToEdit.expiration ? new Date(shareToEdit.expiration).toISOString().slice(0, 16) : "",
|
||||
isPasswordProtected: Boolean(shareToEdit.security?.hasPassword),
|
||||
password: "",
|
||||
@@ -86,6 +90,7 @@ export function ShareActionsModals({
|
||||
try {
|
||||
const updateData = {
|
||||
name: editForm.name,
|
||||
description: editForm.description,
|
||||
expiration: editForm.expiresAt ? new Date(editForm.expiresAt).toISOString() : undefined,
|
||||
maxViews: editForm.maxViews ? parseInt(editForm.maxViews) : null,
|
||||
};
|
||||
@@ -138,6 +143,14 @@ export function ShareActionsModals({
|
||||
<Label>{t("shareActions.nameLabel")}</Label>
|
||||
<Input value={editForm.name} onChange={(e) => setEditForm({ ...editForm, name: e.target.value })} />
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>{t("shareActions.descriptionLabel")}</Label>
|
||||
<Input
|
||||
value={editForm.description}
|
||||
onChange={(e) => setEditForm({ ...editForm, description: e.target.value })}
|
||||
placeholder={t("shareActions.descriptionPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
<div className="grid w-full items-center gap-1.5">
|
||||
<Label>{t("shareActions.expirationLabel")}</Label>
|
||||
<Input
|
||||
@@ -202,18 +215,21 @@ export function ShareActionsModals({
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={!!shareToManageFiles} onOpenChange={() => onCloseManageFiles()}>
|
||||
<DialogContent className="sm:max-w-[425px] md:max-w-[700px]">
|
||||
<DialogContent className="sm:max-w-[450px] md:max-w-[550px] lg:max-w-[650px] max-h-[80vh] overflow-hidden">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("shareActions.manageFilesTitle")}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<FileSelector
|
||||
selectedFiles={shareToManageFiles?.files?.map((file: { id: string }) => file.id) || []}
|
||||
shareId={shareToManageFiles?.id}
|
||||
onSave={async (files) => {
|
||||
await onManageFiles(shareToManageFiles?.id, files);
|
||||
onSuccess();
|
||||
}}
|
||||
/>
|
||||
<div className="overflow-y-auto max-h-[calc(80vh-120px)]">
|
||||
<FileSelector
|
||||
selectedFiles={shareToManageFiles?.files?.map((file: { id: string }) => file.id) || []}
|
||||
shareId={shareToManageFiles?.id}
|
||||
onSave={async (files) => {
|
||||
await onManageFiles(shareToManageFiles?.id, files);
|
||||
onSuccess();
|
||||
}}
|
||||
onEditFile={onEditFile}
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconLock, IconLockOpen, IconMail } from "@tabler/icons-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
IconCheck,
|
||||
IconCopy,
|
||||
IconEdit,
|
||||
IconExternalLink,
|
||||
IconLock,
|
||||
IconLockOpen,
|
||||
IconMail,
|
||||
IconX,
|
||||
} from "@tabler/icons-react";
|
||||
import { format } from "date-fns";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
@@ -16,13 +25,21 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Loader } from "@/components/ui/loader";
|
||||
import { getShare } from "@/http/endpoints";
|
||||
import { getFileIcon } from "@/utils/file-icons";
|
||||
import { GenerateShareLinkModal } from "./generate-share-link-modal";
|
||||
|
||||
interface ShareDetailsModalProps {
|
||||
shareId: string | null;
|
||||
onClose: () => void;
|
||||
onUpdateName?: (shareId: string, newName: string) => Promise<void>;
|
||||
onUpdateDescription?: (shareId: string, newDescription: string) => Promise<void>;
|
||||
onGenerateLink?: (shareId: string, alias: string) => Promise<void>;
|
||||
onManageFiles?: (share: any) => void;
|
||||
refreshTrigger?: number;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
interface ShareFile {
|
||||
@@ -44,10 +61,24 @@ interface ShareRecipient {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export function ShareDetailsModal({ shareId, onClose }: ShareDetailsModalProps) {
|
||||
export function ShareDetailsModal({
|
||||
shareId,
|
||||
onClose,
|
||||
onUpdateName,
|
||||
onUpdateDescription,
|
||||
onGenerateLink,
|
||||
onManageFiles,
|
||||
refreshTrigger,
|
||||
onSuccess,
|
||||
}: ShareDetailsModalProps) {
|
||||
const t = useTranslations();
|
||||
const [share, setShare] = useState<any>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [editingField, setEditingField] = useState<{ field: "name" | "description" } | null>(null);
|
||||
const [editValue, setEditValue] = useState("");
|
||||
const [pendingChanges, setPendingChanges] = useState<{ name?: string; description?: string }>({});
|
||||
const [showLinkModal, setShowLinkModal] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (shareId) {
|
||||
@@ -55,12 +86,30 @@ export function ShareDetailsModal({ shareId, onClose }: ShareDetailsModalProps)
|
||||
}
|
||||
}, [shareId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editingField && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
inputRef.current.select();
|
||||
}
|
||||
}, [editingField]);
|
||||
|
||||
// Clear pending changes when share is updated
|
||||
useEffect(() => {
|
||||
setPendingChanges({});
|
||||
}, [share]);
|
||||
|
||||
// Refresh data when external update happens
|
||||
useEffect(() => {
|
||||
if (refreshTrigger) {
|
||||
loadShareDetails();
|
||||
}
|
||||
}, [refreshTrigger]);
|
||||
|
||||
const loadShareDetails = async () => {
|
||||
if (!shareId) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await getShare(shareId);
|
||||
|
||||
setShare(response.data.share);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
@@ -77,122 +126,421 @@ export function ShareDetailsModal({ shareId, onClose }: ShareDetailsModalProps)
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error("Invalid date:", dateString);
|
||||
|
||||
return t("shareDetails.invalidDate");
|
||||
}
|
||||
};
|
||||
|
||||
const startEdit = (field: "name" | "description", currentValue: string) => {
|
||||
setEditingField({ field });
|
||||
setEditValue(currentValue || "");
|
||||
};
|
||||
|
||||
const saveEdit = async () => {
|
||||
if (!editingField || !shareId) return;
|
||||
|
||||
const { field } = editingField;
|
||||
|
||||
// Update local state optimistically
|
||||
setPendingChanges((prev) => ({
|
||||
...prev,
|
||||
[field]: editValue,
|
||||
}));
|
||||
|
||||
try {
|
||||
if (field === "name" && onUpdateName) {
|
||||
await onUpdateName(shareId, editValue);
|
||||
} else if (field === "description" && onUpdateDescription) {
|
||||
await onUpdateDescription(shareId, editValue);
|
||||
}
|
||||
|
||||
// Reload share details to get updated data
|
||||
await loadShareDetails();
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to update:", error);
|
||||
// Revert optimistic update on error
|
||||
setPendingChanges((prev) => {
|
||||
const newState = { ...prev };
|
||||
delete newState[field];
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
|
||||
setEditingField(null);
|
||||
setEditValue("");
|
||||
};
|
||||
|
||||
const cancelEdit = () => {
|
||||
setEditingField(null);
|
||||
setEditValue("");
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
saveEdit();
|
||||
} else if (e.key === "Escape") {
|
||||
cancelEdit();
|
||||
}
|
||||
};
|
||||
|
||||
const getDisplayValue = (field: "name" | "description") => {
|
||||
const pendingChange = pendingChanges[field];
|
||||
if (pendingChange !== undefined) {
|
||||
return pendingChange;
|
||||
}
|
||||
return field === "name" ? share?.name : share?.description;
|
||||
};
|
||||
|
||||
const handleCopyLink = () => {
|
||||
if (share?.alias?.alias) {
|
||||
const link = `${window.location.origin}/s/${share.alias.alias}`;
|
||||
navigator.clipboard.writeText(link);
|
||||
toast.success(t("shareDetails.linkCopied"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpenLink = () => {
|
||||
if (share?.alias?.alias) {
|
||||
const link = `${window.location.origin}/s/${share.alias.alias}`;
|
||||
window.open(link, "_blank");
|
||||
}
|
||||
};
|
||||
|
||||
const handleLinkGenerated = async () => {
|
||||
setShowLinkModal(false);
|
||||
await loadShareDetails();
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
};
|
||||
|
||||
if (!share) return null;
|
||||
|
||||
const shareLink = share?.alias?.alias ? `${window.location.origin}/s/${share.alias.alias}` : null;
|
||||
const isEditingName = editingField?.field === "name";
|
||||
const isEditingDescription = editingField?.field === "description";
|
||||
const displayName = getDisplayValue("name");
|
||||
const displayDescription = getDisplayValue("description");
|
||||
|
||||
return (
|
||||
<Dialog open={!!shareId} onOpenChange={() => onClose()}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("shareDetails.title")}</DialogTitle>
|
||||
<DialogDescription>{t("shareDetails.subtitle")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<Loader size="lg" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">{t("shareDetails.basicInfo")}</h3>
|
||||
<div className="mt-3 space-y-3">
|
||||
<div>
|
||||
<span className="text-sm text-default-500">{t("shareDetails.name")}</span>
|
||||
<p className="mt-1 font-medium">{share.name || t("shareDetails.untitled")}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-default-500">{t("shareDetails.views")}</span>
|
||||
<p className="mt-1 font-medium">{share.views}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">{t("shareDetails.dates")}</h3>
|
||||
<div className="mt-3 space-y-3">
|
||||
<div>
|
||||
<span className="text-sm text-default-500">{t("shareDetails.created")}</span>
|
||||
<p className="mt-1 font-medium">{formatDate(share.createdAt)}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm text-default-500">{t("shareDetails.expires")}</span>
|
||||
<p className="mt-1 font-medium">
|
||||
{share.expiration ? formatDate(share.expiration) : t("shareDetails.never")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<Dialog open={!!shareId} onOpenChange={() => onClose()}>
|
||||
<DialogContent className="sm:max-w-[600px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("shareDetails.title")}</DialogTitle>
|
||||
<DialogDescription>{t("shareDetails.subtitle")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="py-4">
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<Loader size="lg" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{/* Key metrics */}
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="text-center p-2 bg-muted/30 rounded-lg">
|
||||
<p className="text-lg font-semibold text-green-600">{share.viewCount || 0}</p>
|
||||
<p className="text-xs text-muted-foreground">{t("shareDetails.views")}</p>
|
||||
</div>
|
||||
<div className="text-center p-2 bg-muted/30 rounded-lg">
|
||||
<p className="text-lg font-semibold text-green-600">{share.files?.length || 0}</p>
|
||||
<p className="text-xs text-muted-foreground">{t("shareDetails.files")}</p>
|
||||
</div>
|
||||
<div className="text-center p-2 bg-muted/30 rounded-lg">
|
||||
<p className="text-lg font-semibold text-green-600">{share.recipients?.length || 0}</p>
|
||||
<p className="text-xs text-muted-foreground">{t("shareDetails.recipients")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">{t("shareDetails.security")}</h3>
|
||||
<div className="mt-3 flex gap-2">
|
||||
{share.security?.hasPassword ? (
|
||||
<Badge variant="secondary">
|
||||
<IconLock className="h-4 w-4" />
|
||||
{t("shareDetails.passwordProtected")}
|
||||
</Badge>
|
||||
{/* Basic Information */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
<h3 className="text-base font-medium text-foreground">{t("shareDetails.basicInfo")}</h3>
|
||||
</div>
|
||||
|
||||
{/* Name */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<label className="text-sm font-medium text-muted-foreground">{t("shareDetails.name")}</label>
|
||||
{onUpdateName && !isEditingName && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-5 w-5 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => startEdit("name", displayName || "")}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isEditingName ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 flex-1 text-sm"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-green-600 hover:text-green-700"
|
||||
onClick={saveEdit}
|
||||
>
|
||||
<IconCheck className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-red-600 hover:text-red-700"
|
||||
onClick={cancelEdit}
|
||||
>
|
||||
<IconX className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm font-medium block">{displayName || t("shareDetails.untitled")}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<div>
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<label className="text-sm font-medium text-muted-foreground">
|
||||
{t("shareDetails.description")}
|
||||
</label>
|
||||
{onUpdateDescription && !isEditingDescription && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-5 w-5 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => startEdit("description", displayDescription || "")}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{isEditingDescription ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 flex-1 text-sm"
|
||||
placeholder={t("shareDetails.noDescription")}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-green-600 hover:text-green-700"
|
||||
onClick={saveEdit}
|
||||
>
|
||||
<IconCheck className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-red-600 hover:text-red-700"
|
||||
onClick={cancelEdit}
|
||||
>
|
||||
<IconX className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-sm block">{displayDescription || t("shareDetails.noDescription")}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Share Link Section */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
<h3 className="text-base font-medium text-foreground">{t("shareDetails.shareLink")}</h3>
|
||||
</div>
|
||||
{shareLink ? (
|
||||
<div className="flex gap-2">
|
||||
<Input value={shareLink} readOnly className="flex-1 bg-muted/30 text-sm h-8" />
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
onClick={handleCopyLink}
|
||||
title={t("shareDetails.copyLink")}
|
||||
>
|
||||
<IconCopy className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
onClick={handleOpenLink}
|
||||
title={t("shareDetails.openLink")}
|
||||
>
|
||||
<IconExternalLink className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="h-8 w-8"
|
||||
onClick={() => setShowLinkModal(true)}
|
||||
title={t("shareDetails.editLink")}
|
||||
>
|
||||
<IconEdit className="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Badge variant="secondary">
|
||||
<IconLockOpen className="h-4 w-4" />
|
||||
{t("shareDetails.publicAccess")}
|
||||
</Badge>
|
||||
)}
|
||||
{share.security?.maxViews && (
|
||||
<Badge variant="secondary">
|
||||
{t("shareDetails.maxViews")} {share.security.maxViews}
|
||||
</Badge>
|
||||
<div className="flex items-center justify-between p-2 bg-muted/20 rounded-lg">
|
||||
<p className="text-sm text-muted-foreground">{t("shareDetails.noLink")}</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowLinkModal(true)}
|
||||
className="gap-1 h-7 text-xs"
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
{t("shareDetails.generateLink")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">
|
||||
{t("shareDetails.files")} ({share.files?.length || 0})
|
||||
</h3>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{share.files?.map((file: ShareFile) => {
|
||||
const { icon: FileIcon, color } = getFileIcon(file.name);
|
||||
return (
|
||||
<Badge key={file.id} variant="secondary">
|
||||
<FileIcon className={`h-4 w-4 ${color}`} />
|
||||
{file.name.length > 20 ? file.name.substring(0, 20) + "..." : file.name}
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{/* Dates and Security in Grid */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{/* Dates */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-base font-medium text-foreground border-b pb-2">{t("shareDetails.dates")}</h3>
|
||||
<div className="space-y-2">
|
||||
<div>
|
||||
<div className="text-xs font-medium text-muted-foreground">{t("shareDetails.created")}</div>
|
||||
<div className="text-sm">{formatDate(share.createdAt)}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs font-medium text-muted-foreground">{t("shareDetails.expires")}</div>
|
||||
<div className="text-sm">
|
||||
{share.expiration ? formatDate(share.expiration) : t("shareDetails.never")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border p-4">
|
||||
<h3 className="font-medium">
|
||||
{t("shareDetails.recipients")} ({share.recipients?.length || 0})
|
||||
</h3>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{share.recipients?.map((recipient: ShareRecipient) => (
|
||||
<Badge key={recipient.id} variant="secondary">
|
||||
<IconMail className="h-4 w-4" />
|
||||
{recipient.email}
|
||||
</Badge>
|
||||
))}
|
||||
{/* Security */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-base font-medium text-foreground border-b pb-2">
|
||||
{t("shareDetails.security")}
|
||||
</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{share.security?.hasPassword ? (
|
||||
<Badge variant="secondary" className="bg-yellow-500/20 text-yellow-700 border-yellow-200 w-fit">
|
||||
<IconLock className="h-3 w-3 mr-1" />
|
||||
{t("shareDetails.passwordProtected")}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="secondary" className="bg-green-500/20 text-green-700 border-green-200 w-fit">
|
||||
<IconLockOpen className="h-3 w-3 mr-1" />
|
||||
{t("shareDetails.publicAccess")}
|
||||
</Badge>
|
||||
)}
|
||||
{share.security?.maxViews && (
|
||||
<Badge variant="secondary" className="bg-blue-500/20 text-blue-700 border-blue-200 w-fit">
|
||||
{t("shareDetails.maxViews")} {share.security.maxViews}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Files */}
|
||||
{share.files && share.files.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2 border-b pb-2">
|
||||
<h3 className="text-base font-medium text-foreground">{t("shareDetails.files")}</h3>
|
||||
{onManageFiles && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-5 w-5 text-muted-foreground hover:text-foreground"
|
||||
onClick={() => onManageFiles(share)}
|
||||
title={t("sharesTable.actions.manageFiles")}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="border rounded-lg bg-muted/10 p-2">
|
||||
<div className="grid gap-1 max-h-32 overflow-y-auto">
|
||||
{share.files.map((file: ShareFile) => {
|
||||
const { icon: FileIcon, color } = getFileIcon(file.name);
|
||||
return (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center gap-2 p-2 bg-background rounded border mr-2"
|
||||
>
|
||||
<FileIcon className={`h-3.5 w-3.5 ${color} flex-shrink-0`} />
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-xs font-medium truncate max-w-[280px]" title={file.name}>
|
||||
{file.name}
|
||||
</div>
|
||||
{file.description && (
|
||||
<div
|
||||
className="text-xs text-muted-foreground truncate max-w-[280px]"
|
||||
title={file.description}
|
||||
>
|
||||
{file.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Recipients */}
|
||||
{share.recipients && share.recipients.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-base font-medium text-foreground border-b pb-2">
|
||||
{t("shareDetails.recipients")}
|
||||
</h3>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{share.recipients.map((recipient: ShareRecipient) => (
|
||||
<Badge
|
||||
key={recipient.id}
|
||||
variant="secondary"
|
||||
className="bg-blue-500/20 text-blue-700 border-blue-200 text-xs"
|
||||
>
|
||||
<IconMail className="h-3 w-3 mr-1" />
|
||||
{recipient.email}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={onClose}>{t("common.close")}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button onClick={onClose}>{t("common.close")}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
{showLinkModal && onGenerateLink && (
|
||||
<GenerateShareLinkModal
|
||||
shareId={shareId}
|
||||
share={share}
|
||||
onClose={() => setShowLinkModal(false)}
|
||||
onGenerate={onGenerateLink}
|
||||
onSuccess={handleLinkGenerated}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ export function ShareFileModal({ isOpen, file, onClose, onSuccess }: ShareFileMo
|
||||
const [shareId, setShareId] = useState<string | null>(null);
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
description: "",
|
||||
password: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
@@ -51,6 +52,7 @@ export function ShareFileModal({ isOpen, file, onClose, onSuccess }: ShareFileMo
|
||||
if (isOpen && file) {
|
||||
setFormData({
|
||||
name: `${file.name.split(".")[0]}`,
|
||||
description: "",
|
||||
password: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
@@ -72,6 +74,7 @@ export function ShareFileModal({ isOpen, file, onClose, onSuccess }: ShareFileMo
|
||||
// Criar o compartilhamento
|
||||
const shareResponse = await createShare({
|
||||
name: formData.name,
|
||||
description: formData.description || undefined,
|
||||
password: formData.isPasswordProtected ? formData.password : undefined,
|
||||
expiration: formData.expiresAt ? new Date(formData.expiresAt).toISOString() : undefined,
|
||||
maxViews: formData.maxViews ? parseInt(formData.maxViews) : undefined,
|
||||
@@ -95,6 +98,7 @@ export function ShareFileModal({ isOpen, file, onClose, onSuccess }: ShareFileMo
|
||||
|
||||
setFormData({
|
||||
name: "",
|
||||
description: "",
|
||||
password: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
@@ -132,6 +136,7 @@ export function ShareFileModal({ isOpen, file, onClose, onSuccess }: ShareFileMo
|
||||
setGeneratedLink("");
|
||||
setFormData({
|
||||
name: "",
|
||||
description: "",
|
||||
password: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
@@ -175,6 +180,15 @@ export function ShareFileModal({ isOpen, file, onClose, onSuccess }: ShareFileMo
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>{t("shareFile.descriptionLabel")}</Label>
|
||||
<Input
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
placeholder={t("shareFile.descriptionPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="flex items-center gap-2">
|
||||
<IconCalendar size={16} />
|
||||
|
||||
341
apps/web/src/components/modals/share-multiple-files-modal.tsx
Normal file
341
apps/web/src/components/modals/share-multiple-files-modal.tsx
Normal file
@@ -0,0 +1,341 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { IconCalendar, IconCopy, IconEye, IconLink, IconLock, IconShare } from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { addFiles, createShare, createShareAlias } from "@/http/endpoints";
|
||||
import { customNanoid } from "@/lib/utils";
|
||||
|
||||
interface BulkFile {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
size: number;
|
||||
objectName: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface ShareMultipleFilesModalProps {
|
||||
files: BulkFile[] | null;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
const generateCustomId = () => customNanoid(10, "0123456789abcdefghijklmnopqrstuvwxyz");
|
||||
|
||||
export function ShareMultipleFilesModal({ files, isOpen, onClose, onSuccess }: ShareMultipleFilesModalProps) {
|
||||
const t = useTranslations();
|
||||
const [step, setStep] = useState<"create" | "link">("create");
|
||||
const [shareId, setShareId] = useState<string | null>(null);
|
||||
const [formData, setFormData] = useState({
|
||||
name: "",
|
||||
description: "",
|
||||
password: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
maxViews: "",
|
||||
});
|
||||
const [alias, setAlias] = useState(() => generateCustomId());
|
||||
const [generatedLink, setGeneratedLink] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && files && files.length > 0) {
|
||||
setFormData({
|
||||
name: files.length === 1 ? `${files[0].name.split(".")[0]}` : `${files.length} arquivos compartilhados`,
|
||||
description: "",
|
||||
password: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
maxViews: "",
|
||||
});
|
||||
setAlias(generateCustomId());
|
||||
setStep("create");
|
||||
setShareId(null);
|
||||
setGeneratedLink("");
|
||||
}
|
||||
}, [isOpen, files]);
|
||||
|
||||
const handleCreateShare = async () => {
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
// Criar o compartilhamento
|
||||
const shareResponse = await createShare({
|
||||
name: formData.name,
|
||||
description: formData.description || undefined,
|
||||
password: formData.isPasswordProtected ? formData.password : undefined,
|
||||
expiration: formData.expiresAt ? new Date(formData.expiresAt).toISOString() : undefined,
|
||||
maxViews: formData.maxViews ? parseInt(formData.maxViews) : undefined,
|
||||
files: [],
|
||||
});
|
||||
|
||||
const newShareId = shareResponse.data.share.id;
|
||||
setShareId(newShareId);
|
||||
|
||||
// Adicionar todos os arquivos ao compartilhamento
|
||||
await addFiles(newShareId, { files: files.map((f) => f.id) });
|
||||
|
||||
toast.success(t("createShare.success"));
|
||||
setStep("link");
|
||||
} catch (error) {
|
||||
toast.error(t("createShare.error"));
|
||||
console.error(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleGenerateLink = async () => {
|
||||
if (!shareId) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await createShareAlias(shareId, { alias });
|
||||
const link = `${window.location.origin}/s/${alias}`;
|
||||
setGeneratedLink(link);
|
||||
toast.success(t("generateShareLink.success"));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error(t("generateShareLink.error"));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyLink = () => {
|
||||
navigator.clipboard.writeText(generatedLink);
|
||||
toast.success(t("generateShareLink.copied"));
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
setTimeout(() => {
|
||||
setStep("create");
|
||||
setShareId(null);
|
||||
setGeneratedLink("");
|
||||
setFormData({
|
||||
name: "",
|
||||
description: "",
|
||||
password: "",
|
||||
expiresAt: "",
|
||||
isPasswordProtected: false,
|
||||
maxViews: "",
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handleSuccess = () => {
|
||||
onSuccess();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
if (!files) return null;
|
||||
|
||||
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
||||
const formatFileSize = (bytes: number) => {
|
||||
const sizes = ["B", "KB", "MB", "GB"];
|
||||
if (bytes === 0) return "0 B";
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return `${Math.round((bytes / Math.pow(1024, i)) * 100) / 100} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={handleClose}>
|
||||
<DialogContent className="sm:max-w-md max-h-[90vh] overflow-hidden flex flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
{step === "create" ? (
|
||||
<>
|
||||
<IconShare size={20} />
|
||||
{t("shareMultipleFiles.title")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<IconLink size={20} />
|
||||
{t("shareFile.linkTitle")}
|
||||
</>
|
||||
)}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 overflow-auto">
|
||||
{step === "create" && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>{t("shareMultipleFiles.shareNameLabel")} *</Label>
|
||||
<Input
|
||||
value={formData.name}
|
||||
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
|
||||
placeholder={t("shareMultipleFiles.shareNamePlaceholder")}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>{t("shareMultipleFiles.descriptionLabel")}</Label>
|
||||
<Input
|
||||
value={formData.description}
|
||||
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
|
||||
placeholder={t("shareMultipleFiles.descriptionPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="flex items-center gap-2">
|
||||
<IconCalendar size={16} />
|
||||
{t("shareFile.expirationLabel")}
|
||||
</Label>
|
||||
<Input
|
||||
placeholder={t("shareFile.expirationPlaceholder")}
|
||||
type="datetime-local"
|
||||
value={formData.expiresAt}
|
||||
onChange={(e) => setFormData({ ...formData, expiresAt: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="flex items-center gap-2">
|
||||
<IconEye size={16} />
|
||||
{t("shareFile.maxViewsLabel")}
|
||||
</Label>
|
||||
<Input
|
||||
min="1"
|
||||
placeholder={t("shareFile.maxViewsPlaceholder")}
|
||||
type="number"
|
||||
value={formData.maxViews}
|
||||
onChange={(e) => setFormData({ ...formData, maxViews: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch
|
||||
checked={formData.isPasswordProtected}
|
||||
onCheckedChange={(checked) =>
|
||||
setFormData({
|
||||
...formData,
|
||||
isPasswordProtected: checked,
|
||||
password: "",
|
||||
})
|
||||
}
|
||||
id="password-protection"
|
||||
/>
|
||||
<Label htmlFor="password-protection" className="flex items-center gap-2">
|
||||
<IconLock size={16} />
|
||||
{t("shareFile.passwordProtection")}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
{formData.isPasswordProtected && (
|
||||
<div className="space-y-2">
|
||||
<Label>{t("shareFile.passwordLabel")}</Label>
|
||||
<Input
|
||||
type="password"
|
||||
value={formData.password}
|
||||
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
|
||||
placeholder={t("shareFile.passwordPlaceholder")}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>
|
||||
{t("shareMultipleFiles.filesToShare")} ({files.length} {t("shareMultipleFiles.files")})
|
||||
</Label>
|
||||
<ScrollArea className="h-32 w-full rounded-md border p-2 bg-muted/30">
|
||||
<div className="space-y-1">
|
||||
{files.map((file) => (
|
||||
<div key={file.id} className="flex justify-between items-center text-sm">
|
||||
<span className="truncate flex-1">{file.name}</span>
|
||||
<span className="text-muted-foreground ml-2">{formatFileSize(file.size)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t("shareMultipleFiles.totalSize")}: {formatFileSize(totalSize)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{step === "link" && (
|
||||
<div className="space-y-4">
|
||||
{!generatedLink ? (
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground">{t("shareFile.linkDescription")}</p>
|
||||
<div className="space-y-2">
|
||||
<Label>{t("shareFile.aliasLabel")}</Label>
|
||||
<Input
|
||||
placeholder={t("shareFile.aliasPlaceholder")}
|
||||
value={alias}
|
||||
onChange={(e) => setAlias(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm text-muted-foreground">{t("shareFile.linkReady")}</p>
|
||||
<Input readOnly value={generatedLink} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
{step === "create" && (
|
||||
<>
|
||||
<Button variant="outline" onClick={handleClose}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={
|
||||
isLoading || !formData.name.trim() || (formData.isPasswordProtected && !formData.password.trim())
|
||||
}
|
||||
onClick={handleCreateShare}
|
||||
>
|
||||
{isLoading ? <div className="animate-spin">⠋</div> : t("shareMultipleFiles.create")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === "link" && !generatedLink && (
|
||||
<>
|
||||
<Button variant="outline" onClick={() => setStep("create")}>
|
||||
{t("common.back")}
|
||||
</Button>
|
||||
<Button disabled={!alias || isLoading} onClick={handleGenerateLink}>
|
||||
{isLoading ? <div className="animate-spin">⠋</div> : t("shareFile.generateLink")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{step === "link" && generatedLink && (
|
||||
<>
|
||||
<Button variant="outline" onClick={handleSuccess}>
|
||||
{t("common.close")}
|
||||
</Button>
|
||||
<Button onClick={handleCopyLink}>
|
||||
<IconCopy className="h-4 w-4 mr-2" />
|
||||
{t("shareFile.copyLink")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
IconCheck,
|
||||
IconChevronDown,
|
||||
IconDotsVertical,
|
||||
IconDownload,
|
||||
IconEdit,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -42,6 +44,10 @@ interface FilesTableProps {
|
||||
onDownload: (objectName: string, fileName: string) => void;
|
||||
onShare: (file: File) => void;
|
||||
onDelete: (file: File) => void;
|
||||
onBulkDelete?: (files: File[]) => void;
|
||||
onBulkShare?: (files: File[]) => void;
|
||||
onBulkDownload?: (files: File[]) => void;
|
||||
setClearSelectionCallback?: (callback: () => void) => void;
|
||||
}
|
||||
|
||||
export function FilesTable({
|
||||
@@ -53,6 +59,10 @@ export function FilesTable({
|
||||
onDownload,
|
||||
onShare,
|
||||
onDelete,
|
||||
onBulkDelete,
|
||||
onBulkShare,
|
||||
onBulkDownload,
|
||||
setClearSelectionCallback,
|
||||
}: FilesTableProps) {
|
||||
const t = useTranslations();
|
||||
const [editingField, setEditingField] = useState<{ fileId: string; field: "name" | "description" } | null>(null);
|
||||
@@ -61,6 +71,7 @@ export function FilesTable({
|
||||
const [pendingChanges, setPendingChanges] = useState<{ [fileId: string]: { name?: string; description?: string } }>(
|
||||
{}
|
||||
);
|
||||
const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set());
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -75,6 +86,17 @@ export function FilesTable({
|
||||
setPendingChanges({});
|
||||
}, [files]);
|
||||
|
||||
// Clear selected files when files array changes
|
||||
useEffect(() => {
|
||||
setSelectedFiles(new Set());
|
||||
}, [files]);
|
||||
|
||||
// Register clearSelection callback with parent
|
||||
useEffect(() => {
|
||||
const clearSelection = () => setSelectedFiles(new Set());
|
||||
setClearSelectionCallback?.(clearSelection);
|
||||
}, [setClearSelectionCallback]);
|
||||
|
||||
const splitFileName = (fullName: string) => {
|
||||
const lastDotIndex = fullName.lastIndexOf(".");
|
||||
return lastDotIndex === -1
|
||||
@@ -162,60 +184,239 @@ export function FilesTable({
|
||||
return field === "name" ? file.name : file.description;
|
||||
};
|
||||
|
||||
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 getSelectedFiles = () => {
|
||||
return files.filter((file) => selectedFiles.has(file.id));
|
||||
};
|
||||
|
||||
const isAllSelected = files.length > 0 && selectedFiles.size === files.length;
|
||||
|
||||
const handleBulkAction = (action: "delete" | "share" | "download") => {
|
||||
const selectedFileObjects = getSelectedFiles();
|
||||
|
||||
if (selectedFileObjects.length === 0) return;
|
||||
|
||||
switch (action) {
|
||||
case "delete":
|
||||
if (onBulkDelete) {
|
||||
onBulkDelete(selectedFileObjects);
|
||||
}
|
||||
break;
|
||||
case "share":
|
||||
if (onBulkShare) {
|
||||
onBulkShare(selectedFileObjects);
|
||||
}
|
||||
break;
|
||||
case "download":
|
||||
if (onBulkDownload) {
|
||||
onBulkDownload(selectedFileObjects);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Don't clear selection here - let the individual handlers do it after their actions complete
|
||||
};
|
||||
|
||||
const showBulkActions = selectedFiles.size > 0 && (onBulkDelete || onBulkShare || onBulkDownload);
|
||||
|
||||
return (
|
||||
<div className="rounded-lg shadow-sm overflow-hidden border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-b-0">
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4 rounded-tl-lg">
|
||||
{t("filesTable.columns.name")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.description")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.size")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.createdAt")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.updatedAt")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 w-[70px] text-xs font-bold text-muted-foreground bg-muted/50 px-4 rounded-tr-lg">
|
||||
{t("filesTable.columns.actions")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{files.map((file) => {
|
||||
const { icon: FileIcon, color } = getFileIcon(file.name);
|
||||
const isEditingName = editingField?.fileId === file.id && editingField?.field === "name";
|
||||
const isEditingDescription = editingField?.fileId === file.id && editingField?.field === "description";
|
||||
const isHoveringName = hoveredField?.fileId === file.id && hoveredField?.field === "name";
|
||||
const isHoveringDescription = hoveredField?.fileId === file.id && hoveredField?.field === "description";
|
||||
<div className="space-y-4">
|
||||
{showBulkActions && (
|
||||
<div className="flex items-center justify-between p-4 bg-muted/30 border rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{t("filesTable.bulkActions.selected", { count: selectedFiles.size })}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="default" size="sm" className="gap-2">
|
||||
{t("filesTable.bulkActions.actions")}
|
||||
<IconChevronDown className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[200px]">
|
||||
{onBulkDownload && (
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => handleBulkAction("download")}>
|
||||
<IconDownload className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.bulkActions.download")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onBulkShare && (
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => handleBulkAction("share")}>
|
||||
<IconShare className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.bulkActions.share")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
{onBulkDelete && (
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleBulkAction("delete")}
|
||||
className="cursor-pointer py-2 text-destructive focus:text-destructive"
|
||||
>
|
||||
<IconTrash className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.bulkActions.delete")}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Button variant="outline" size="sm" onClick={() => setSelectedFiles(new Set())}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
const displayName = getDisplayValue(file, "name") || file.name;
|
||||
const displayDescription = getDisplayValue(file, "description");
|
||||
<div className="rounded-lg shadow-sm overflow-hidden border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow className="border-b-0">
|
||||
<TableHead className="h-10 w-[50px] text-xs font-bold text-muted-foreground bg-muted/50 px-4 rounded-tl-lg">
|
||||
<Checkbox
|
||||
checked={isAllSelected}
|
||||
onCheckedChange={handleSelectAll}
|
||||
aria-label={t("filesTable.selectAll")}
|
||||
/>
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.name")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.description")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.size")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.createdAt")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("filesTable.columns.updatedAt")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 w-[70px] text-xs font-bold text-muted-foreground bg-muted/50 px-4 rounded-tr-lg">
|
||||
{t("filesTable.columns.actions")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{files.map((file) => {
|
||||
const { icon: FileIcon, color } = getFileIcon(file.name);
|
||||
const isEditingName = editingField?.fileId === file.id && editingField?.field === "name";
|
||||
const isEditingDescription = editingField?.fileId === file.id && editingField?.field === "description";
|
||||
const isHoveringName = hoveredField?.fileId === file.id && hoveredField?.field === "name";
|
||||
const isHoveringDescription = hoveredField?.fileId === file.id && hoveredField?.field === "description";
|
||||
const isSelected = selectedFiles.has(file.id);
|
||||
|
||||
return (
|
||||
<TableRow key={file.id} className="hover:bg-muted/50 transition-colors border-0">
|
||||
<TableCell className="h-12 px-4 border-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileIcon className={`h-5 w-5 ${color}`} />
|
||||
const displayName = getDisplayValue(file, "name") || file.name;
|
||||
const displayDescription = getDisplayValue(file, "description");
|
||||
|
||||
return (
|
||||
<TableRow key={file.id} className="hover:bg-muted/50 transition-colors border-0">
|
||||
<TableCell className="h-12 px-4 border-0">
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
onCheckedChange={(checked: boolean) => handleSelectFile(file.id, checked)}
|
||||
aria-label={t("filesTable.selectFile", { fileName: file.name })}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4 border-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<FileIcon className={`h-5 w-5 ${color}`} />
|
||||
<div
|
||||
className="flex items-center gap-1 min-w-0 flex-1"
|
||||
onMouseEnter={() => setHoveredField({ fileId: file.id, field: "name" })}
|
||||
onMouseLeave={() => setHoveredField(null)}
|
||||
>
|
||||
{isEditingName ? (
|
||||
<div className="flex items-center gap-1 flex-1">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 text-sm font-medium"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-green-600 hover:text-green-700 flex-shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
saveEdit();
|
||||
}}
|
||||
>
|
||||
<IconCheck className="h-3 w-3" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-red-600 hover:text-red-700 flex-shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
cancelEdit();
|
||||
}}
|
||||
>
|
||||
<IconX className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 flex-1 min-w-0">
|
||||
<span className="truncate max-w-[200px] font-medium" title={displayName}>
|
||||
{displayName}
|
||||
</span>
|
||||
<div className="w-6 flex justify-center flex-shrink-0">
|
||||
{isHoveringName && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-muted-foreground hover:text-foreground hidden sm:block"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
startEdit(file.id, "name", displayName);
|
||||
}}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4">
|
||||
<div
|
||||
className="flex items-center gap-1 min-w-0 flex-1"
|
||||
onMouseEnter={() => setHoveredField({ fileId: file.id, field: "name" })}
|
||||
className="flex items-center gap-1"
|
||||
onMouseEnter={() => setHoveredField({ fileId: file.id, field: "description" })}
|
||||
onMouseLeave={() => setHoveredField(null)}
|
||||
>
|
||||
{isEditingName ? (
|
||||
{isEditingDescription ? (
|
||||
<div className="flex items-center gap-1 flex-1">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 text-sm font-medium"
|
||||
placeholder="Add description..."
|
||||
className="h-8 text-sm"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Button
|
||||
@@ -243,18 +444,21 @@ export function FilesTable({
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 flex-1 min-w-0">
|
||||
<span className="truncate max-w-[200px] font-medium" title={displayName}>
|
||||
{displayName}
|
||||
<span
|
||||
className="text-muted-foreground truncate max-w-[150px]"
|
||||
title={displayDescription || "-"}
|
||||
>
|
||||
{displayDescription || "-"}
|
||||
</span>
|
||||
<div className="w-6 flex justify-center flex-shrink-0">
|
||||
{isHoveringName && (
|
||||
{isHoveringDescription && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-muted-foreground hover:text-foreground hidden sm:block"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
startEdit(file.id, "name", displayName);
|
||||
startEdit(file.id, "description", displayDescription || "");
|
||||
}}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
@@ -264,116 +468,54 @@ export function FilesTable({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4">
|
||||
<div
|
||||
className="flex items-center gap-1"
|
||||
onMouseEnter={() => setHoveredField({ fileId: file.id, field: "description" })}
|
||||
onMouseLeave={() => setHoveredField(null)}
|
||||
>
|
||||
{isEditingDescription ? (
|
||||
<div className="flex items-center gap-1 flex-1">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Add description..."
|
||||
className="h-8 text-sm"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-green-600 hover:text-green-700 flex-shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
saveEdit();
|
||||
}}
|
||||
>
|
||||
<IconCheck className="h-3 w-3" />
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4">{formatFileSize(file.size)}</TableCell>
|
||||
<TableCell className="h-12 px-4">{formatDateTime(file.createdAt)}</TableCell>
|
||||
<TableCell className="h-12 px-4">{formatDateTime(file.updatedAt || file.createdAt)}</TableCell>
|
||||
<TableCell className="h-12 px-4 text-right">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 hover:bg-muted cursor-pointer">
|
||||
<IconDotsVertical className="h-4 w-4" />
|
||||
<span className="sr-only">{t("filesTable.actions.menu")}</span>
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-red-600 hover:text-red-700 flex-shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
cancelEdit();
|
||||
}}
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[200px]">
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onPreview(file)}>
|
||||
<IconEye className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.preview")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onRename(file)}>
|
||||
<IconEdit className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.edit")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer py-2"
|
||||
onClick={() => onDownload(file.objectName, file.name)}
|
||||
>
|
||||
<IconX className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 flex-1 min-w-0">
|
||||
<span className="flex-1 text-muted-foreground truncate">{displayDescription || "-"}</span>
|
||||
<div className="w-6 flex justify-center flex-shrink-0">
|
||||
{isHoveringDescription && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-muted-foreground hover:text-foreground hidden sm:block"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
startEdit(file.id, "description", displayDescription || "");
|
||||
}}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4">{formatFileSize(file.size)}</TableCell>
|
||||
<TableCell className="h-12 px-4">{formatDateTime(file.createdAt)}</TableCell>
|
||||
<TableCell className="h-12 px-4">{formatDateTime(file.updatedAt || file.createdAt)}</TableCell>
|
||||
<TableCell className="h-12 px-4 text-right">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" size="icon" className="h-8 w-8 hover:bg-muted cursor-pointer">
|
||||
<IconDotsVertical className="h-4 w-4" />
|
||||
<span className="sr-only">{t("filesTable.actions.menu")}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-[200px]">
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onPreview(file)}>
|
||||
<IconEye className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.preview")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onRename(file)}>
|
||||
<IconEdit className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.edit")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="cursor-pointer py-2"
|
||||
onClick={() => onDownload(file.objectName, file.name)}
|
||||
>
|
||||
<IconDownload className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.download")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onShare(file)}>
|
||||
<IconShare className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.share")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => onDelete(file)}
|
||||
className="cursor-pointer py-2 text-destructive focus:text-destructive"
|
||||
>
|
||||
<IconTrash className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.delete")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<IconDownload className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.download")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem className="cursor-pointer py-2" onClick={() => onShare(file)}>
|
||||
<IconShare className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.share")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => onDelete(file)}
|
||||
className="cursor-pointer py-2 text-destructive focus:text-destructive"
|
||||
>
|
||||
<IconTrash className="mr-2 h-4 w-4" />
|
||||
{t("filesTable.actions.delete")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface SharesTableProps {
|
||||
onDelete: (share: any) => void;
|
||||
onEdit: (share: any) => void;
|
||||
onUpdateName: (shareId: string, newName: string) => void;
|
||||
onUpdateDescription: (shareId: string, newDescription: string) => void;
|
||||
onManageFiles: (share: any) => void;
|
||||
onManageRecipients: (share: any) => void;
|
||||
onViewDetails: (share: any) => void;
|
||||
@@ -47,6 +48,7 @@ export function SharesTable({
|
||||
onDelete,
|
||||
onEdit,
|
||||
onUpdateName,
|
||||
onUpdateDescription,
|
||||
onManageFiles,
|
||||
onManageRecipients,
|
||||
onViewDetails,
|
||||
@@ -56,45 +58,54 @@ export function SharesTable({
|
||||
}: SharesTableProps) {
|
||||
const t = useTranslations();
|
||||
const { smtpEnabled } = useShareContext();
|
||||
const [editingShareId, setEditingShareId] = useState<string | null>(null);
|
||||
const [editingField, setEditingField] = useState<{ shareId: string; field: "name" | "description" } | null>(null);
|
||||
const [editValue, setEditValue] = useState("");
|
||||
const [hoveredShareId, setHoveredShareId] = useState<string | null>(null);
|
||||
const [pendingChanges, setPendingChanges] = useState<{ [shareId: string]: { name?: string } }>({});
|
||||
const [hoveredField, setHoveredField] = useState<{ shareId: string; field: "name" | "description" } | null>(null);
|
||||
const [pendingChanges, setPendingChanges] = useState<{ [shareId: string]: { name?: string; description?: string } }>(
|
||||
{}
|
||||
);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (editingShareId && inputRef.current) {
|
||||
if (editingField && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
inputRef.current.select();
|
||||
}
|
||||
}, [editingShareId]);
|
||||
}, [editingField]);
|
||||
|
||||
// Clear pending changes when shares are updated
|
||||
useEffect(() => {
|
||||
setPendingChanges({});
|
||||
}, [shares]);
|
||||
|
||||
const startEdit = (shareId: string, currentName: string) => {
|
||||
setEditingShareId(shareId);
|
||||
setEditValue(currentName);
|
||||
const startEdit = (shareId: string, field: "name" | "description", currentValue: string) => {
|
||||
setEditingField({ shareId, field });
|
||||
setEditValue(currentValue || "");
|
||||
};
|
||||
|
||||
const saveEdit = () => {
|
||||
if (!editingShareId) return;
|
||||
if (!editingField) return;
|
||||
|
||||
const { shareId, field } = editingField;
|
||||
|
||||
// Update local state optimistically
|
||||
setPendingChanges((prev) => ({
|
||||
...prev,
|
||||
[editingShareId]: { name: editValue },
|
||||
[shareId]: { ...prev[shareId], [field]: editValue },
|
||||
}));
|
||||
|
||||
onUpdateName(editingShareId, editValue);
|
||||
setEditingShareId(null);
|
||||
if (field === "name") {
|
||||
onUpdateName(shareId, editValue);
|
||||
} else {
|
||||
onUpdateDescription(shareId, editValue);
|
||||
}
|
||||
|
||||
setEditingField(null);
|
||||
setEditValue("");
|
||||
};
|
||||
|
||||
const cancelEdit = () => {
|
||||
setEditingShareId(null);
|
||||
setEditingField(null);
|
||||
setEditValue("");
|
||||
};
|
||||
|
||||
@@ -106,12 +117,12 @@ export function SharesTable({
|
||||
}
|
||||
};
|
||||
|
||||
const getDisplayName = (share: any) => {
|
||||
const getDisplayValue = (share: any, field: "name" | "description") => {
|
||||
const pendingChange = pendingChanges[share.id];
|
||||
if (pendingChange && pendingChange.name !== undefined) {
|
||||
return pendingChange.name;
|
||||
if (pendingChange && pendingChange[field] !== undefined) {
|
||||
return pendingChange[field];
|
||||
}
|
||||
return share.name;
|
||||
return field === "name" ? share.name : share.description;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -122,6 +133,9 @@ export function SharesTable({
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("sharesTable.columns.name")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("sharesTable.columns.description")}
|
||||
</TableHead>
|
||||
<TableHead className="h-10 text-xs font-bold text-muted-foreground bg-muted/50 px-4">
|
||||
{t("sharesTable.columns.createdAt")}
|
||||
</TableHead>
|
||||
@@ -147,65 +161,135 @@ export function SharesTable({
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{shares.map((share) => {
|
||||
const isEditing = editingShareId === share.id;
|
||||
const isHovering = hoveredShareId === share.id;
|
||||
const displayName = getDisplayName(share);
|
||||
const isEditingName = editingField?.shareId === share.id && editingField?.field === "name";
|
||||
const isEditingDescription = editingField?.shareId === share.id && editingField?.field === "description";
|
||||
const isHoveringName = hoveredField?.shareId === share.id && hoveredField?.field === "name";
|
||||
const isHoveringDescription = hoveredField?.shareId === share.id && hoveredField?.field === "description";
|
||||
const displayName = getDisplayValue(share, "name");
|
||||
const displayDescription = getDisplayValue(share, "description");
|
||||
|
||||
return (
|
||||
<TableRow key={share.id} className="hover:bg-muted/50 transition-colors border-0">
|
||||
<TableCell className="h-12 px-4 border-0">
|
||||
<div
|
||||
className="flex items-center gap-1 min-w-0"
|
||||
onMouseEnter={() => setHoveredShareId(share.id)}
|
||||
onMouseLeave={() => setHoveredShareId(null)}
|
||||
onMouseEnter={() => setHoveredField({ shareId: share.id, field: "name" })}
|
||||
onMouseLeave={() => setHoveredField(null)}
|
||||
>
|
||||
{isEditing ? (
|
||||
{isEditingName ? (
|
||||
<div className="flex items-center gap-1 flex-1">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 text-sm font-medium"
|
||||
className="h-8 text-sm font-medium min-w-[200px]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-green-600 hover:text-green-700 flex-shrink-0"
|
||||
className="h-8 w-8 text-green-600 hover:text-green-700 flex-shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
saveEdit();
|
||||
}}
|
||||
>
|
||||
<IconCheck className="h-3 w-3" />
|
||||
<IconCheck className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-red-600 hover:text-red-700 flex-shrink-0"
|
||||
className="h-8 w-8 text-red-600 hover:text-red-700 flex-shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
cancelEdit();
|
||||
}}
|
||||
>
|
||||
<IconX className="h-3 w-3" />
|
||||
<IconX className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 flex-1 min-w-0">
|
||||
<span className="truncate max-w-[200px] font-medium" title={displayName}>
|
||||
<span className="truncate max-w-[120px] font-medium" title={displayName}>
|
||||
{displayName}
|
||||
</span>
|
||||
<div className="w-6 flex justify-center flex-shrink-0">
|
||||
{isHovering && (
|
||||
{isHoveringName && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-muted-foreground hover:text-foreground hidden sm:block"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
startEdit(share.id, displayName);
|
||||
startEdit(share.id, "name", displayName);
|
||||
}}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="h-12 px-4">
|
||||
<div
|
||||
className="flex items-center gap-1 min-w-0"
|
||||
onMouseEnter={() => setHoveredField({ shareId: share.id, field: "description" })}
|
||||
onMouseLeave={() => setHoveredField(null)}
|
||||
>
|
||||
{isEditingDescription ? (
|
||||
<div className="flex items-center gap-1 flex-1">
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="h-8 text-sm min-w-[250px]"
|
||||
placeholder="Adicionar descrição..."
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-8 w-8 text-green-600 hover:text-green-700 flex-shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
saveEdit();
|
||||
}}
|
||||
>
|
||||
<IconCheck className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-8 w-8 text-red-600 hover:text-red-700 flex-shrink-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
cancelEdit();
|
||||
}}
|
||||
>
|
||||
<IconX className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 flex-1 min-w-0">
|
||||
<span
|
||||
className="text-muted-foreground truncate max-w-[100px]"
|
||||
title={displayDescription || "-"}
|
||||
>
|
||||
{displayDescription || "-"}
|
||||
</span>
|
||||
<div className="w-6 flex justify-center flex-shrink-0">
|
||||
{isHoveringDescription && (
|
||||
<Button
|
||||
size="icon"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 text-muted-foreground hover:text-foreground hidden sm:block"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
startEdit(share.id, "description", displayDescription || "");
|
||||
}}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
|
||||
29
apps/web/src/components/ui/checkbox.tsx
Normal file
29
apps/web/src/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Checkbox({ className, ...props }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
||||
return (
|
||||
<CheckboxPrimitive.Root
|
||||
data-slot="checkbox"
|
||||
className={cn(
|
||||
"cursor-pointer peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
data-slot="checkbox-indicator"
|
||||
className="flex items-center justify-center text-current transition-none"
|
||||
>
|
||||
<CheckIcon className="size-3.5" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export { Checkbox };
|
||||
23
apps/web/src/components/ui/textarea.tsx
Normal file
23
apps/web/src/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
});
|
||||
Textarea.displayName = "Textarea";
|
||||
|
||||
export { Textarea };
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -30,33 +30,68 @@ interface FileToShare {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface BulkFile {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
size: number;
|
||||
objectName: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface FileManagerHook {
|
||||
previewFile: PreviewFile | null;
|
||||
fileToDelete: any;
|
||||
fileToRename: any;
|
||||
fileToShare: FileToShare | null;
|
||||
filesToDelete: BulkFile[] | null;
|
||||
filesToShare: BulkFile[] | null;
|
||||
filesToDownload: BulkFile[] | null;
|
||||
isBulkDownloadModalOpen: boolean;
|
||||
setFileToDelete: (file: any) => void;
|
||||
setFileToRename: (file: any) => void;
|
||||
setPreviewFile: (file: PreviewFile | null) => void;
|
||||
setFileToShare: (file: FileToShare | null) => void;
|
||||
setFilesToDelete: (files: BulkFile[] | null) => void;
|
||||
setFilesToShare: (files: BulkFile[] | null) => void;
|
||||
setFilesToDownload: (files: BulkFile[] | null) => void;
|
||||
setBulkDownloadModalOpen: (open: boolean) => void;
|
||||
handleDelete: (fileId: string) => Promise<void>;
|
||||
handleDownload: (objectName: string, fileName: string) => Promise<void>;
|
||||
handleRename: (fileId: string, newName: string, description?: string) => Promise<void>;
|
||||
handleBulkDelete: (files: BulkFile[]) => void;
|
||||
handleBulkShare: (files: BulkFile[]) => void;
|
||||
handleBulkDownload: (files: BulkFile[]) => void;
|
||||
handleBulkDownloadWithZip: (files: BulkFile[], zipName: string) => Promise<void>;
|
||||
handleDeleteBulk: () => Promise<void>;
|
||||
handleShareBulkSuccess: () => void;
|
||||
clearSelection?: () => void;
|
||||
setClearSelectionCallback?: (callback: () => void) => void;
|
||||
}
|
||||
|
||||
export function useFileManager(onRefresh: () => Promise<void>) {
|
||||
export function useFileManager(onRefresh: () => Promise<void>, clearSelection?: () => void) {
|
||||
const t = useTranslations();
|
||||
const [previewFile, setPreviewFile] = useState<PreviewFile | null>(null);
|
||||
const [fileToRename, setFileToRename] = useState<FileToRename | null>(null);
|
||||
const [fileToDelete, setFileToDelete] = useState<FileToDelete | null>(null);
|
||||
const [fileToShare, setFileToShare] = useState<FileToShare | null>(null);
|
||||
const [filesToDelete, setFilesToDelete] = useState<BulkFile[] | null>(null);
|
||||
const [filesToShare, setFilesToShare] = useState<BulkFile[] | null>(null);
|
||||
const [filesToDownload, setFilesToDownload] = useState<BulkFile[] | null>(null);
|
||||
const [isBulkDownloadModalOpen, setBulkDownloadModalOpen] = useState(false);
|
||||
const [clearSelectionCallback, setClearSelectionCallbackState] = useState<(() => void) | null>(null);
|
||||
|
||||
const setClearSelectionCallback = useCallback((callback: () => void) => {
|
||||
setClearSelectionCallbackState(() => callback);
|
||||
}, []);
|
||||
|
||||
const handleDownload = async (objectName: string, fileName: string) => {
|
||||
try {
|
||||
const encodedObjectName = encodeURIComponent(objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
const downloadUrl = response.data.url;
|
||||
console.log(fileName);
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = downloadUrl;
|
||||
link.download = fileName;
|
||||
@@ -66,7 +101,7 @@ export function useFileManager(onRefresh: () => Promise<void>) {
|
||||
toast.success(t("files.downloadStart"));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.success(t("files.downloadError"));
|
||||
toast.error(t("files.downloadError"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -97,6 +132,107 @@ export function useFileManager(onRefresh: () => Promise<void>) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBulkDelete = (files: BulkFile[]) => {
|
||||
setFilesToDelete(files);
|
||||
};
|
||||
|
||||
const handleBulkShare = (files: BulkFile[]) => {
|
||||
setFilesToShare(files);
|
||||
};
|
||||
|
||||
const handleShareBulkSuccess = () => {
|
||||
setFilesToShare(null);
|
||||
// Clear selection after successful share
|
||||
if (clearSelectionCallback) {
|
||||
clearSelectionCallback();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBulkDownload = (files: BulkFile[]) => {
|
||||
setFilesToDownload(files);
|
||||
setBulkDownloadModalOpen(true);
|
||||
};
|
||||
|
||||
const handleBulkDownloadWithZip = async (files: BulkFile[], zipName: string) => {
|
||||
try {
|
||||
toast.promise(
|
||||
(async () => {
|
||||
// Dynamically import JSZip
|
||||
const JSZip = (await import("jszip")).default;
|
||||
const zip = new JSZip();
|
||||
|
||||
// Download all files and add to zip
|
||||
const downloadPromises = files.map(async (file) => {
|
||||
try {
|
||||
const encodedObjectName = encodeURIComponent(file.objectName);
|
||||
const downloadResponse = await getDownloadUrl(encodedObjectName);
|
||||
const downloadUrl = downloadResponse.data.url;
|
||||
const response = await fetch(downloadUrl);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download ${file.name}`);
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
zip.file(file.name, blob);
|
||||
} catch (error) {
|
||||
console.error(`Error downloading file ${file.name}:`, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(downloadPromises);
|
||||
|
||||
// Generate ZIP blob
|
||||
const zipBlob = await zip.generateAsync({ type: "blob" });
|
||||
|
||||
// Download ZIP file
|
||||
const url = URL.createObjectURL(zipBlob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = zipName.endsWith(".zip") ? zipName : `${zipName}.zip`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
// Clear selection after successful download
|
||||
if (clearSelectionCallback) {
|
||||
clearSelectionCallback();
|
||||
}
|
||||
})(),
|
||||
{
|
||||
loading: "Criando arquivo ZIP...",
|
||||
success: "Arquivo ZIP baixado com sucesso!",
|
||||
error: "Erro ao criar arquivo ZIP",
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error creating ZIP:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteBulk = async () => {
|
||||
if (!filesToDelete) return;
|
||||
|
||||
try {
|
||||
const deletePromises = filesToDelete.map((file) => deleteFile(file.id));
|
||||
await Promise.all(deletePromises);
|
||||
|
||||
toast.success(t("files.bulkDeleteSuccess", { count: filesToDelete.length }));
|
||||
setFilesToDelete(null);
|
||||
onRefresh();
|
||||
|
||||
// Clear selection after successful deletion
|
||||
if (clearSelectionCallback) {
|
||||
clearSelectionCallback();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete files:", error);
|
||||
toast.error(t("files.bulkDeleteError"));
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
previewFile,
|
||||
setPreviewFile,
|
||||
@@ -106,8 +242,24 @@ export function useFileManager(onRefresh: () => Promise<void>) {
|
||||
setFileToDelete,
|
||||
fileToShare,
|
||||
setFileToShare,
|
||||
filesToDelete,
|
||||
setFilesToDelete,
|
||||
filesToShare,
|
||||
setFilesToShare,
|
||||
filesToDownload,
|
||||
setFilesToDownload,
|
||||
isBulkDownloadModalOpen,
|
||||
setBulkDownloadModalOpen,
|
||||
handleDownload,
|
||||
handleRename,
|
||||
handleDelete,
|
||||
handleBulkDelete,
|
||||
handleBulkShare,
|
||||
handleBulkDownload,
|
||||
handleBulkDownloadWithZip,
|
||||
handleDeleteBulk,
|
||||
handleShareBulkSuccess,
|
||||
clearSelection,
|
||||
setClearSelectionCallback: setClearSelectionCallback,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface ShareManagerHook {
|
||||
handleDelete: (shareId: string) => Promise<void>;
|
||||
handleEdit: (shareId: string, data: any) => Promise<void>;
|
||||
handleUpdateName: (shareId: string, newName: string) => Promise<void>;
|
||||
handleUpdateDescription: (shareId: string, newDescription: string) => Promise<void>;
|
||||
handleManageFiles: (shareId: string, files: any[]) => Promise<void>;
|
||||
handleManageRecipients: (shareId: string, recipients: any[]) => Promise<void>;
|
||||
handleGenerateLink: (shareId: string, alias: string) => Promise<void>;
|
||||
@@ -80,6 +81,17 @@ export function useShareManager(onSuccess: () => void) {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateDescription = async (shareId: string, newDescription: string) => {
|
||||
try {
|
||||
await updateShare({ id: shareId, description: newDescription });
|
||||
await onSuccess();
|
||||
toast.success(t("shareManager.updateSuccess"));
|
||||
} catch (error) {
|
||||
toast.error(t("shareManager.updateError"));
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleManageFiles = async (shareId: string, files: string[]) => {
|
||||
try {
|
||||
await addFiles(shareId, { files });
|
||||
@@ -147,6 +159,7 @@ export function useShareManager(onSuccess: () => void) {
|
||||
handleDelete,
|
||||
handleEdit,
|
||||
handleUpdateName,
|
||||
handleUpdateDescription,
|
||||
handleManageFiles,
|
||||
handleManageRecipients,
|
||||
handleGenerateLink,
|
||||
|
||||
Reference in New Issue
Block a user