diff --git a/src/components/specific/projects/project-history-activity/ProjectHistoryActivity.css b/src/components/specific/projects/project-history-activity/ProjectHistoryActivity.css index e511e667..d93ca242 100644 --- a/src/components/specific/projects/project-history-activity/ProjectHistoryActivity.css +++ b/src/components/specific/projects/project-history-activity/ProjectHistoryActivity.css @@ -1,23 +1,31 @@ .history-activity-panel { - height: calc(100vh - 44px - 40px - 12px - 12px); - overflow: auto; + height: calc(100% - 40px); + display: flex; + flex-direction: column; .header { h3 { margin-bottom: 12px; font-size: 18px; } } - .input-search { - border: 1px solid transparent; - border-radius: 3px; - &.focus { - background-color: var(--color-silver-light); - box-shadow: none; - border: 1px solid #efefef; + .actions { + gap: 6px; + .input-search { + border: 1px solid transparent; + border-radius: 3px; + &.focus { + background-color: var(--color-silver-light); + box-shadow: none; + border: 1px solid #efefef; + } } } + .content { + flex: 1; + overflow: auto; + } .day-group { - margin-top: 20px; + margin-top: var(--spacing-unit); .day-title { font-size: 13px; color: var(--color-granite); diff --git a/src/components/specific/projects/project-history-activity/ProjectHistoryActivity.vue b/src/components/specific/projects/project-history-activity/ProjectHistoryActivity.vue index 27fb9966..eb7ebc56 100644 --- a/src/components/specific/projects/project-history-activity/ProjectHistoryActivity.vue +++ b/src/components/specific/projects/project-history-activity/ProjectHistoryActivity.vue @@ -1,33 +1,44 @@ @@ -40,6 +51,7 @@ import { useTimeAgo } from "../../../../composables/time.js"; import ProjectService from "../../../../services/ProjectService.js"; import ActivityItem from "./activity-item/ActivityItem.vue"; +import ActivityFilters from "./activity-filters/ActivityFilters.vue"; export default { props: { @@ -47,6 +59,7 @@ export default { }, components: { ActivityItem, + ActivityFilters, }, emits: ["go-folder"], @@ -119,24 +132,58 @@ export default { const displayedGroupedLogs = computed(() => { const search = searchText.value.trim().toLowerCase(); + const f = filters.value; return sortedLogs.value - .filter((log) => !search || log._search.includes(search)) + .filter((log) => { + if (search && !log._search.includes(search)) return false; + + // Types + if (f.types.length && !f.types.includes(log.action)) return false; + + // Users + if (f.users.length && !f.users.includes(log.user_email)) return false; + + // Date + if (f.dateFrom && log.dateObj < f.dateFrom) return false; + if (f.dateTo) { + const endOfDay = new Date(f.dateTo); + endOfDay.setHours(23, 59, 59, 999); + if (log.dateObj > endOfDay) return false; + } + + return true; + }) .reduce((groups, log) => { const day = formatDay(log.dateObj); - groups[day] ??= []; groups[day].push(log); - return groups; }, {}); }); + const hasDisplayedLogs = computed(() => Object.keys(displayedGroupedLogs.value).length > 0); + const filters = ref({ + types: [], + users: [], + dateFrom: null, + dateTo: null, + }); + + const availableUsers = computed(() => [ + ...new Set(logs.value.map((l) => l.user_email).filter(Boolean)), + ]); + + const availableActions = computed(() => new Set(logs.value.map((l) => l.action))); + return { displayedGroupedLogs, hasDisplayedLogs, searchText, + filters, + availableUsers, + availableActions, formatTimeAgo, }; }, diff --git a/src/components/specific/projects/project-history-activity/activity-filters/ActivityFilters.css b/src/components/specific/projects/project-history-activity/activity-filters/ActivityFilters.css new file mode 100644 index 00000000..f5c2470a --- /dev/null +++ b/src/components/specific/projects/project-history-activity/activity-filters/ActivityFilters.css @@ -0,0 +1,87 @@ +.activity-filters { + position: relative; + + /* Filters button */ + .activity-filters__btn { + gap: 6px; + .activity-filters__btn--active { + border-color: var(--color-primary); + background-color: rgba(47, 55, 74, .05); + color: var(--color-primary); + } + .activity-filters__badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 4px; + border-radius: 9px; + background: var(--color-primary); + color: white; + font-size: 10px; + font-weight: 600; + } + } + + /* Panel */ + .activity-filters__panel { + position: absolute; + top: calc(100% + 6px); + right: 3px; + z-index: 100; + width: 320px; + background: var(--color-white); + border-radius: 6px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + overflow: auto; + .activity-filters__panel-header { + padding: 12px 16px; + font-size: 12px; + font-weight: 600; + color: var(--color-primary); + border-bottom: 1px solid var(--color-silver-light, #f0f0f0); + } + + /* Sections */ + .activity-filters__section { + padding: 12px; + .activity-filters__title { + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.04em; + margin-bottom: 3px; + } + .activity-filters__row { + padding: 2px 0; + font-size: 12px; + cursor: pointer; + user-select: none; + + &.is-disabled { + opacity: 0.4; + cursor: default; + } + } + } + .activity-filters__date-range { + :deep() .not-empty { + label { + display: none + } + } + } + + /* Footer */ + .activity-filters__footer { + display: flex; + justify-content: flex-end; + align-items: center; + gap: var(--spacing-unit); + padding: 12px 16px; + border-top: 1px solid var(--color-silver-light, #f0f0f0); + background: var(--color-white); + } + } +} \ No newline at end of file diff --git a/src/components/specific/projects/project-history-activity/activity-filters/ActivityFilters.vue b/src/components/specific/projects/project-history-activity/activity-filters/ActivityFilters.vue new file mode 100644 index 00000000..19f5f7cd --- /dev/null +++ b/src/components/specific/projects/project-history-activity/activity-filters/ActivityFilters.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/src/components/specific/projects/project-history-activity/activity-item/ActivityItem.css b/src/components/specific/projects/project-history-activity/activity-item/ActivityItem.css index 86b8c403..df63f171 100644 --- a/src/components/specific/projects/project-history-activity/activity-item/ActivityItem.css +++ b/src/components/specific/projects/project-history-activity/activity-item/ActivityItem.css @@ -131,7 +131,7 @@ } .red { background: rgba(188, 0, 10, 0.1); - color: var(--color-danger); + color: rgba(188, 0, 10, 1); } .teal { background: rgba(27, 155, 162, 0.1); diff --git a/src/components/specific/projects/project-history-activity/activity-item/ActivityItem.vue b/src/components/specific/projects/project-history-activity/activity-item/ActivityItem.vue index d213c603..5a062773 100644 --- a/src/components/specific/projects/project-history-activity/activity-item/ActivityItem.vue +++ b/src/components/specific/projects/project-history-activity/activity-item/ActivityItem.vue @@ -74,6 +74,28 @@ {{ $t("ProjectOverview.activity.newNameTitle") }} {{ log.activity.details.newName }} + +
+
+ {{ $t("ProjectOverview.activity.oldPathTitle") }} + +
+
+ + {{ $t("ProjectOverview.activity.newPathTitle") }} + +
+
diff --git a/src/config/activity-config.js b/src/config/activity-config.js index befe937b..e151a85f 100644 --- a/src/config/activity-config.js +++ b/src/config/activity-config.js @@ -16,7 +16,7 @@ const PERMISSION_LIST = [ const getFileName = (path) => path?.split("/").pop() || ""; -const ACTION_CONFIG = { +export const ACTION_CONFIG = { document_created: { actionKey: "ProjectOverview.activity.document_created", getTarget: (log) => getFileName(log.description?.path), @@ -170,6 +170,14 @@ export const getActivityFromLog = (log) => { const roleKey = roleList[log.description?.project_role]; + const getParentPath = (path) => path?.split("/").slice(0, -1).join("/") || ""; + + const oldPath = log.description?.old_path; + const newPath = log.description?.new_path; + + const isMoved = oldPath && newPath && getParentPath(oldPath) !== getParentPath(newPath); + const isRenamed = oldPath && newPath && getFileName(oldPath) !== getFileName(newPath); + const newPermissionEntry = PERMISSION_LIST.find( (entry) => entry.value === log.description?.new_permission, ); @@ -195,10 +203,10 @@ export const getActivityFromLog = (log) => { roleKey, newPermissionKey, oldPermissionKey, - oldName: getFileName(log.description?.old_path), - newName: getFileName(log.description?.new_path), - oldPath: log.description?.old_path, - newPath: log.description?.new_path, + oldName: !isMoved && isRenamed ? getFileName(oldPath) : null, + newName: !isMoved && isRenamed ? getFileName(newPath) : null, + oldPath: isMoved ? oldPath : null, + newPath: isMoved ? newPath : null, }, }; }; diff --git a/src/i18n/lang/en.json b/src/i18n/lang/en.json index 6c200ff1..9635601d 100644 --- a/src/i18n/lang/en.json +++ b/src/i18n/lang/en.json @@ -31,6 +31,8 @@ "empty": "No recent activity", "title": "Activity history", "folderTitle": "Folder:", + "newPathTitle": "New path:", + "oldPathTitle": "Old path:", "roleTitle": "Role:", "newPermissionTitle": "New rights:", "oldPermissionTitle": "Old rights:", @@ -40,22 +42,25 @@ "document_deleted": "File deleted:", "document_renamed": "File renamed:", "document_moved": "File moved:", - "folder_created": "Folder created:", "folder_deleted": "Folder deleted:", "folder_permissions_updated": "Folder permissions updated:", "folder_renamed": "Folder renamed:", "folder_moved": "Folder moved:", - "cloud_invitation_sent": "Invitation sent to:", "cloud_invitation_canceled": "Invitation canceled for:", "cloud_invitation_accepted": "Invitation accepted by:", "cloud_invitation_denied": "Invitation denied by:", - "project_invitation_sent": "Project invitation sent to:", "project_invitation_canceled": "Project invitation canceled for:", "project_invitation_accepted": "Project invitation accepted by:", "project_invitation_denied": "Project invitation denied by:", + "filters": { + "title": "Filters", + "types": "Types", + "users": "Users", + "period": "Period" + }, "roles": { "admin": "Administrator", "user": "User", @@ -954,6 +959,7 @@ "t": { "add": "Add", "amount": "Amount", + "apply": "Apply", "archive": "Archive", "back": "Back", "cancel": "Cancel", @@ -972,6 +978,7 @@ "error": "Error", "export": "Export", "file": "File", + "filters": "Filters", "folder": "Folder", "import": "Import", "invalidName": "Invalid name", @@ -983,6 +990,7 @@ "open": "Open", "or": "or", "rename": "Rename", + "reset": "Reset", "search": "Search", "size": "Size", "space": "Space", diff --git a/src/i18n/lang/fr.json b/src/i18n/lang/fr.json index 84d8fc31..6804246b 100644 --- a/src/i18n/lang/fr.json +++ b/src/i18n/lang/fr.json @@ -2,6 +2,7 @@ "t": { "add": "Ajouter", "amount": "Montant", + "apply": "Appliquer", "archive": "Archiver", "back": "Retour", "cancel": "Annuler", @@ -25,6 +26,7 @@ "export": "Exporter en BCF", "exportXlsx": "Exporter en Excel", "file": "Fichier", + "filters": "Filtres", "folder": "Dossier", "hours_ago": "il y a {count} heure | il y a {count} heures", "import": "Importer", @@ -43,6 +45,7 @@ "project": "Projet", "projects": "Projets", "rename": "Renommer", + "reset": "Réinitialiser", "rootFolder": "Dossier racine", "save": "Sauvegarder", "search": "Rechercher", @@ -99,6 +102,8 @@ "empty": "Aucune activité récente", "title": "Historique d’activité", "folderTitle": "Dossier :", + "newPathTitle": "Nouveau chemin :", + "oldPathTitle": "Ancien chemin :", "roleTitle": "Rôle :", "newPermissionTitle": "Nouveaux droits :", "oldPermissionTitle": "Anciens droits :", @@ -108,22 +113,25 @@ "document_deleted": "Suppression du fichier :", "document_renamed": "Renommage du fichier :", "document_moved": "Déplacement du fichier :", - "folder_created": "Création du dossier :", "folder_deleted": "Suppression du dossier :", "folder_permissions_updated": "Modification des permissions du dossier :", "folder_renamed": "Renommage du dossier :", "folder_moved": "Déplacement du dossier :", - "cloud_invitation_sent": "Invitation envoyée à :", "cloud_invitation_canceled": "Invitation annulée pour :", "cloud_invitation_accepted": "Invitation acceptée par :", "cloud_invitation_denied": "Invitation refusée par :", - "project_invitation_sent": "Invitation projet envoyée à :", "project_invitation_canceled": "Invitation projet annulée pour :", "project_invitation_accepted": "Invitation projet acceptée par :", "project_invitation_denied": "Invitation projet refusée par :", + "filters": { + "title": "Filtres", + "types": "Types", + "users": "Utilisateurs", + "period": "Période" + }, "roles": { "admin": "Administrateur", "user": "Utilisateur",