idph / index.html
NaderY's picture
Add 3 files
e9b9c5e verified
Raw
History Blame Contribute Delete
27.9 kB
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Workflow de Validation Multi-Niveaux</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.workflow-step {
position: relative;
}
.workflow-step:not(:last-child):after {
content: '';
position: absolute;
top: 50%;
right: -20px;
width: 40px;
height: 2px;
background-color: #d1d5db;
}
.workflow-connector {
height: 30px;
width: 2px;
background-color: #d1d5db;
margin: 0 auto;
}
.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.custom-scrollbar::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #555;
}
</style>
</head>
<body class="bg-gray-100 font-sans">
<div class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<div class="hidden md:flex md:flex-shrink-0">
<div class="flex flex-col w-64 bg-indigo-700 text-white">
<div class="flex items-center justify-center h-16 px-4 bg-indigo-800">
<div class="flex items-center">
<i class="fas fa-tasks text-xl mr-2"></i>
<span class="text-xl font-semibold">WorkflowPro</span>
</div>
</div>
<div class="flex flex-col flex-grow px-4 py-4 overflow-y-auto">
<div class="space-y-1">
<a href="#" class="flex items-center px-2 py-3 text-sm font-medium rounded-md bg-indigo-800 text-white">
<i class="fas fa-tachometer-alt mr-3"></i>
Tableau de bord
</a>
<a href="#" class="flex items-center px-2 py-3 text-sm font-medium rounded-md text-indigo-200 hover:text-white hover:bg-indigo-600">
<i class="fas fa-inbox mr-3"></i>
Mes demandes
</a>
<a href="#" class="flex items-center px-2 py-3 text-sm font-medium rounded-md text-indigo-200 hover:text-white hover:bg-indigo-600">
<i class="fas fa-check-circle mr-3"></i>
Validations
</a>
<a href="#" class="flex items-center px-2 py-3 text-sm font-medium rounded-md text-indigo-200 hover:text-white hover:bg-indigo-600">
<i class="fas fa-chart-bar mr-3"></i>
Statistiques
</a>
<a href="#" class="flex items-center px-2 py-3 text-sm font-medium rounded-md text-indigo-200 hover:text-white hover:bg-indigo-600">
<i class="fas fa-cog mr-3"></i>
Paramètres
</a>
</div>
<div class="mt-auto mb-4">
<div class="p-4 bg-indigo-800 rounded-lg">
<p class="text-sm text-indigo-200">Besoin d'aide?</p>
<a href="#" class="text-white font-medium hover:underline">Contactez le support</a>
</div>
</div>
</div>
</div>
</div>
<!-- Main content -->
<div class="flex flex-col flex-1 overflow-hidden">
<!-- Top navigation -->
<div class="flex items-center justify-between h-16 px-4 bg-white border-b border-gray-200">
<div class="flex items-center">
<button class="md:hidden text-gray-500 focus:outline-none">
<i class="fas fa-bars"></i>
</button>
<h1 class="ml-4 text-xl font-semibold text-gray-800">Tableau de bord</h1>
</div>
<div class="flex items-center space-x-4">
<button class="p-1 text-gray-500 rounded-full hover:bg-gray-100 focus:outline-none">
<i class="fas fa-bell"></i>
</button>
<div class="relative">
<button id="user-menu" class="flex items-center text-sm text-gray-700 rounded-full focus:outline-none">
<img class="w-8 h-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="User photo">
<span class="ml-2 hidden md:inline">Jean Dupont</span>
</button>
</div>
</div>
</div>
<!-- Main content area -->
<div class="flex-1 overflow-auto custom-scrollbar p-4 md:p-6">
<!-- Stats cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
<div class="bg-white rounded-lg shadow p-4">
<div class="flex items-center">
<div class="p-3 rounded-full bg-blue-100 text-blue-600">
<i class="fas fa-file-alt"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">Demandes en cours</p>
<p class="text-xl font-semibold text-gray-800">24</p>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div class="flex items-center">
<div class="p-3 rounded-full bg-green-100 text-green-600">
<i class="fas fa-check-circle"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">Demandes approuvées</p>
<p class="text-xl font-semibold text-gray-800">42</p>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div class="flex items-center">
<div class="p-3 rounded-full bg-red-100 text-red-600">
<i class="fas fa-times-circle"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">Demandes rejetées</p>
<p class="text-xl font-semibold text-gray-800">8</p>
</div>
</div>
</div>
<div class="bg-white rounded-lg shadow p-4">
<div class="flex items-center">
<div class="p-3 rounded-full bg-yellow-100 text-yellow-600">
<i class="fas fa-clock"></i>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-500">En attente de moi</p>
<p class="text-xl font-semibold text-gray-800">5</p>
</div>
</div>
</div>
</div>
<!-- Action buttons and recent requests -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Left column - Create request -->
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Créer une nouvelle demande</h3>
</div>
<div class="p-4">
<form id="request-form">
<div class="mb-4">
<label for="request-type" class="block text-sm font-medium text-gray-700 mb-1">Type de demande</label>
<select id="request-type" class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
<option value="">Sélectionnez un type</option>
<option value="conges">Demande de congés</option>
<option value="achat">Demande d'achat</option>
<option value="formation">Demande de formation</option>
<option value="remboursement">Demande de remboursement</option>
</select>
</div>
<div class="mb-4">
<label for="request-title" class="block text-sm font-medium text-gray-700 mb-1">Titre</label>
<input type="text" id="request-title" class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div class="mb-4">
<label for="request-description" class="block text-sm font-medium text-gray-700 mb-1">Description</label>
<textarea id="request-description" rows="3" class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"></textarea>
</div>
<div class="mb-4">
<label for="request-amount" class="block text-sm font-medium text-gray-700 mb-1">Montant (si applicable)</label>
<input type="number" id="request-amount" class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div class="mb-4">
<label for="request-date" class="block text-sm font-medium text-gray-700 mb-1">Date (si applicable)</label>
<input type="date" id="request-date" class="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div class="flex justify-end">
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Soumettre la demande
</button>
</div>
</form>
</div>
</div>
<!-- Middle column - Recent requests -->
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Mes demandes récentes</h3>
</div>
<div class="overflow-y-auto" style="max-height: 500px;">
<ul class="divide-y divide-gray-200" id="recent-requests">
<!-- Requests will be added here by JavaScript -->
</ul>
</div>
</div>
<!-- Right column - Approval workflow -->
<div class="bg-white rounded-lg shadow overflow-hidden">
<div class="px-4 py-5 sm:px-6 bg-gray-50 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Workflow de validation</h3>
</div>
<div class="p-4">
<div class="flex justify-between mb-6" id="workflow-steps">
<!-- Workflow steps will be added here by JavaScript -->
</div>
<div class="workflow-connector"></div>
<div class="flex justify-between mt-6" id="approvers-list">
<!-- Approvers will be added here by JavaScript -->
</div>
</div>
<div class="px-4 py-4 border-t border-gray-200">
<h4 class="text-md font-medium text-gray-900 mb-2">Détails de la demande</h4>
<div class="bg-gray-50 p-3 rounded-lg">
<p class="text-sm text-gray-600" id="workflow-details">Sélectionnez une demande pour voir son workflow de validation.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Sample data
const requests = [
{
id: 1,
type: 'conges',
title: 'Demande de congés annuels',
description: 'Je souhaite prendre 2 semaines de congés en août pour des vacances familiales.',
date: '2023-08-15',
status: 'pending',
created: '2023-06-10',
workflow: [
{ level: 1, approver: 'Responsable d\'équipe', status: 'approved', date: '2023-06-12' },
{ level: 2, approver: 'DRH', status: 'pending', date: null },
{ level: 3, approver: 'Directeur Général', status: 'pending', date: null }
]
},
{
id: 2,
type: 'achat',
title: 'Achat nouveau matériel',
description: 'Besoin d\'un nouvel ordinateur portable pour le nouveau collaborateur.',
amount: 1200,
status: 'approved',
created: '2023-05-28',
workflow: [
{ level: 1, approver: 'Responsable d\'équipe', status: 'approved', date: '2023-05-29' },
{ level: 2, approver: 'Responsable financier', status: 'approved', date: '2023-05-30' }
]
},
{
id: 3,
type: 'formation',
title: 'Formation React Avancé',
description: 'Participation à une formation React avancé de 3 jours pour améliorer mes compétences.',
amount: 800,
status: 'rejected',
created: '2023-06-05',
workflow: [
{ level: 1, approver: 'Responsable d\'équipe', status: 'approved', date: '2023-06-06' },
{ level: 2, approver: 'Responsable formation', status: 'rejected', date: '2023-06-07', comment: 'Budget formation épuisé pour ce trimestre.' }
]
},
{
id: 4,
type: 'remboursement',
title: 'Remboursement frais de transport',
description: 'Remboursement des frais de transport pour le déplacement client du 20 mai.',
amount: 145.50,
status: 'pending',
created: '2023-06-08',
workflow: [
{ level: 1, approver: 'Responsable d\'équipe', status: 'pending', date: null },
{ level: 2, approver: 'Comptabilité', status: 'pending', date: null }
]
}
];
// DOM elements
const recentRequestsList = document.getElementById('recent-requests');
const workflowSteps = document.getElementById('workflow-steps');
const approversList = document.getElementById('approvers-list');
const workflowDetails = document.getElementById('workflow-details');
const requestForm = document.getElementById('request-form');
// Render recent requests
function renderRecentRequests() {
recentRequestsList.innerHTML = '';
// Sort requests by creation date (newest first)
const sortedRequests = [...requests].sort((a, b) => new Date(b.created) - new Date(a.created));
sortedRequests.forEach(request => {
const listItem = document.createElement('li');
// Determine status color and icon
let statusColor, statusIcon;
if (request.status === 'approved') {
statusColor = 'bg-green-100 text-green-800';
statusIcon = 'fa-check-circle';
} else if (request.status === 'rejected') {
statusColor = 'bg-red-100 text-red-800';
statusIcon = 'fa-times-circle';
} else {
statusColor = 'bg-yellow-100 text-yellow-800';
statusIcon = 'fa-clock';
}
// Format date
const createdDate = new Date(request.created);
const formattedDate = createdDate.toLocaleDateString('fr-FR');
listItem.className = 'px-4 py-4 hover:bg-gray-50 cursor-pointer';
listItem.innerHTML = `
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="min-w-0">
<p class="text-sm font-medium text-indigo-600 truncate">${request.title}</p>
<p class="text-sm text-gray-500 truncate">${request.description.substring(0, 50)}...</p>
</div>
</div>
<div class="ml-2 flex-shrink-0 flex">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${statusColor}">
<i class="fas ${statusIcon} mr-1"></i>
${request.status === 'approved' ? 'Approuvé' : request.status === 'rejected' ? 'Rejeté' : 'En attente'}
</span>
</div>
</div>
<div class="mt-2 flex justify-between">
<div class="flex">
<p class="flex items-center text-sm text-gray-500">
<i class="fas fa-calendar-alt mr-1.5"></i>
${formattedDate}
</p>
</div>
<div class="flex">
<button class="text-sm text-indigo-600 hover:text-indigo-900 view-workflow" data-id="${request.id}">
Voir workflow
</button>
</div>
</div>
`;
recentRequestsList.appendChild(listItem);
});
// Add event listeners to view workflow buttons
document.querySelectorAll('.view-workflow').forEach(button => {
button.addEventListener('click', (e) => {
e.stopPropagation();
const requestId = parseInt(button.getAttribute('data-id'));
const request = requests.find(r => r.id === requestId);
renderWorkflow(request);
});
});
// Add click event to list items to show details
document.querySelectorAll('#recent-requests li').forEach((item, index) => {
item.addEventListener('click', () => {
renderWorkflow(sortedRequests[index]);
});
});
}
// Render workflow for a specific request
function renderWorkflow(request) {
// Clear previous workflow
workflowSteps.innerHTML = '';
approversList.innerHTML = '';
// Render workflow steps
request.workflow.forEach(step => {
const stepElement = document.createElement('div');
stepElement.className = 'workflow-step';
// Determine step status color and icon
let stepStatusColor, stepStatusIcon;
if (step.status === 'approved') {
stepStatusColor = 'bg-green-500';
stepStatusIcon = 'fa-check';
} else if (step.status === 'rejected') {
stepStatusColor = 'bg-red-500';
stepStatusIcon = 'fa-times';
} else {
stepStatusColor = 'bg-gray-300 animate-pulse';
stepStatusIcon = 'fa-clock';
}
stepElement.innerHTML = `
<div class="flex flex-col items-center">
<div class="w-10 h-10 rounded-full ${stepStatusColor} flex items-center justify-center text-white">
<i class="fas ${stepStatusIcon}"></i>
</div>
<p class="mt-2 text-xs font-medium text-gray-700">Étape ${step.level}</p>
</div>
`;
workflowSteps.appendChild(stepElement);
});
// Render approvers
request.workflow.forEach(step => {
const approverElement = document.createElement('div');
approverElement.className = 'text-center';
approverElement.innerHTML = `
<p class="text-sm font-medium text-gray-900">${step.approver}</p>
<p class="text-xs text-gray-500">
${step.date ? new Date(step.date).toLocaleDateString('fr-FR') : 'En attente'}
</p>
${step.comment ? `<p class="text-xs text-red-500 mt-1">${step.comment}</p>` : ''}
`;
approversList.appendChild(approverElement);
});
// Render request details
let detailsHtml = `
<p><strong>Type:</strong> ${getRequestTypeName(request.type)}</p>
<p><strong>Titre:</strong> ${request.title}</p>
<p><strong>Description:</strong> ${request.description}</p>
`;
if (request.amount) {
detailsHtml += `<p><strong>Montant:</strong> ${request.amount} €</p>`;
}
if (request.date) {
detailsHtml += `<p><strong>Date:</strong> ${new Date(request.date).toLocaleDateString('fr-FR')}</p>`;
}
detailsHtml += `<p><strong>Statut:</strong> ${request.status === 'approved' ? 'Approuvé' : request.status === 'rejected' ? 'Rejeté' : 'En attente'}</p>`;
workflowDetails.innerHTML = detailsHtml;
}
// Helper function to get request type name
function getRequestTypeName(type) {
const types = {
'conges': 'Demande de congés',
'achat': 'Demande d\'achat',
'formation': 'Demande de formation',
'remboursement': 'Demande de remboursement'
};
return types[type] || type;
}
// Form submission handler
requestForm.addEventListener('submit', (e) => {
e.preventDefault();
const type = document.getElementById('request-type').value;
const title = document.getElementById('request-title').value;
const description = document.getElementById('request-description').value;
const amount = document.getElementById('request-amount').value;
const date = document.getElementById('request-date').value;
if (!type || !title || !description) {
alert('Veuillez remplir tous les champs obligatoires.');
return;
}
// Create new request
const newRequest = {
id: requests.length + 1,
type,
title,
description,
status: 'pending',
created: new Date().toISOString().split('T')[0],
workflow: [
{ level: 1, approver: 'Responsable d\'équipe', status: 'pending', date: null },
{ level: 2, approver: type === 'conges' ? 'DRH' : 'Responsable financier', status: 'pending', date: null }
]
};
if (amount) newRequest.amount = parseFloat(amount);
if (date) newRequest.date = date;
requests.unshift(newRequest);
// Reset form
requestForm.reset();
// Show success message
alert('Votre demande a été soumise avec succès!');
// Refresh the list
renderRecentRequests();
// Show the workflow for the new request
renderWorkflow(newRequest);
});
// Initialize the app
document.addEventListener('DOMContentLoaded', () => {
renderRecentRequests();
// Show the first request's workflow by default
if (requests.length > 0) {
renderWorkflow(requests[0]);
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=NaderY/idph" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>