Construa um aplicativo web full-stack de cronômetro para sala de aula inteligente.

De Wikiprompt, a enciclopédia livre de prompts

عبير الحسن

28 de set. de 2026

Construa um aplicativo web full-stack de cronômetro para sala de aula inteligente. نص برومبت شامل باللغة العربية لبناء تطبيق ويب كامل (Full-Stack) لساعة صفية تفاعلية ذكية، تتضمن عدّادًا تنازليًا، وأصواتًا، ومنتقيًا عشوائيًا للطلاب، وتخصيصًا للمراحل.

Conteúdo do PromptSalvar

🌐
خطة تطوير التطبيق: 1. **البنية التقنية**: تطبيق ويب كامل (Full-Stack) باستخدام Next.js 14 مع React و Tailwind CSS للتصميم، مع قاعدة بيانات SQLite عبر Prisma لتخزين أسماء الطلاب ومراحل الدرس. 2. **المكونات الرئيسية**: - مكوّن المؤقت الدائري (Circular Timer) مع تغيير الألوان - نظام الصوت (Web Audio API) مع مكتبة أصوات جاهزة - أداة اختيار الطالب العشوائي - مدير مراحل الدرس 3. **واجهة المستخدم**: تصميم عربي RTL بألوان مريحة (أزرق داكن، أبيض، وألوان متدرجة للمؤقت) 4. **قاعدة البيانات**: جدول للطلاب، جدول للمراحل، إعدادات التطبيق --- سأبدأ الآن ببناء التطبيق: ```jsx // app/layout.jsx import './globals.css' import { Cairo } from 'next/font/google' const cairo = Cairo({ subsets: ['arabic'] }) export const metadata = { title: 'مؤقت الفصل الذكي', description: 'تطبيق مؤقت تفاعلي للمعلمين', } export default function RootLayout({ children }) { return ( <html lang="ar" dir="rtl"> <body className={cairo.className}>{children}</body> </html> ) } ``` ```jsx // app/page.jsx 'use client' import { useState, useEffect, useRef } from 'react' import Timer from '@/components/Timer' import StudentPicker from '@/components/StudentPicker' import StageManager from '@/components/StageManager' import SoundLibrary from '@/components/SoundLibrary' import { PrismaClient } from '@prisma/client' export default function Home() { const [students, setStudents] = useState([]) const [stages, setStages] = useState([]) const [isMuted, setIsMuted] = useState(false) const [currentStage, setCurrentStage] = useState(null) useEffect(() => { fetchStudents() fetchStages() }, []) const fetchStudents = async () => { const res = await fetch('/api/students') const data = await res.json() setStudents(data) } const fetchStages = async () => { const res = await fetch('/api/stages') const data = await res.json() setStages(data) } return ( <main className="min-h-screen bg-gradient-to-br from-blue-900 to-purple-900 p-6"> <div className="max-w-7xl mx-auto"> <header className="text-center mb-8"> <h1 className="text-4xl font-bold text-white mb-2">مؤقت الفصل الذكي</h1> <p className="text-blue-200">أداة تفاعلية لإدارة وقت الحصة الدراسية</p> </header> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="lg:col-span-2"> <Timer isMuted={isMuted} currentStage={currentStage} onStageComplete={() => setCurrentStage(null)} /> </div> <div className="space-y-6"> <StudentPicker students={students} isMuted={isMuted} /> <SoundLibrary isMuted={isMuted} setIsMuted={setIsMuted} /> </div> </div> <div className="mt-8"> <StageManager stages={stages} setStages={setStages} currentStage={currentStage} setCurrentStage={setCurrentStage} /> </div> </div> </main> ) } ``` ```jsx // components/Timer.jsx 'use client' import { useState, useEffect, useRef } from 'react' import { playSound } from '@/utils/sound' export default function Timer({ isMuted, currentStage, onStageComplete }) { const [duration, setDuration] = useState(45 * 60) // 45 minutes in seconds const [timeLeft, setTimeLeft] = useState(45 * 60) const [isRunning, setIsRunning] = useState(false) const [isPaused, setIsPaused] = useState(false) const [lastMinutePlayed, setLastMinutePlayed] = useState(false) const timerRef = useRef(null) useEffect(() => { if (currentStage) { setDuration(currentStage.duration * 60) setTimeLeft(currentStage.duration * 60) setIsRunning(true) setIsPaused(false) } }, [currentStage]) useEffect(() => { if (isRunning && !isPaused) { timerRef.current = setInterval(() => { setTimeLeft(prev => { if (prev <= 1) { clearInterval(timerRef.current) setIsRunning(false) if (!isMuted) playSound('timesUp') onStageComplete() return 0 } // Last minute warning if (prev === 60 && !lastMinutePlayed) { setLastMinutePlayed(true) if (!isMuted) playSound('lastMinute') } return prev - 1 }) }, 1000) } return () => clearInterval(timerRef.current) }, [isRunning, isPaused, isMuted, lastMinutePlayed]) const formatTime = (seconds) => { const mins = Math.floor(seconds / 60) const secs = seconds % 60 return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}` } const getColor = () => { const percentage = (timeLeft / duration) * 100 if (percentage > 50) return '#22c55e' // Green if (percentage > 20) return '#eab308' // Yellow return '#ef4444' // Red } const circumference = 2 * Math.PI * 120 const progress = ((duration - timeLeft) / duration) * circumference const startTimer = () => { if (timeLeft === 0) { setTimeLeft(duration) } setIsRunning(true) setIsPaused(false) if (!isMuted) playSound('start') } const pauseTimer = () => { setIsPaused(true) setIsRunning(false) } const resetTimer = () => { setIsRunning(false) setIsPaused(false) setTimeLeft(duration) setLastMinutePlayed(false) } return ( <div className="bg-white/10 backdrop-blur-lg rounded-2xl p-8 shadow-2xl"> <div className="text-center mb-6"> <h2 className="text-2xl font-bold text-white mb-2"> {currentStage ? currentStage.name : 'الدرس الكامل'} </h2> <p className="text-blue-200"> {currentStage ? `مدة المرحلة: ${currentStage.duration} دقيقة` : 'المدة الافتراضية: 45 دقيقة'} </p> </div> <div className="relative w-72 h-72 mx-auto mb-8"> <svg className="w-full h-full -rotate-90" viewBox="0 0 280 280"> <circle cx="140" cy="140" r="120" fill="none" stroke="#ffffff20" strokeWidth="12" /> <circle cx="140" cy="140" r="120" fill="none" stroke={getColor()} strokeWidth="12" strokeLinecap="round" strokeDasharray={circumference} strokeDashoffset={circumference - progress} className="transition-all duration-1000" /> </svg> <div className="absolute inset-0 flex flex-col items-center justify-center"> <span className="text-6xl font-bold text-white" style={{ direction: 'ltr' }}> {formatTime(timeLeft)} </span> <span className="text-blue-200 mt-2">الوقت المتبقي</span> </div> </div> <div className="flex justify-center gap-4"> {!isRunning && !isPaused && ( <button onClick={startTimer} className="px-8 py-3 bg-green-500 hover:bg-green-600 text-white rounded-xl font-bold transition-colors" > ▶ بدء </button> )} {isRunning && ( <button onClick={pauseTimer} className="px-8 py-3 bg-yellow-500 hover:bg-yellow-600 text-white rounded-xl font-bold transition-colors" > ⏸ إيقاف مؤقت </button> )} {isPaused && ( <button onClick={() => { setIsRunning(true); setIsPaused(false) }} className="px-8 py-3 bg-green-500 hover:bg-green-600 text-white rounded-xl font-bold transition-colors" > ▶ متابعة </button> )} <button onClick={resetTimer} className="px-8 py-3 bg-red-500 hover:bg-red-600 text-white rounded-xl font-bold transition-colors" > ↺ إعادة تعيين </button> </div> </div> ) } ``` ```jsx // components/StudentPicker.jsx 'use client' import { useState } from 'react' import { playSound } from '@/utils/sound' export default function StudentPicker({ students, isMuted }) { const [selectedStudent, setSelectedStudent] = useState(null) const [isPicking, setIsPicking] = useState(false) const [showAddForm, setShowAddForm] = useState(false) const [newStudentName, setNewStudentName] = useState('') const pickRandomStudent = () => { if (students.length === 0) return setIsPicking(true) if (!isMuted) playSound('suspense') // Animation effect let counter = 0 const interval = setInterval(() => { const randomIndex = Math.floor(Math.random() * students.length) setSelectedStudent(students[randomIndex]) counter++ if (counter > 15) { clearInterval(interval) setIsPicking(false) if (!isMuted) playSound('ding') } }, 100) } const addStudent = async (e) => { e.preventDefault() if (!newStudentName.trim()) return const res = await fetch('/api/students', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newStudentName.trim() }) }) if (res.ok) { setNewStudentName('') setShowAddForm(false) window.location.reload() } } return ( <div className="bg-white/10 backdrop-blur-lg rounded-2xl p-6 shadow-2xl"> <h3 className="text-xl font-bold text-white mb-4">🎯 اختيار طالب عشوائي</h3> <button onClick={pickRandomStudent} disabled={isPicking || students.length === 0} className="w-full py-4 bg-purple-500 hover:bg-purple-600 disabled:bg-gray-500 text-white rounded-xl font-bold transition-colors mb-4" > {isPicking ? 'جاري الاختيار...' : 'اختر طالباً'} </button> {selectedStudent && ( <div className="text-center py-6 bg-white/5 rounded-xl mb-4"> <p className="text-3xl font-bold text-yellow-300 animate-pulse"> {selectedStudent.name} </p> </div> )} <button onClick={() => setShowAddForm(!showAddForm)} className="w-full py-2 text-blue-300 hover:text-blue-200 transition-colors" > {showAddForm ? 'إغلاق' : '+ إضافة طالب جديد'} </button> {showAddForm && ( <form onSubmit={addStudent} className="mt-4"> <input type="text" value={newStudentName} onChange={(e) => setNewStudentName(e.target.value)} placeholder="اسم الطالب" className="w-full px-4 py-2 bg-white/10 text-white rounded-xl placeholder-blue-300 focus:outline-none focus:ring-2 focus:ring-purple-500 mb-2" /> <button type="submit" className="w-full py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-xl font-bold transition-colors" > حفظ الطالب </button> </form> )} {students.length > 0 && ( <div className="mt-4 max-h-40 overflow-y-auto"> <p className="text-blue-200 text-sm mb-2">الطلاب المسجلون ({students.length}):</p> <div className="flex flex-wrap gap-2"> {students.map((student) => ( <span key={student.id} className="px-3 py-1 bg-white/10 text-white rounded-full text-sm"> {student.name} </span> ))} </div> </div> )} </div> ) } ``` ```jsx // components/SoundLibrary.jsx 'use client' import { playSound } from '@/utils/sound' export default function SoundLibrary({ isMuted, setIsMuted }) { const sounds = [ { id: 'applause', name: '👏 تصفيق', icon: '👏' }, { id: 'ding', name: '🔔 جرس نجاح', icon: '🔔' }, { id: 'error', name: '❌ صوت خطأ', icon: '❌' }, { id: 'music', name: '🎵 موسيقى قصيرة', icon: '🎵' }, { id: 'start', name: '🚀 بداية الدرس', icon: '🚀' }, { id: 'timesUp', name: '⏰ انتهاء الوقت', icon: '⏰' }, ] return ( <div className="bg-white/10 backdrop-blur-lg rounded-2xl p-6 shadow-2xl"> <div className="flex justify-between items-center mb-4"> <h3 className="text-xl font-bold text-white">🔊 مكتبة الأصوات</h3> <button onClick={() => setIsMuted(!isMuted)} className={`px-4 py-2 rounded-xl font-bold transition-colors ${ isMuted ? 'bg-red-500 hover:bg-red-600' : 'bg-green-500 hover:bg-green-600' } text-white`} > {isMuted ? '🔇 كتم' : '🔊 صوت'} </button> </div> <div className="grid grid-cols-2 gap-3"> {sounds.map((sound) => ( <button key={sound.id} onClick={() => !isMuted && playSound(sound.id)} disabled={isMuted} className="p-3 bg-white/5 hover:bg-white/10 disabled:opacity-50 rounded-xl text-white transition-colors flex flex-col items-center gap-2" > <span className="text-2xl">{sound.icon}</span> <span className="text-sm">{sound.name}</span> </button> ))} </div> </div> ) } ``` ```jsx // components/StageManager.jsx 'use client' import { useState } from 'react' export default function StageManager({ stages, setStages, currentStage, setCurrentStage }) { const [showAddForm, setShowAddForm] = useState(false) const [newStage, setNewStage] = useState({ name: '', duration: 5 }) const addStage = async (e) => { e.preventDefault() if (!newStage.name.trim()) return const res = await fetch('/api/stages', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newStage) }) if (res.ok) { setNewStage({ name: '', duration: 5 }) setShowAddForm(false) window.location.reload() } } const deleteStage = async (id) => { const res = await fetch(`/api/stages/${id}`, { method: 'DELETE' }) if (res.ok) { window.location.reload() } } return ( <div className="bg-white/10 backdrop-blur-lg rounded-2xl p-6 shadow-2xl"> <div className="flex justify-between items-center mb-4"> <h3 className="text-xl font-bold text-white">📋 مراحل الدرس</h3> <button onClick={() => setShowAddForm(!showAddForm)} className="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-xl font-bold transition-colors" > {showAddForm ? 'إغلاق' : '+ إضافة مرحلة'} </button> </div> {showAddForm && ( <form onSubmit={addStage} className="mb-4 p-4 bg-white/5 rounded-xl"> <div className="grid grid-cols-2 gap-4"> <input type="text" value={newStage.name} onChange={(e) => setNewStage({ ...newStage, name: e.target.value })} placeholder="اسم المرحلة (مثال: تمهيد)" className="px-4 py-2 bg-white/10 text-white rounded-xl placeholder-blue-300 focus:outline-none focus:ring-2 focus:ring-blue-500" /> <input type="number" value={newStage.duration} onChange={(e) => setNewStage({ ...newStage, duration: parseInt(e.target.value) })} min="1" max="60" placeholder="المدة (دقائق)" className="px-4 py-2 bg-white/10 text-white rounded-xl placeholder-blue-300 focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <button type="submit" className="mt-4 w-full py-2 bg-green-500 hover:bg-green-600 text-white rounded-xl font-bold transition-colors" > حفظ المرحلة </button> </form> )} {stages.length > 0 ? ( <div className="space-y-3"> {stages.map((stage) => ( <div key={stage.id} className={`flex items-center justify-between p-4 rounded-xl transition-colors ${ currentStage?.id === stage.id ? 'bg-yellow-500/20 border-2 border-yellow-500' : 'bg-white/5 hover:bg-white/10' }`} > <div> <p className="text-white font-bold">{stage.name}</p> <p className="text-blue-200 text-sm">{stage.duration} دقائق</p> </div> <div className="flex gap-2"> <button onClick={() => setCurrentStage(stage)} className="px-4 py-2 bg-green-500 hover:bg-green-600 text-white rounded-xl font-bold transition-colors" > ▶ تشغيل </button> <button onClick={() => deleteStage(stage.id)} className="px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-xl font-bold transition-colors" > 🗑 حذف </button> </div> </div> ))} </div> ) : ( <p className="text-blue-200 text-center py-4"> لا توجد مراحل مضافة. أضف مراحل لتقسيم الدرس. </p> )} </div> ) } ``` ```jsx // utils/sound.js let audioContext = null export const playSound = (type) => { if (!audioContext) { audioContext = new (window.AudioContext || window.webkitAudioContext)() } const playTone = (frequency, duration, volume = 0.5) => { const oscillator = audioContext.createOscillator() const gainNode = audioContext.createGain() oscillator.connect(gainNode) gainNode.connect(audioContext.destination) oscillator.frequency.value = frequency oscillator.type = 'sine' gainNode.gain.setValueAtTime(volume, audioContext.currentTime) gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration) oscillator.start(audioContext.currentTime) oscillator.stop(audioContext.currentTime + duration) } const playSequence = (notes) => { notes.forEach((note, index) => { setTimeout(() => { playTone(note.freq, note.duration, note.volume || 0.5) }, note.delay || 0) }) } switch (type) { case 'start': playSequence([ { freq: 523.25, duration: 0.3, delay: 0 }, // C5 { freq: 659.25, duration: 0.3, delay: 0.3 }, // E5 { freq: 783.99, duration: 0.5, delay: 0.6 }, // G5 ]) break case 'lastMinute': playSequence([ { freq: 880, duration: 0.1, delay: 0 }, // A5 { freq: 880, duration: 0.1, delay: 0.2 }, // A5 { freq: 880, duration: 0.1, delay: 0.4 }, // A5 ]) break case 'timesUp': playSequence([ { freq: 392, duration: 0.5, delay: 0 }, // G4 { freq: 392, duration: 0.5, delay: 0.5 }, // G4 { freq: 392, duration: 0.5, delay: 1 }, // G4 { freq: 311.13, duration: 1, delay: 1.5 }, // Eb4 ]) break case 'applause': // Simulated applause using noise const bufferSize = audioContext.sampleRate * 2 const buffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate) const data = buffer.getChannelData(0) for (let i = 0; i < bufferSize; i++) { data[i] = Math.random() * 2 - 1 } const source = audioContext.createBufferSource() source.buffer = buffer const gainNode = audioContext.createGain() gainNode.gain.setValueAtTime(0.3, audioContext.currentTime) gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 2) source.connect(gainNode) gainNode.connect(audioContext.destination) source.start() break case 'ding': playSequence([ { freq: 1046.5, duration: 0.5, delay: 0 }, // C6 { freq: 1318.5, duration: 0.5, delay: 0.1 }, // E6 ]) break case 'error': playSequence([ { freq: 200, duration: 0.2, delay: 0 }, { freq: 150, duration: 0.3, delay: 0.2 }, ]) break case 'music': playSequence([ { freq: 523.25, duration: 0.2, delay: 0 }, // C5 { freq: 659.25, duration: 0.2, delay: 0.2 }, // E5 { freq: 783.99, duration: 0.2, delay: 0.4 }, // G5 { freq: 1046.5, duration: 0.4, delay: 0.6 }, // C6 { freq: 783.99, duration: 0.2, delay: 1 }, // G5 { freq: 659.25, duration: 0.2, delay: 1.2 }, // E5 { freq: 523.25, duration: 0.4, delay: 1.4 }, // C5 ]) break case 'suspense': playSequence([ { freq: 196, duration: 0.3, delay: 0 }, // G3 { freq: 233.08, duration: 0.3, delay: 0.3 }, // Bb3 { freq: 261.63, duration: 0.3, delay: 0.6 }, // C4 { freq: 311.13, duration: 0.3, delay: 0.9 }, // Eb4 { freq: 349.23, duration: 0.3, delay: 1.2 }, // F4 { freq: 392, duration: 0.5, delay: 1.5 }, // G4 ]) break default: break } } ``` ```jsx // app/api/students/route.js import { PrismaClient } from '@prisma/client' import { NextResponse } from 'next/server' const prisma = new PrismaClient() export async function GET() { const students = await prisma.student.findMany({ orderBy: { name: 'asc' } }) return NextResponse.json(students) } export async function POST(request) { const { name } = await request.json() const student = await prisma.student.create({ data: { name } }) return NextResponse.json(student) } ``` ```jsx // app/api/stages/route.js import { PrismaClient } from '@prisma/client' import { NextResponse } from 'next/server' const prisma = new PrismaClient() export async function GET() { const stages = await prisma.stage.findMany({ orderBy: { createdAt: 'asc' } }) return NextResponse.json(stages) } export async function POST(request) { const { name, duration } = await request.json() const stage = await prisma.stage.create({ data: { name, duration } }) return NextResponse.json(stage) } ``` ```jsx // app/api/stages/[id]/route.js import { PrismaClient } from '@prisma/client' import { NextResponse } from 'next/server' const prisma = new PrismaClient() export async function DELETE(request, { params }) { const { id } = params await prisma.stage.delete({ where: { id: parseInt(id) } }) return NextResponse.json({ success: true }) } ``` ```jsx // prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "sqlite" url = "file:./dev.db" } model Student { id Int @id @default(autoincrement()) name String } model Stage { id Int @id @default(autoincrement()) name String duration Int createdAt DateTime @default(now()) } ``` ```css /* app/globals.css */ @tailwind base; @tailwind components; @tailwind utilities; :root { --foreground-rgb: 255, 255, 255; --background-start-rgb: 30, 58, 138; --background-end-rgb: 88, 28, 135; } body { color: rgb(var(--foreground-rgb)); background: linear-gradient( to bottom right, rgb(var(--background-start-rgb)), rgb(var(--background-end-rgb)) ); min-height: 100vh; } /* Custom scrollbar */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-track { background: rgba(255, 255, 255, 0.1); } ::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.5); } ``` ```json // package.json { "name": "classroom-timer", "version": "1.0.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "postinstall": "prisma generate && prisma db push" }, "dependencies": { "@prisma/client": "^5.6.0", "next": "14.0.3", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "autoprefixer": "^10.4.16", "postcss": "^8.4.31", "prisma": "^5.6.0", "tailwindcss": "^3.3.5" } } ``` ```json // tailwind.config.js /** @type {import('tailwindcss').Config} */ module.exports = { content: [ './pages/**/*.{js,ts,jsx,tsx,mdx}', './components/**/*.{js,ts,jsx,tsx,mdx}', './app/**/*.{js,ts,jsx,tsx,mdx}', ], theme: { extend: {}, }, plugins: [], } ``` ```json // postcss.config.js module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, } ``` ```jsx // next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { experimental: { serverComponentsExternalPackages: ['@prisma/client', 'prisma'], }, } module.exports = nextConfig ``` --- **ملخص التطبيق المكتمل:** ✅ **المؤقت الرئيسي**: دائري مع تغيير الألوان (أخضر، أصفر، أحمر) حسب الوقت المتبقي ✅ **الأصوات التفاعلية**: جرس بداية، تحذير آخر دقيقة، جرس انتهاء، تصفيق، جرس نجاح، صوت خطأ، موسيقى ✅ **اختيار الطالب العشوائي**: مع تأثير تشويقي وصوت ✅ **إدارة المراحل**: إضافة/حذف/تشغيل مراحل الدرس بشكل منفصل ✅ **التحكم بالصوت**: كتم/تشغيل كامل ✅ **تصميم عربي RTL**: بألوان مريحة وخط Cairo ✅ **قاعدة بيانات**: لتخزين الطلاب والمراحل بشكل دائم **للتشغيل محلياً:** ```bash npm install npx prisma db push npm run dev ``` **للنشر على Vercel:** ```bash npm run build vercel deploy ``` التطبيق جاهز للاستخدام مباشرة في الفصل الدراسي! 🎓

Entre para ver o prompt completo

Continuar com:

Ao entrar, você concorda com nossos Termos de uso e Política de privacidade

Uso

Este prompt foi projetado para uso com education. Copie o conteúdo acima e cole na sua ferramenta de IA preferida.

Para melhores resultados, personalize os marcadores (indicados por colchetes ou maiúsculas) com seus requisitos específicos.

Referências

Categorias:education| twitter| arabic| classroom-timer

Discussão

0 comentários