Add EOI automation system with email processing and document management

- Implement automated EOI processing from sales emails
- Add EOI document upload and management capabilities
- Enhance email thread handling with better parsing and grouping
- Add retry logic and error handling for file operations
- Introduce Documeso integration for document processing
- Create server tasks and plugins infrastructure
- Update email composer with improved attachment handling
This commit is contained in:
2025-06-10 13:59:09 +02:00
parent 5c30411c2b
commit 218705da52
25 changed files with 2351 additions and 71 deletions

View File

@@ -1,7 +1,7 @@
<template>
<v-container fluid class="pa-6">
<!-- Header -->
<v-row class="mb-6">
<v-row class="mb-6" v-if="!props.selectionMode">
<v-col>
<h1 class="text-h4 font-weight-bold">
File Browser
@@ -12,14 +12,26 @@
</v-col>
</v-row>
<!-- Selection Mode Header -->
<v-row v-if="props.selectionMode" class="mb-4">
<v-col>
<h2 class="text-h5 font-weight-bold">
Select Files to Attach
</h2>
<p class="text-subtitle-2 text-grey mt-1">
Click on files to attach them to your email
</p>
</v-col>
</v-row>
<!-- Breadcrumb Navigation -->
<v-row class="mb-4" v-if="currentPath">
<v-row class="mb-4" v-if="currentPath && !props.selectionMode">
<v-col>
<v-breadcrumbs :items="breadcrumbItems" class="pa-0">
<template v-slot:item="{ item }">
<v-breadcrumbs-item
:to="item.to"
@click="navigateToFolder(item.path)"
@click="navigateToFolder((item as any).path)"
class="cursor-pointer"
>
{{ item.title }}
</v-breadcrumbs-item>
@@ -41,7 +53,7 @@
@update:model-value="filterFiles"
/>
</v-col>
<v-col cols="12" md="6" class="d-flex justify-end ga-2">
<v-col cols="12" md="6" class="d-flex justify-end ga-2" v-if="!props.selectionMode">
<v-btn
color="secondary"
size="large"
@@ -63,7 +75,7 @@
</v-row>
<!-- Bulk Actions Bar (shown when items selected) -->
<v-row v-if="selectedItems.length > 0" class="mb-4">
<v-row v-if="selectedItems.length > 0 && !props.selectionMode" class="mb-4">
<v-col>
<v-alert
type="info"
@@ -312,7 +324,7 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { ref, computed, onMounted, watch } from 'vue';
import FileUploader from '~/components/FileUploader.vue';
import FilePreviewModal from '~/components/FilePreviewModal.vue';
@@ -325,8 +337,20 @@ interface FileItem {
icon: string;
displayName: string;
isFolder: boolean;
path?: string;
}
interface Props {
selectionMode?: boolean;
}
interface Emits {
(e: 'file-selected', file: FileItem): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const toast = useToast();
// Data
@@ -383,22 +407,42 @@ const breadcrumbItems = computed(() => {
return items;
});
// Load files
const loadFiles = async () => {
// Load files with retry logic
const loadFiles = async (retryCount = 0) => {
loading.value = true;
try {
const response = await $fetch('/api/files/list', {
params: {
prefix: currentPath.value,
recursive: false,
}
},
timeout: 15000 // 15 second timeout
});
files.value = response.files;
filteredFiles.value = response.files;
} catch (error) {
toast.error('Failed to load files');
files.value = response.files || [];
filteredFiles.value = response.files || [];
} catch (error: any) {
console.error(`Failed to load files (attempt ${retryCount + 1}/3):`, error);
// Retry on certain errors
if (retryCount < 2 && (
error.message?.includes('Failed to fetch') ||
error.message?.includes('Network') ||
error.statusCode === 500 ||
error.statusCode === 503
)) {
console.log('Retrying file load...');
setTimeout(() => {
loadFiles(retryCount + 1);
}, (retryCount + 1) * 1000); // Exponential backoff
} else {
toast.error('Failed to load files. Please refresh the page.');
files.value = [];
filteredFiles.value = [];
}
} finally {
loading.value = false;
if (retryCount === 0 || retryCount === 2) {
loading.value = false;
}
}
};
@@ -417,6 +461,15 @@ const filterFiles = () => {
// Handle file/folder click
const handleFileClick = (item: FileItem) => {
if (props.selectionMode && !item.isFolder) {
// In selection mode, emit the file for attachment
emit('file-selected', {
...item,
path: item.name
});
return;
}
if (item.isFolder) {
navigateToFolder(item.name);
} else if (canPreview(item)) {