mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 14:21:46 +00:00
feat: add copy/duplicate trip from dashboard (#270)
New POST /api/trips/:id/copy endpoint that deep copies all trip planning data (days, places, assignments, reservations, budget, packing, accommodations, day notes) with proper FK remapping inside a transaction. Skips files, collab data, and members. Copy button on all dashboard card types (spotlight, grid, list, archived) gated by trip_create permission. Translations for all 12 languages. Also adds reminder_days to Trip interface (removes as-any casts).
This commit is contained in:
@@ -87,6 +87,8 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.places': 'الأماكن',
|
||||
'dashboard.members': 'ال חברים',
|
||||
'dashboard.archive': 'أرشفة',
|
||||
'dashboard.copyTrip': 'نسخ',
|
||||
'dashboard.copySuffix': 'نسخة',
|
||||
'dashboard.restore': 'استعادة',
|
||||
'dashboard.archived': 'مؤرشفة',
|
||||
'dashboard.status.ongoing': 'جارية',
|
||||
@@ -105,6 +107,8 @@ const ar: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.toast.archiveError': 'فشل الأرشفة',
|
||||
'dashboard.toast.restored': 'تمت استعادة الرحلة',
|
||||
'dashboard.toast.restoreError': 'فشل الاستعادة',
|
||||
'dashboard.toast.copied': 'تم نسخ الرحلة!',
|
||||
'dashboard.toast.copyError': 'فشل نسخ الرحلة',
|
||||
'dashboard.confirm.delete': 'حذف الرحلة "{title}"؟ سيتم حذف جميع الأماكن والخطط نهائيًا.',
|
||||
'dashboard.editTrip': 'تعديل الرحلة',
|
||||
'dashboard.createTrip': 'إنشاء رحلة جديدة',
|
||||
|
||||
@@ -82,6 +82,8 @@ const br: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.places': 'Lugares',
|
||||
'dashboard.members': 'Parceiros de viagem',
|
||||
'dashboard.archive': 'Arquivar',
|
||||
'dashboard.copyTrip': 'Copiar',
|
||||
'dashboard.copySuffix': 'cópia',
|
||||
'dashboard.restore': 'Restaurar',
|
||||
'dashboard.archived': 'Arquivada',
|
||||
'dashboard.status.ongoing': 'Em andamento',
|
||||
@@ -100,6 +102,8 @@ const br: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.toast.archiveError': 'Não foi possível arquivar',
|
||||
'dashboard.toast.restored': 'Viagem restaurada',
|
||||
'dashboard.toast.restoreError': 'Não foi possível restaurar',
|
||||
'dashboard.toast.copied': 'Viagem copiada!',
|
||||
'dashboard.toast.copyError': 'Não foi possível copiar a viagem',
|
||||
'dashboard.confirm.delete': 'Excluir a viagem "{title}"? Todos os lugares e planos serão excluídos permanentemente.',
|
||||
'dashboard.editTrip': 'Editar viagem',
|
||||
'dashboard.createTrip': 'Criar nova viagem',
|
||||
|
||||
@@ -83,6 +83,8 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.places': 'Míst',
|
||||
'dashboard.members': 'Cestovní parťáci',
|
||||
'dashboard.archive': 'Archivovat',
|
||||
'dashboard.copyTrip': 'Kopírovat',
|
||||
'dashboard.copySuffix': 'kopie',
|
||||
'dashboard.restore': 'Obnovit',
|
||||
'dashboard.archived': 'Archivováno',
|
||||
'dashboard.status.ongoing': 'Probíhající',
|
||||
@@ -101,7 +103,9 @@ const cs: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.toast.archiveError': 'Nepodařilo se archivovat cestu',
|
||||
'dashboard.toast.restored': 'Cesta byla obnovena',
|
||||
'dashboard.toast.restoreError': 'Nepodařilo se obnovit cestu',
|
||||
'dashboard.confirm.delete': 'Smazat cestu „{title}“? Všechna místa a plány budou trvale smazány.',
|
||||
'dashboard.toast.copied': 'Cesta byla zkopírována!',
|
||||
'dashboard.toast.copyError': 'Nepodařilo se zkopírovat cestu',
|
||||
'dashboard.confirm.delete': 'Smazat cestu „{title}”? Všechna místa a plány budou trvale smazány.',
|
||||
'dashboard.editTrip': 'Upravit cestu',
|
||||
'dashboard.createTrip': 'Vytvořit novou cestu',
|
||||
'dashboard.tripTitle': 'Název',
|
||||
|
||||
@@ -82,6 +82,8 @@ const de: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.places': 'Orte',
|
||||
'dashboard.members': 'Reise-Buddies',
|
||||
'dashboard.archive': 'Archivieren',
|
||||
'dashboard.copyTrip': 'Kopieren',
|
||||
'dashboard.copySuffix': 'Kopie',
|
||||
'dashboard.restore': 'Wiederherstellen',
|
||||
'dashboard.archived': 'Archiviert',
|
||||
'dashboard.status.ongoing': 'Laufend',
|
||||
@@ -100,6 +102,8 @@ const de: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.toast.archiveError': 'Fehler beim Archivieren',
|
||||
'dashboard.toast.restored': 'Reise wiederhergestellt',
|
||||
'dashboard.toast.restoreError': 'Fehler beim Wiederherstellen',
|
||||
'dashboard.toast.copied': 'Reise kopiert!',
|
||||
'dashboard.toast.copyError': 'Fehler beim Kopieren der Reise',
|
||||
'dashboard.confirm.delete': 'Reise "{title}" löschen? Alle Orte und Pläne werden unwiderruflich gelöscht.',
|
||||
'dashboard.editTrip': 'Reise bearbeiten',
|
||||
'dashboard.createTrip': 'Neue Reise erstellen',
|
||||
|
||||
@@ -82,6 +82,8 @@ const en: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.places': 'Places',
|
||||
'dashboard.members': 'Buddies',
|
||||
'dashboard.archive': 'Archive',
|
||||
'dashboard.copyTrip': 'Copy',
|
||||
'dashboard.copySuffix': 'copy',
|
||||
'dashboard.restore': 'Restore',
|
||||
'dashboard.archived': 'Archived',
|
||||
'dashboard.status.ongoing': 'Ongoing',
|
||||
@@ -100,6 +102,8 @@ const en: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.toast.archiveError': 'Failed to archive trip',
|
||||
'dashboard.toast.restored': 'Trip restored',
|
||||
'dashboard.toast.restoreError': 'Failed to restore trip',
|
||||
'dashboard.toast.copied': 'Trip copied!',
|
||||
'dashboard.toast.copyError': 'Failed to copy trip',
|
||||
'dashboard.confirm.delete': 'Delete trip "{title}"? All places and plans will be permanently deleted.',
|
||||
'dashboard.editTrip': 'Edit Trip',
|
||||
'dashboard.createTrip': 'Create New Trip',
|
||||
|
||||
@@ -83,6 +83,8 @@ const es: Record<string, string> = {
|
||||
'dashboard.places': 'Lugares',
|
||||
'dashboard.members': 'Compañeros de viaje',
|
||||
'dashboard.archive': 'Archivar',
|
||||
'dashboard.copyTrip': 'Copiar',
|
||||
'dashboard.copySuffix': 'copia',
|
||||
'dashboard.restore': 'Restaurar',
|
||||
'dashboard.archived': 'Archivado',
|
||||
'dashboard.status.ongoing': 'En curso',
|
||||
@@ -101,6 +103,8 @@ const es: Record<string, string> = {
|
||||
'dashboard.toast.archiveError': 'No se pudo archivar el viaje',
|
||||
'dashboard.toast.restored': 'Viaje restaurado',
|
||||
'dashboard.toast.restoreError': 'No se pudo restaurar el viaje',
|
||||
'dashboard.toast.copied': '¡Viaje copiado!',
|
||||
'dashboard.toast.copyError': 'No se pudo copiar el viaje',
|
||||
'dashboard.confirm.delete': '¿Eliminar el viaje "{title}"? Todos los lugares y planes se borrarán permanentemente.',
|
||||
'dashboard.editTrip': 'Editar viaje',
|
||||
'dashboard.createTrip': 'Crear nuevo viaje',
|
||||
|
||||
@@ -82,6 +82,8 @@ const fr: Record<string, string> = {
|
||||
'dashboard.places': 'Lieux',
|
||||
'dashboard.members': 'Compagnons de voyage',
|
||||
'dashboard.archive': 'Archiver',
|
||||
'dashboard.copyTrip': 'Copier',
|
||||
'dashboard.copySuffix': 'copie',
|
||||
'dashboard.restore': 'Restaurer',
|
||||
'dashboard.archived': 'Archivé',
|
||||
'dashboard.status.ongoing': 'En cours',
|
||||
@@ -100,6 +102,8 @@ const fr: Record<string, string> = {
|
||||
'dashboard.toast.archiveError': "Impossible d'archiver le voyage",
|
||||
'dashboard.toast.restored': 'Voyage restauré',
|
||||
'dashboard.toast.restoreError': 'Impossible de restaurer le voyage',
|
||||
'dashboard.toast.copied': 'Voyage copié !',
|
||||
'dashboard.toast.copyError': 'Impossible de copier le voyage',
|
||||
'dashboard.confirm.delete': 'Supprimer le voyage « {title} » ? Tous les lieux et plans seront définitivement supprimés.',
|
||||
'dashboard.editTrip': 'Modifier le voyage',
|
||||
'dashboard.createTrip': 'Créer un nouveau voyage',
|
||||
|
||||
@@ -82,6 +82,8 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.places': 'hely',
|
||||
'dashboard.members': 'Útitársak',
|
||||
'dashboard.archive': 'Archiválás',
|
||||
'dashboard.copyTrip': 'Másolás',
|
||||
'dashboard.copySuffix': 'másolat',
|
||||
'dashboard.restore': 'Visszaállítás',
|
||||
'dashboard.archived': 'Archivált',
|
||||
'dashboard.status.ongoing': 'Folyamatban',
|
||||
@@ -100,6 +102,8 @@ const hu: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.toast.archiveError': 'Nem sikerült archiválni',
|
||||
'dashboard.toast.restored': 'Utazás visszaállítva',
|
||||
'dashboard.toast.restoreError': 'Nem sikerült visszaállítani',
|
||||
'dashboard.toast.copied': 'Utazás másolva!',
|
||||
'dashboard.toast.copyError': 'Nem sikerült másolni az utazást',
|
||||
'dashboard.confirm.delete': '"{title}" utazás törlése? Minden hely és terv véglegesen törlődik.',
|
||||
'dashboard.editTrip': 'Utazás szerkesztése',
|
||||
'dashboard.createTrip': 'Új utazás létrehozása',
|
||||
|
||||
@@ -82,6 +82,8 @@ const it: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.places': 'Luoghi',
|
||||
'dashboard.members': 'Compagni di viaggio',
|
||||
'dashboard.archive': 'Archivia',
|
||||
'dashboard.copyTrip': 'Copia',
|
||||
'dashboard.copySuffix': 'copia',
|
||||
'dashboard.restore': 'Ripristina',
|
||||
'dashboard.archived': 'Archiviati',
|
||||
'dashboard.status.ongoing': 'In corso',
|
||||
@@ -100,6 +102,8 @@ const it: Record<string, string | { name: string; category: string }[]> = {
|
||||
'dashboard.toast.archiveError': 'Impossibile archiviare il viaggio',
|
||||
'dashboard.toast.restored': 'Viaggio ripristinato',
|
||||
'dashboard.toast.restoreError': 'Impossibile ripristinare il viaggio',
|
||||
'dashboard.toast.copied': 'Viaggio copiato!',
|
||||
'dashboard.toast.copyError': 'Impossibile copiare il viaggio',
|
||||
'dashboard.confirm.delete': 'Eliminare il viaggio "{title}"? Tutti i luoghi e i programmi verranno eliminati in modo permanente.',
|
||||
'dashboard.editTrip': 'Modifica Viaggio',
|
||||
'dashboard.createTrip': 'Crea Nuovo Viaggio',
|
||||
|
||||
@@ -82,6 +82,8 @@ const nl: Record<string, string> = {
|
||||
'dashboard.places': 'Plaatsen',
|
||||
'dashboard.members': 'Reisgenoten',
|
||||
'dashboard.archive': 'Archiveren',
|
||||
'dashboard.copyTrip': 'Kopiëren',
|
||||
'dashboard.copySuffix': 'kopie',
|
||||
'dashboard.restore': 'Herstellen',
|
||||
'dashboard.archived': 'Gearchiveerd',
|
||||
'dashboard.status.ongoing': 'Lopend',
|
||||
@@ -100,6 +102,8 @@ const nl: Record<string, string> = {
|
||||
'dashboard.toast.archiveError': 'Reis archiveren mislukt',
|
||||
'dashboard.toast.restored': 'Reis hersteld',
|
||||
'dashboard.toast.restoreError': 'Reis herstellen mislukt',
|
||||
'dashboard.toast.copied': 'Reis gekopieerd!',
|
||||
'dashboard.toast.copyError': 'Reis kopiëren mislukt',
|
||||
'dashboard.confirm.delete': 'Reis "{title}" verwijderen? Alle plaatsen en plannen worden permanent verwijderd.',
|
||||
'dashboard.editTrip': 'Reis bewerken',
|
||||
'dashboard.createTrip': 'Nieuwe reis aanmaken',
|
||||
|
||||
@@ -82,6 +82,8 @@ const ru: Record<string, string> = {
|
||||
'dashboard.places': 'Места',
|
||||
'dashboard.members': 'Попутчики',
|
||||
'dashboard.archive': 'Архивировать',
|
||||
'dashboard.copyTrip': 'Копировать',
|
||||
'dashboard.copySuffix': 'копия',
|
||||
'dashboard.restore': 'Восстановить',
|
||||
'dashboard.archived': 'В архиве',
|
||||
'dashboard.status.ongoing': 'В процессе',
|
||||
@@ -100,6 +102,8 @@ const ru: Record<string, string> = {
|
||||
'dashboard.toast.archiveError': 'Не удалось архивировать поездку',
|
||||
'dashboard.toast.restored': 'Поездка восстановлена',
|
||||
'dashboard.toast.restoreError': 'Не удалось восстановить поездку',
|
||||
'dashboard.toast.copied': 'Поездка скопирована!',
|
||||
'dashboard.toast.copyError': 'Не удалось скопировать поездку',
|
||||
'dashboard.confirm.delete': 'Удалить поездку «{title}»? Все места и планы будут безвозвратно удалены.',
|
||||
'dashboard.editTrip': 'Редактировать поездку',
|
||||
'dashboard.createTrip': 'Создать новую поездку',
|
||||
|
||||
@@ -82,6 +82,8 @@ const zh: Record<string, string> = {
|
||||
'dashboard.places': '地点',
|
||||
'dashboard.members': '旅伴',
|
||||
'dashboard.archive': '归档',
|
||||
'dashboard.copyTrip': '复制',
|
||||
'dashboard.copySuffix': '副本',
|
||||
'dashboard.restore': '恢复',
|
||||
'dashboard.archived': '已归档',
|
||||
'dashboard.status.ongoing': '进行中',
|
||||
@@ -100,6 +102,8 @@ const zh: Record<string, string> = {
|
||||
'dashboard.toast.archiveError': '归档旅行失败',
|
||||
'dashboard.toast.restored': '旅行已恢复',
|
||||
'dashboard.toast.restoreError': '恢复旅行失败',
|
||||
'dashboard.toast.copied': '旅行已复制!',
|
||||
'dashboard.toast.copyError': '复制旅行失败',
|
||||
'dashboard.confirm.delete': '删除旅行「{title}」?所有地点和计划将被永久删除。',
|
||||
'dashboard.editTrip': '编辑旅行',
|
||||
'dashboard.createTrip': '创建新旅行',
|
||||
|
||||
Reference in New Issue
Block a user