ambamali / index.html
MORYDIABAGATE's picture
L'ajout, la modification et la suppression d’étudiants. L’affichage de la liste des étudiants avec possibilité de recherche par nom ou matricule. Chaque étudiant a : un identifiant, nom, prénom, date de naissance, sexe, filière, et niveau. Je travaille en local (localhost) avec [indiquer ici ta technologie préférée : PHP + MySQL / Python Flask + SQLite / Node.js + MongoDB / etc.]. Donne-moi : La structure de la base de données. Le plan des fichiers du projet. Des exemples de code (backend + frontend). Comment exécuter l’application en local. Des conseils pour l’améliorer plus tard (authentification, pagination, export PDF...). - Initial Deployment
8a35789 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Student Management System</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>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.slide-enter-active, .slide-leave-active {
transition: all 0.3s ease;
}
.slide-enter, .slide-leave-to {
transform: translateX(20px);
opacity: 0;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8" id="app">
<header class="mb-8">
<h1 class="text-3xl font-bold text-indigo-700">Student Management System</h1>
<p class="text-gray-600">Manage your student records efficiently</p>
</header>
<div class="bg-white rounded-lg shadow-md p-6 mb-8">
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6 gap-4">
<div class="w-full md:w-auto">
<div class="relative">
<input
v-model="searchQuery"
@input="searchStudents"
type="text"
placeholder="Search by name or ID..."
class="w-full md:w-64 pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
>
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
</div>
<button
@click="showAddModal = true"
class="w-full md:w-auto bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition-colors"
>
<i class="fas fa-plus"></i>
Add Student
</button>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date of Birth</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Gender</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Program</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Level</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<transition-group name="fade" tag="tbody">
<tr v-for="student in filteredStudents" :key="student.id" class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ student.id }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ student.lastName }}, {{ student.firstName }}
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ formatDate(student.dob) }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span :class="{'bg-blue-100 text-blue-800': student.gender === 'Male', 'bg-pink-100 text-pink-800': student.gender === 'Female'}" class="px-2 py-1 rounded-full text-xs">
{{ student.gender }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ student.program }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<span class="px-2 py-1 bg-indigo-100 text-indigo-800 rounded-full text-xs">
Level {{ student.level }}
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<button @click="editStudent(student)" class="text-indigo-600 hover:text-indigo-900 mr-3">
<i class="fas fa-edit"></i>
</button>
<button @click="confirmDelete(student)" class="text-red-600 hover:text-red-900">
<i class="fas fa-trash"></i>
</button>
</td>
</tr>
</transition-group>
<tr v-if="filteredStudents.length === 0">
<td colspan="7" class="px-6 py-4 text-center text-sm text-gray-500">
No students found. Add some students to get started!
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Add/Edit Student Modal -->
<transition name="fade">
<div v-if="showAddModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900">
{{ isEditing ? 'Edit Student' : 'Add New Student' }}
</h3>
<button @click="closeModal" class="text-gray-400 hover:text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<form @submit.prevent="isEditing ? updateStudent() : addStudent()">
<div class="space-y-4">
<div>
<label for="firstName" class="block text-sm font-medium text-gray-700">First Name</label>
<input
type="text"
id="firstName"
v-model="currentStudent.firstName"
required
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
>
</div>
<div>
<label for="lastName" class="block text-sm font-medium text-gray-700">Last Name</label>
<input
type="text"
id="lastName"
v-model="currentStudent.lastName"
required
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
>
</div>
<div>
<label for="dob" class="block text-sm font-medium text-gray-700">Date of Birth</label>
<input
type="date"
id="dob"
v-model="currentStudent.dob"
required
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
>
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Gender</label>
<div class="mt-1 flex gap-4">
<label class="inline-flex items-center">
<input
type="radio"
v-model="currentStudent.gender"
value="Male"
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300"
>
<span class="ml-2 text-sm text-gray-700">Male</span>
</label>
<label class="inline-flex items-center">
<input
type="radio"
v-model="currentStudent.gender"
value="Female"
class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300"
>
<span class="ml-2 text-sm text-gray-700">Female</span>
</label>
</div>
</div>
<div>
<label for="program" class="block text-sm font-medium text-gray-700">Program</label>
<select
id="program"
v-model="currentStudent.program"
required
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
>
<option value="">Select a program</option>
<option value="Computer Science">Computer Science</option>
<option value="Engineering">Engineering</option>
<option value="Business">Business</option>
<option value="Medicine">Medicine</option>
<option value="Law">Law</option>
<option value="Arts">Arts</option>
</select>
</div>
<div>
<label for="level" class="block text-sm font-medium text-gray-700">Level</label>
<select
id="level"
v-model="currentStudent.level"
required
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
>
<option value="">Select level</option>
<option value="1">Level 1</option>
<option value="2">Level 2</option>
<option value="3">Level 3</option>
<option value="4">Level 4</option>
<option value="5">Level 5</option>
</select>
</div>
</div>
<div class="mt-6 flex justify-end gap-3">
<button
type="button"
@click="closeModal"
class="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Cancel
</button>
<button
type="submit"
class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
{{ isEditing ? 'Update' : 'Add' }}
</button>
</div>
</form>
</div>
</div>
</div>
</transition>
<!-- Delete Confirmation Modal -->
<transition name="fade">
<div v-if="showDeleteModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div class="bg-white rounded-lg shadow-xl w-full max-w-md">
<div class="p-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium text-gray-900">Confirm Deletion</h3>
<button @click="showDeleteModal = false" class="text-gray-400 hover:text-gray-500">
<i class="fas fa-times"></i>
</button>
</div>
<p class="text-sm text-gray-500 mb-6">
Are you sure you want to delete {{ studentToDelete.firstName }} {{ studentToDelete.lastName }}? This action cannot be undone.
</p>
<div class="flex justify-end gap-3">
<button
@click="showDeleteModal = false"
class="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Cancel
</button>
<button
@click="deleteStudent"
class="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
>
Delete
</button>
</div>
</div>
</div>
</div>
</transition>
<!-- Toast Notification -->
<transition name="slide">
<div v-if="showToast" class="fixed bottom-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg flex items-center gap-2">
<i class="fas fa-check-circle"></i>
<span>{{ toastMessage }}</span>
</div>
</transition>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@3.2.47/dist/vue.global.min.js"></script>
<script>
const { createApp, ref, computed, onMounted } = Vue;
createApp({
setup() {
// Data properties
const students = ref([]);
const searchQuery = ref('');
const filteredStudents = ref([]);
const showAddModal = ref(false);
const showDeleteModal = ref(false);
const isEditing = ref(false);
const showToast = ref(false);
const toastMessage = ref('');
const currentStudent = ref({
id: '',
firstName: '',
lastName: '',
dob: '',
gender: 'Male',
program: '',
level: ''
});
const studentToDelete = ref({
id: '',
firstName: '',
lastName: ''
});
// Methods
const loadStudents = () => {
const savedStudents = localStorage.getItem('students');
if (savedStudents) {
students.value = JSON.parse(savedStudents);
filteredStudents.value = [...students.value];
} else {
// Sample data
students.value = [
{
id: 'ST001',
firstName: 'John',
lastName: 'Doe',
dob: '2000-05-15',
gender: 'Male',
program: 'Computer Science',
level: '3'
},
{
id: 'ST002',
firstName: 'Jane',
lastName: 'Smith',
dob: '1999-08-22',
gender: 'Female',
program: 'Business',
level: '2'
},
{
id: 'ST003',
firstName: 'Michael',
lastName: 'Johnson',
dob: '2001-03-10',
gender: 'Male',
program: 'Engineering',
level: '4'
}
];
filteredStudents.value = [...students.value];
saveStudents();
}
};
const saveStudents = () => {
localStorage.setItem('students', JSON.stringify(students.value));
};
const generateStudentId = () => {
const lastId = students.value.length > 0
? parseInt(students.value[students.value.length - 1].id.substring(2))
: 0;
return `ST${(lastId + 1).toString().padStart(3, '0')}`;
};
const searchStudents = () => {
if (!searchQuery.value) {
filteredStudents.value = [...students.value];
return;
}
const query = searchQuery.value.toLowerCase();
filteredStudents.value = students.value.filter(student =>
student.firstName.toLowerCase().includes(query) ||
student.lastName.toLowerCase().includes(query) ||
student.id.toLowerCase().includes(query)
);
};
const addStudent = () => {
const newStudent = {
...currentStudent.value,
id: generateStudentId()
};
students.value.push(newStudent);
saveStudents();
filteredStudents.value = [...students.value];
closeModal();
showNotification('Student added successfully!');
};
const editStudent = (student) => {
currentStudent.value = { ...student };
isEditing.value = true;
showAddModal.value = true;
};
const updateStudent = () => {
const index = students.value.findIndex(s => s.id === currentStudent.value.id);
if (index !== -1) {
students.value[index] = { ...currentStudent.value };
saveStudents();
filteredStudents.value = [...students.value];
closeModal();
showNotification('Student updated successfully!');
}
};
const confirmDelete = (student) => {
studentToDelete.value = { ...student };
showDeleteModal.value = true;
};
const deleteStudent = () => {
students.value = students.value.filter(s => s.id !== studentToDelete.value.id);
saveStudents();
filteredStudents.value = [...students.value];
showDeleteModal.value = false;
showNotification('Student deleted successfully!');
};
const closeModal = () => {
showAddModal.value = false;
showDeleteModal.value = false;
isEditing.value = false;
currentStudent.value = {
id: '',
firstName: '',
lastName: '',
dob: '',
gender: 'Male',
program: '',
level: ''
};
};
const showNotification = (message) => {
toastMessage.value = message;
showToast.value = true;
setTimeout(() => {
showToast.value = false;
}, 3000);
};
const formatDate = (dateString) => {
const options = { year: 'numeric', month: 'short', day: 'numeric' };
return new Date(dateString).toLocaleDateString(undefined, options);
};
// Lifecycle hooks
onMounted(() => {
loadStudents();
});
return {
students,
searchQuery,
filteredStudents,
showAddModal,
showDeleteModal,
isEditing,
showToast,
toastMessage,
currentStudent,
studentToDelete,
loadStudents,
searchStudents,
addStudent,
editStudent,
updateStudent,
confirmDelete,
deleteStudent,
closeModal,
formatDate
};
}
}).mount('#app');
</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=MORYDIABAGATE/ambamali" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>