feat: ajout de la gestion des jobs avec création, récupération, suppression et filtrage via l'API, incluant des entités, des composants Vue.js et des mises à jour de la documentation API
This commit is contained in:
parent
4d1d5b9f21
commit
fd2d3cd640
@@ -0,0 +1,124 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ApiJobRepository } from '../../infrastructure/api/ApiJobRepository';
|
||||
|
||||
const jobRepository = new ApiJobRepository();
|
||||
|
||||
export const useActivityStore = defineStore('activity', {
|
||||
state: () => ({
|
||||
jobs: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
filter: {
|
||||
status: ['pending', 'in_progress', 'failed'], // Par défaut, afficher les jobs actifs
|
||||
sortBy: 'createdAt',
|
||||
sortOrder: 'DESC'
|
||||
}
|
||||
}),
|
||||
|
||||
getters: {
|
||||
activeJobs: state => state.jobs.filter(job => job.isActive()),
|
||||
completedJobs: state => state.jobs.filter(job => job.isCompleted()),
|
||||
failedJobs: state => state.jobs.filter(job => job.hasError()),
|
||||
isLoading: state => state.loading,
|
||||
hasError: state => !!state.error
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* Charge la liste des jobs selon les filtres actuels
|
||||
*/
|
||||
async loadJobs() {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const options = {
|
||||
page: 1, // Page fixée à 1
|
||||
limit: 100, // Limite augmentée pour récupérer plus de jobs
|
||||
sortBy: this.filter.sortBy,
|
||||
sortOrder: this.filter.sortOrder,
|
||||
status: this.filter.status
|
||||
};
|
||||
|
||||
const jobCollection = await jobRepository.getJobs(options);
|
||||
this.jobs = jobCollection.items;
|
||||
} catch (error) {
|
||||
this.error = error.message;
|
||||
console.error('Error loading jobs:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Met à jour les filtres et recharge la liste
|
||||
* @param {Object} filter
|
||||
*/
|
||||
async updateFilter(filter) {
|
||||
this.filter = { ...this.filter, ...filter };
|
||||
await this.loadJobs();
|
||||
},
|
||||
|
||||
/**
|
||||
* Supprime un job par son ID
|
||||
* @param {string} id
|
||||
*/
|
||||
async deleteJob(id) {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
await jobRepository.deleteJob(id);
|
||||
// Supprimer le job de la liste locale
|
||||
this.jobs = this.jobs.filter(job => job.id !== id);
|
||||
} catch (error) {
|
||||
this.error = error.message;
|
||||
console.error('Error deleting job:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Supprime tous les jobs correspondant aux critères
|
||||
* @param {Object} criteria
|
||||
*/
|
||||
async deleteJobs(criteria = {}) {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
|
||||
try {
|
||||
const deleted = await jobRepository.deleteJobs(criteria);
|
||||
// Recharger la liste après suppression
|
||||
await this.loadJobs();
|
||||
return deleted;
|
||||
} catch (error) {
|
||||
this.error = error.message;
|
||||
console.error('Error deleting jobs:', error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Supprime tous les jobs terminés
|
||||
*/
|
||||
async deleteCompletedJobs() {
|
||||
return this.deleteJobs({ status: ['COMPLETED'] });
|
||||
},
|
||||
|
||||
/**
|
||||
* Supprime tous les jobs en erreur
|
||||
*/
|
||||
async deleteFailedJobs() {
|
||||
return this.deleteJobs({ status: ['ERROR'] });
|
||||
},
|
||||
|
||||
/**
|
||||
* Supprime tous les jobs
|
||||
*/
|
||||
async deleteAllJobs() {
|
||||
return this.deleteJobs({});
|
||||
}
|
||||
}
|
||||
});
|
||||
50
assets/vue/app/domain/activity/domain/entities/job.js
Normal file
50
assets/vue/app/domain/activity/domain/entities/job.js
Normal file
@@ -0,0 +1,50 @@
|
||||
export class Job {
|
||||
constructor({
|
||||
id,
|
||||
type,
|
||||
status,
|
||||
progress = 0,
|
||||
payload = {},
|
||||
result = null,
|
||||
error = null,
|
||||
createdAt = new Date().toISOString(),
|
||||
updatedAt = new Date().toISOString()
|
||||
}) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.status = status;
|
||||
this.progress = progress;
|
||||
this.payload = payload;
|
||||
this.result = result;
|
||||
this.error = error;
|
||||
this.createdAt = createdAt;
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
static create(data) {
|
||||
return new Job(data);
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return ['PENDING', 'IN_PROGRESS'].includes(this.status);
|
||||
}
|
||||
|
||||
hasError() {
|
||||
return this.status === 'ERROR';
|
||||
}
|
||||
|
||||
isCompleted() {
|
||||
return this.status === 'COMPLETED';
|
||||
}
|
||||
}
|
||||
|
||||
export class JobCollection {
|
||||
constructor(items, total, page, limit, hasNextPage, hasPreviousPage) {
|
||||
this.items = items.map(item => Job.create(item));
|
||||
this.total = total;
|
||||
this.page = page;
|
||||
this.limit = limit;
|
||||
this.hasNextPage = hasNextPage;
|
||||
this.hasPreviousPage = hasPreviousPage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
export class JobRepositoryInterface {
|
||||
/**
|
||||
* Récupère la liste des jobs
|
||||
* @param {Object} options Les options de filtrage et pagination
|
||||
* @param {number} options.page Numéro de la page
|
||||
* @param {number} options.limit Nombre d'éléments par page
|
||||
* @param {string} options.sortBy Champ pour le tri
|
||||
* @param {string} options.sortOrder Direction du tri ('ASC' ou 'DESC')
|
||||
* @param {Array<string>} options.status Liste des statuts à filtrer
|
||||
* @returns {Promise<JobCollection>} Collection de jobs
|
||||
*/
|
||||
async getJobs(options) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère un job par son ID
|
||||
* @param {string} id Identifiant du job
|
||||
* @returns {Promise<Job>} Job
|
||||
*/
|
||||
async getJobById(id) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un job
|
||||
* @param {string} id Identifiant du job
|
||||
* @returns {Promise<boolean>} Succès de l'opération
|
||||
*/
|
||||
async deleteJob(id) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime tous les jobs correspondant aux critères
|
||||
* @param {Object} criteria Critères de suppression
|
||||
* @returns {Promise<number>} Nombre de jobs supprimés
|
||||
*/
|
||||
async deleteJobs(criteria) {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { Job, JobCollection } from '../../domain/entities/job';
|
||||
import { JobRepositoryInterface } from '../../domain/repository/JobRepositoryInterface';
|
||||
|
||||
export class ApiJobRepository extends JobRepositoryInterface {
|
||||
/**
|
||||
* Récupère la liste des jobs
|
||||
* @param {Object} options Les options de filtrage et pagination
|
||||
* @param {number} options.page Numéro de la page
|
||||
* @param {number} options.limit Nombre d'éléments par page
|
||||
* @param {string} options.sortBy Champ pour le tri
|
||||
* @param {string} options.sortOrder Direction du tri ('ASC' ou 'DESC')
|
||||
* @param {Array<string>} options.status Liste des statuts à filtrer
|
||||
* @returns {Promise<JobCollection>} Collection de jobs
|
||||
*/
|
||||
async getJobs(options = {}) {
|
||||
const { page = 1, limit = 100, sortBy = 'createdAt', sortOrder = 'DESC', status = [] } = options;
|
||||
|
||||
try {
|
||||
let url = `/api/jobs?page=${page}&limit=${limit}&sortBy=${sortBy}&sortOrder=${sortOrder}`;
|
||||
|
||||
// Ajouter les filtres de statut s'ils sont fournis
|
||||
if (status && status.length > 0) {
|
||||
url += `&status=${status.join(',')}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch jobs');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
return new JobCollection(
|
||||
data.items || [],
|
||||
data.total || 0,
|
||||
data.page || 1,
|
||||
data.limit || limit,
|
||||
!!data.hasNextPage,
|
||||
!!data.hasPreviousPage
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère un job par son ID
|
||||
* @param {string} id Identifiant du job
|
||||
* @returns {Promise<Job>} Job
|
||||
*/
|
||||
async getJobById(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/jobs/${id}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch job');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return Job.create(data);
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un job
|
||||
* @param {string} id Identifiant du job
|
||||
* @returns {Promise<boolean>} Succès de l'opération
|
||||
*/
|
||||
async deleteJob(id) {
|
||||
try {
|
||||
const response = await fetch(`/api/jobs/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete job');
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime tous les jobs correspondant aux critères
|
||||
* @param {Object} criteria Critères de suppression
|
||||
* @returns {Promise<number>} Nombre de jobs supprimés
|
||||
*/
|
||||
async deleteJobs(criteria = {}) {
|
||||
try {
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
// Ajouter les critères à l'URL
|
||||
Object.entries(criteria).forEach(([key, value]) => {
|
||||
if (Array.isArray(value)) {
|
||||
queryParams.append(key, value.join(','));
|
||||
} else {
|
||||
queryParams.append(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
const response = await fetch(`/api/jobs?${queryParams.toString()}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete jobs');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.deleted || 0;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<tr
|
||||
class="border-b border-gray-200 hover:bg-gray-50 transition duration-150 ease-in-out"
|
||||
:class="{
|
||||
'bg-yellow-50': job.status === 'PENDING',
|
||||
'bg-blue-50': job.status === 'IN_PROGRESS',
|
||||
'bg-green-50': job.status === 'COMPLETED',
|
||||
'bg-red-50': job.status === 'ERROR'
|
||||
}">
|
||||
<td class="py-4 px-4 text-center">
|
||||
<input type="checkbox" class="form-checkbox h-5 w-5 text-green-600" />
|
||||
</td>
|
||||
<td class="py-4 px-4 font-medium">{{ job.type }}</td>
|
||||
<td class="py-4 px-4">
|
||||
<span
|
||||
class="px-2 py-1 text-xs rounded-full"
|
||||
:class="{
|
||||
'bg-yellow-100 text-yellow-800': job.status === 'pending',
|
||||
'bg-blue-100 text-blue-800': job.status === 'in_progress',
|
||||
'bg-green-100 text-green-800': job.status === 'completed',
|
||||
'bg-red-100 text-red-800': job.status === 'failed'
|
||||
}">
|
||||
{{ job.status }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="py-4 px-4">
|
||||
<div v-if="job.error" class="text-sm text-red-600">
|
||||
{{ job.error }}
|
||||
</div>
|
||||
<div v-else class="text-sm text-gray-600">
|
||||
{{ formatDate(job.createdAt) }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-4 px-4">
|
||||
<div v-if="job.status === 'IN_PROGRESS'" class="mt-2">
|
||||
<div class="relative bg-gray-200 rounded-full h-6 overflow-hidden">
|
||||
<div
|
||||
class="absolute top-0 left-0 h-full bg-green-400 transition-all duration-300 ease-out"
|
||||
:style="{ width: `${job.progress}%` }"></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-white">
|
||||
{{ job.progress }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="job.status === 'COMPLETED'" class="relative bg-gray-200 rounded-full h-6 overflow-hidden">
|
||||
<div
|
||||
class="absolute top-0 left-0 h-full bg-green-400 transition-all duration-300 ease-out"
|
||||
style="width: 100%"></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-white">
|
||||
100%
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="job.status === 'ERROR'" class="relative bg-gray-200 rounded-full h-6 overflow-hidden">
|
||||
<div
|
||||
class="absolute top-0 left-0 h-full bg-red-400 transition-all duration-300 ease-out"
|
||||
style="width: 100%"></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-white">
|
||||
Erreur
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="relative bg-gray-200 rounded-full h-6 overflow-hidden">
|
||||
<div
|
||||
class="absolute top-0 left-0 h-full bg-yellow-400 transition-all duration-300 ease-out"
|
||||
style="width: 0%"></div>
|
||||
<div class="absolute inset-0 flex items-center justify-center text-xs font-semibold text-gray-600">
|
||||
En attente
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-4 px-4">
|
||||
<button
|
||||
@click="onDelete"
|
||||
class="text-red-500 hover:text-red-700 transition duration-150 ease-in-out"
|
||||
title="Supprimer">
|
||||
<TrashIcon class="h-5 w-5" />
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits } from 'vue';
|
||||
import { TrashIcon } from '@heroicons/vue/24/outline';
|
||||
|
||||
const props = defineProps({
|
||||
job: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['delete']);
|
||||
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString();
|
||||
}
|
||||
|
||||
function onDelete() {
|
||||
emit('delete', props.job.id);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<div>
|
||||
<Toolbar :config="toolbarConfig" class="mb-6" />
|
||||
|
||||
<div v-if="activityStore.loading" class="flex justify-center py-8">
|
||||
<div class="animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-indigo-500"></div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="activityStore.error" class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6">
|
||||
<p>{{ activityStore.error }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="container mx-auto p-2">
|
||||
<div class="bg-white overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full bg-white">
|
||||
<thead>
|
||||
<tr class="bg-gray-200 text-gray-800">
|
||||
<th class="w-1/12 py-3 px-4 text-left">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form-checkbox h-5 w-5 text-green-600"
|
||||
@change="toggleSelectAll" />
|
||||
</th>
|
||||
<th class="w-2/12 py-3 px-4 text-left">Type</th>
|
||||
<th class="w-2/12 py-3 px-4 text-left">Statut</th>
|
||||
<th class="w-3/12 py-3 px-4 text-left">Informations</th>
|
||||
<th class="w-3/12 py-3 px-4 text-left">Progression</th>
|
||||
<th class="w-1/12 py-3 px-4 text-left">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="text-gray-700">
|
||||
<template v-if="activityStore.jobs.length === 0">
|
||||
<tr>
|
||||
<td colspan="6" class="py-4 px-4 text-center text-gray-500"
|
||||
>Aucune activité trouvée avec les filtres actuels.</td
|
||||
>
|
||||
</tr>
|
||||
</template>
|
||||
<template v-else>
|
||||
<JobItem
|
||||
v-for="job in activityStore.jobs"
|
||||
:key="job.id"
|
||||
:job="job"
|
||||
@delete="deleteJob" />
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, computed } from 'vue';
|
||||
import { useActivityStore } from '../../application/store/activityStore';
|
||||
import JobItem from '../components/JobItem.vue';
|
||||
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
|
||||
import { ArrowPathIcon, TrashIcon, FunnelIcon } from '@heroicons/vue/24/outline';
|
||||
|
||||
const activityStore = useActivityStore();
|
||||
const selectedAll = ref(false);
|
||||
|
||||
// Statuts disponibles pour le filtre
|
||||
const statusOptions = [
|
||||
{ value: ['PENDING', 'IN_PROGRESS'], label: 'Actifs' },
|
||||
{ value: ['PENDING', 'IN_PROGRESS', 'COMPLETED', 'ERROR'], label: 'Tous' },
|
||||
{ value: ['COMPLETED'], label: 'Terminés' },
|
||||
{ value: ['ERROR'], label: 'En erreur' }
|
||||
];
|
||||
|
||||
// Index du statut actif
|
||||
const activeStatusIndex = ref(0);
|
||||
|
||||
// Rendre toolbarConfig computed pour que le label du dropdown soit réactif
|
||||
const toolbarConfig = computed(() => ({
|
||||
leftSection: [
|
||||
{
|
||||
icon: FunnelIcon,
|
||||
type: 'dropdown',
|
||||
label: statusOptions[activeStatusIndex.value].label,
|
||||
active: false,
|
||||
items: statusOptions.map((option, index) => ({
|
||||
label: option.label,
|
||||
isSelected: index === activeStatusIndex.value,
|
||||
onClick: () => setStatusFilter(index)
|
||||
}))
|
||||
}
|
||||
],
|
||||
rightSection: [
|
||||
{
|
||||
icon: ArrowPathIcon,
|
||||
type: 'button',
|
||||
label: 'Rafraîchir',
|
||||
onClick: refreshJobs
|
||||
},
|
||||
{
|
||||
icon: TrashIcon,
|
||||
type: 'button',
|
||||
label: 'Supprimer',
|
||||
onClick: deleteVisibleJobs
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
onMounted(() => {
|
||||
loadJobs();
|
||||
});
|
||||
|
||||
function loadJobs() {
|
||||
activityStore.loadJobs();
|
||||
}
|
||||
|
||||
function refreshJobs() {
|
||||
loadJobs();
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
selectedAll.value = !selectedAll.value;
|
||||
// La logique pour sélectionner tous les jobs serait ajoutée ici
|
||||
}
|
||||
|
||||
function setStatusFilter(index) {
|
||||
if (index >= 0 && index < statusOptions.length) {
|
||||
activeStatusIndex.value = index;
|
||||
activityStore.updateFilter({ status: statusOptions[index].value });
|
||||
}
|
||||
}
|
||||
|
||||
function deleteJob(id) {
|
||||
if (confirm('Voulez-vous vraiment supprimer ce job ?')) {
|
||||
activityStore.deleteJob(id);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteVisibleJobs() {
|
||||
if (confirm('Voulez-vous vraiment supprimer tous les jobs affichés ?')) {
|
||||
activityStore.deleteJobs({ status: activityStore.filter.status });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,60 +1,85 @@
|
||||
<template>
|
||||
<div>
|
||||
<Toolbar :config="toolbarConfig" class="sticky top-16 z-10" />
|
||||
<div class="container mx-auto px-4">
|
||||
<MangaGrid :mangas="collection?.items || []" />
|
||||
<div v-if="isBackgroundLoading" class="fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
Mise à jour en cours...
|
||||
</div>
|
||||
<div>
|
||||
<Toolbar :config="toolbarConfig" class="sticky top-16 z-10" />
|
||||
<div class="container mx-auto px-4">
|
||||
<MangaGrid :mangas="collection?.items || []" />
|
||||
<div
|
||||
v-if="isBackgroundLoading"
|
||||
class="fixed bottom-4 right-4 bg-gray-800 text-white px-4 py-2 rounded-lg shadow-lg">
|
||||
Mise à jour en cours...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useMangaStore } from '../../application/store/mangaStore';
|
||||
import MangaGrid from '../components/MangaGrid.vue';
|
||||
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
MagnifyingGlassIcon,
|
||||
Cog6ToothIcon,
|
||||
EyeIcon,
|
||||
ArrowsUpDownIcon,
|
||||
FunnelIcon
|
||||
} from '@heroicons/vue/24/outline';
|
||||
import { onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useMangaStore } from '../../application/store/mangaStore';
|
||||
import MangaGrid from '../components/MangaGrid.vue';
|
||||
import Toolbar from '../../../../shared/components/ui/Toolbar.vue';
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
MagnifyingGlassIcon,
|
||||
Cog6ToothIcon,
|
||||
EyeIcon,
|
||||
ArrowsUpDownIcon,
|
||||
FunnelIcon
|
||||
} from '@heroicons/vue/24/outline';
|
||||
|
||||
const router = useRouter();
|
||||
const mangaStore = useMangaStore();
|
||||
const router = useRouter();
|
||||
const mangaStore = useMangaStore();
|
||||
|
||||
const {
|
||||
collection,
|
||||
loading,
|
||||
error,
|
||||
isBackgroundLoading
|
||||
} = storeToRefs(mangaStore);
|
||||
const { collection, loading, error, isBackgroundLoading } = storeToRefs(mangaStore);
|
||||
|
||||
onMounted(() => {
|
||||
mangaStore.loadCollection();
|
||||
});
|
||||
onMounted(() => {
|
||||
mangaStore.loadCollection();
|
||||
});
|
||||
|
||||
const toolbarConfig = {
|
||||
leftSection: [
|
||||
{
|
||||
icon: ArrowPathIcon,
|
||||
label: 'Refresh',
|
||||
onClick: () => mangaStore.refreshCollectionInBackground(),
|
||||
active: isBackgroundLoading
|
||||
},
|
||||
{ icon: MagnifyingGlassIcon, label: 'Search', onClick: () => {} }
|
||||
],
|
||||
rightSection: [
|
||||
{ icon: Cog6ToothIcon, onClick: () => {} },
|
||||
{ icon: EyeIcon, onClick: () => {} },
|
||||
{ icon: ArrowsUpDownIcon, onClick: () => {} },
|
||||
{ icon: FunnelIcon, onClick: () => {} }
|
||||
]
|
||||
};
|
||||
const toolbarConfig = {
|
||||
leftSection: [
|
||||
{
|
||||
icon: ArrowPathIcon,
|
||||
label: 'Refresh',
|
||||
type: 'button',
|
||||
onClick: () => mangaStore.refreshCollectionInBackground(),
|
||||
active: isBackgroundLoading
|
||||
},
|
||||
{ icon: MagnifyingGlassIcon, label: 'Search', type: 'button', onClick: () => {} }
|
||||
],
|
||||
rightSection: [
|
||||
{ icon: Cog6ToothIcon, type: 'button', onClick: () => {} },
|
||||
{
|
||||
icon: EyeIcon,
|
||||
type: 'dropdown',
|
||||
label: 'View',
|
||||
items: [
|
||||
{ label: 'List', onClick: () => {} },
|
||||
{ label: 'Grid', onClick: () => {} }
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: ArrowsUpDownIcon,
|
||||
type: 'dropdown',
|
||||
label: 'Sort',
|
||||
items: [
|
||||
{ label: 'Title', onClick: () => {} },
|
||||
{ label: 'Author', onClick: () => {} },
|
||||
{ label: 'Status', onClick: () => {} },
|
||||
{ label: 'Year', onClick: () => {} }
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: FunnelIcon,
|
||||
type: 'dropdown',
|
||||
label: 'Filter',
|
||||
items: [
|
||||
{ label: 'All', onClick: () => {} },
|
||||
{ label: 'Completed', onClick: () => {} },
|
||||
{ label: 'In Progress', onClick: () => {} }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user