tiffank1802 commited on
Commit
7cc7aef
·
0 Parent(s):

Initial commit: site cours Énergétique - Thermique

Browse files
.env.example ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Variables d'environnement Appwrite
2
+ VITE_APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1
3
+ VITE_APPWRITE_PROJECT_ID=your_project_id
4
+ VITE_APPWRITE_DATABASE_ID=your_database_id
5
+ VITE_APPWRITE_MODULES_COLLECTION_ID=your_modules_collection_id
6
+ VITE_APPWRITE_SECTIONS_COLLECTION_ID=your_sections_collection_id
7
+ VITE_APPWRITE_RESOURCES_COLLECTION_ID=your_resources_collection_id
.github/workflows/deploy.yml ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Deploy to GitHub Pages
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ pages: write
11
+ id-token: write
12
+
13
+ concurrency:
14
+ group: "pages"
15
+ cancel-in-progress: false
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v4
23
+
24
+ - name: Setup Node.js
25
+ uses: actions/setup-node@v4
26
+ with:
27
+ node-version: 20
28
+ cache: 'npm'
29
+
30
+ - name: Install dependencies
31
+ run: npm ci
32
+
33
+ - name: Build
34
+ run: npm run build
35
+ env:
36
+ VITE_APPWRITE_ENDPOINT: ${{ secrets.VITE_APPWRITE_ENDPOINT }}
37
+ VITE_APPWRITE_PROJECT_ID: ${{ secrets.VITE_APPWRITE_PROJECT_ID }}
38
+ VITE_APPWRITE_DATABASE_ID: ${{ secrets.VITE_APPWRITE_DATABASE_ID }}
39
+ VITE_APPWRITE_MODULES_COLLECTION_ID: ${{ secrets.VITE_APPWRITE_MODULES_COLLECTION_ID }}
40
+ VITE_APPWRITE_SECTIONS_COLLECTION_ID: ${{ secrets.VITE_APPWRITE_SECTIONS_COLLECTION_ID }}
41
+ VITE_APPWRITE_RESOURCES_COLLECTION_ID: ${{ secrets.VITE_APPWRITE_RESOURCES_COLLECTION_ID }}
42
+
43
+ - name: Upload artifact
44
+ uses: actions/upload-pages-artifact@v3
45
+ with:
46
+ path: dist
47
+
48
+ deploy:
49
+ environment:
50
+ name: github-pages
51
+ url: ${{ steps.deployment.outputs.page_url }}
52
+ runs-on: ubuntu-latest
53
+ needs: build
54
+ steps:
55
+ - name: Deploy to GitHub Pages
56
+ id: deployment
57
+ uses: actions/deploy-pages@v4
README.md ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Site du cours Énergétique - Thermique
2
+
3
+ Site React déployé sur GitHub Pages avec Appwrite comme backend.
4
+
5
+ ## Configuration Appwrite
6
+
7
+ ### 1. Créer le projet
8
+
9
+ - Se connecter à [Appwrite Cloud](https://cloud.appwrite.io/)
10
+ - Créer un nouveau projet
11
+
12
+ ### 2. Créer la base de données
13
+
14
+ - Aller dans **Databases** → **Create Database**
15
+ - Nommer la base `cours_enise`
16
+
17
+ ### 3. Collections
18
+
19
+ Créer les 3 collections suivantes :
20
+
21
+ #### `modules`
22
+ | Attribut | Type |
23
+ |----------|------|
24
+ | title | string |
25
+ | code | string |
26
+ | description | string |
27
+ | year | integer |
28
+ | semester | string |
29
+
30
+ #### `sections`
31
+ | Attribut | Type |
32
+ |----------|------|
33
+ | moduleId | string |
34
+ | title | string |
35
+ | order | integer |
36
+
37
+ #### `resources`
38
+ | Attribut | Type |
39
+ |----------|------|
40
+ | moduleId | string |
41
+ | sectionId | string |
42
+ | title | string |
43
+ | type | string |
44
+ | url | string |
45
+ | description | string |
46
+ | order | integer |
47
+
48
+ ### 4. Permissions
49
+
50
+ Pour chaque collection :
51
+ - Ouvrir l'onglet **Settings** → **Permissions**
52
+ - Ajouter le rôle `any` avec permission `read`
53
+
54
+ ### 5. Données示例
55
+
56
+ **Module :**
57
+ - title: `Énergétique – Thermique`
58
+ - code: `4A-S7-ET`
59
+ - year: `4`
60
+ - semester: `S7`
61
+
62
+ **Sections :**
63
+ - Cours (order: 1)
64
+ - TD (order: 2)
65
+ - TP (order: 3)
66
+
67
+ **Resources :**
68
+ - Pointer vers PDF stockés dans Appwrite Storage ou Google Drive
69
+
70
+ ## Variables d'environnement
71
+
72
+ Copier `.env.example` vers `.env` et remplir :
73
+
74
+ ```bash
75
+ cp .env.example .env
76
+ ```
77
+
78
+ ## Installation
79
+
80
+ ```bash
81
+ npm install
82
+ npm run dev
83
+ ```
84
+
85
+ ## Déploiement
86
+
87
+ Le projet est configuré avec GitHub Actions. Pousser sur `main` déclenche le déploiement.
88
+
89
+ ## Structure du projet
90
+
91
+ ```
92
+ src/
93
+ ├── appwrite.js # Configuration Appwrite
94
+ ├── App.jsx # Point d'entrée
95
+ ├── main.jsx # Mount React
96
+ ├── index.css # Styles
97
+ └── components/
98
+ ├── ModulePage.jsx # Page principale du module
99
+ ├── SectionList.jsx # Navigation par section
100
+ └── ResourceList.jsx # Liste des ressources
101
+ ```
index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="fr">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Énergétique - Thermique</title>
7
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
package.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "enise-site-2",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "appwrite": "^14.0.0",
13
+ "react": "^18.2.0",
14
+ "react-dom": "^18.2.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/react": "^18.2.0",
18
+ "@types/react-dom": "^18.2.0",
19
+ "@vitejs/plugin-react": "^4.2.0",
20
+ "vite": "^5.0.0"
21
+ }
22
+ }
src/App.jsx ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ModulePage from './components/ModulePage';
2
+
3
+ function App() {
4
+ const moduleCode = '4A-S7-ET';
5
+
6
+ return (
7
+ <div className="app">
8
+ <ModulePage moduleCode={moduleCode} />
9
+ </div>
10
+ );
11
+ }
12
+
13
+ export default App;
src/appwrite.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Client, Databases, Query } from 'appwrite';
2
+
3
+ const client = new Client()
4
+ .setEndpoint(import.meta.env.VITE_APPWRITE_ENDPOINT)
5
+ .setProject(import.meta.env.VITE_APPWRITE_PROJECT_ID);
6
+
7
+ const databases = new Databases(client);
8
+
9
+ export { client, databases, Query };
10
+
11
+ export const CONFIG = {
12
+ databaseId: import.meta.env.VITE_APPWRITE_DATABASE_ID,
13
+ modulesCollectionId: import.meta.env.VITE_APPWRITE_MODULES_COLLECTION_ID,
14
+ sectionsCollectionId: import.meta.env.VITE_APPWRITE_SECTIONS_COLLECTION_ID,
15
+ resourcesCollectionId: import.meta.env.VITE_APPWRITE_RESOURCES_COLLECTION_ID,
16
+ };
src/components/ModulePage.jsx ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useState } from 'react';
2
+ import { databases, Query, CONFIG } from '../appwrite';
3
+ import SectionList from './SectionList';
4
+
5
+ export default function ModulePage({ moduleCode }) {
6
+ const [module, setModule] = useState(null);
7
+ const [sections, setSections] = useState([]);
8
+ const [loading, setLoading] = useState(true);
9
+
10
+ const fetchModuleAndSections = async () => {
11
+ try {
12
+ const modulesRes = await databases.listDocuments(
13
+ CONFIG.databaseId,
14
+ CONFIG.modulesCollectionId,
15
+ [Query.equal('code', moduleCode)]
16
+ );
17
+
18
+ if (modulesRes.total === 0) {
19
+ setLoading(false);
20
+ return;
21
+ }
22
+
23
+ const mod = modulesRes.documents[0];
24
+ setModule(mod);
25
+
26
+ const sectionsRes = await databases.listDocuments(
27
+ CONFIG.databaseId,
28
+ CONFIG.sectionsCollectionId,
29
+ [Query.equal('moduleId', mod.$id), Query.orderAsc('order')]
30
+ );
31
+
32
+ setSections(sectionsRes.documents);
33
+ } catch (err) {
34
+ console.error('Erreur fetchModuleAndSections:', err);
35
+ } finally {
36
+ setLoading(false);
37
+ }
38
+ };
39
+
40
+ useEffect(() => {
41
+ fetchModuleAndSections();
42
+ }, [moduleCode]);
43
+
44
+ if (loading) return <div className="loading">Chargement...</div>;
45
+ if (!module) return <div className="error">Module introuvable.</div>;
46
+
47
+ return (
48
+ <div className="module-page">
49
+ <header className="module-header">
50
+ <h1>{module.title}</h1>
51
+ <p className="module-meta">{module.year}A - {module.semester}</p>
52
+ <p className="module-description">{module.description}</p>
53
+ </header>
54
+ <SectionList module={module} sections={sections} />
55
+ </div>
56
+ );
57
+ }
src/components/ResourceList.jsx ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useState } from 'react';
2
+ import { databases, Query, CONFIG } from '../appwrite';
3
+
4
+ export default function ResourceList({ moduleId, sectionId, sectionTitle }) {
5
+ const [resources, setResources] = useState([]);
6
+ const [loading, setLoading] = useState(true);
7
+
8
+ const fetchResources = async () => {
9
+ try {
10
+ const res = await databases.listDocuments(
11
+ CONFIG.databaseId,
12
+ CONFIG.resourcesCollectionId,
13
+ [
14
+ Query.equal('moduleId', moduleId),
15
+ Query.equal('sectionId', sectionId),
16
+ Query.orderAsc('order'),
17
+ ]
18
+ );
19
+ setResources(res.documents);
20
+ } catch (err) {
21
+ console.error('Erreur fetchResources:', err);
22
+ } finally {
23
+ setLoading(false);
24
+ }
25
+ };
26
+
27
+ useEffect(() => {
28
+ setLoading(true);
29
+ fetchResources();
30
+ }, [moduleId, sectionId]);
31
+
32
+ if (loading) return <div className="loading">Chargement des ressources...</div>;
33
+
34
+ return (
35
+ <div className="resources-container">
36
+ <h2>{sectionTitle}</h2>
37
+ {resources.length === 0 ? (
38
+ <p className="no-resources">Aucune ressource pour cette section.</p>
39
+ ) : (
40
+ <ul className="resources-list">
41
+ {resources.map((r) => (
42
+ <li key={r.$id} className="resource-item">
43
+ <a
44
+ href={r.url}
45
+ target="_blank"
46
+ rel="noopener noreferrer"
47
+ className="resource-link"
48
+ >
49
+ <span className="resource-icon">{getIcon(r.type)}</span>
50
+ <span className="resource-title">{r.title}</span>
51
+ </a>
52
+ {r.description && (
53
+ <p className="resource-description">{r.description}</p>
54
+ )}
55
+ </li>
56
+ ))}
57
+ </ul>
58
+ )}
59
+ </div>
60
+ );
61
+ }
62
+
63
+ function getIcon(type) {
64
+ switch (type) {
65
+ case 'pdf': return '📄';
66
+ case 'video': return '🎬';
67
+ case 'link': return '🔗';
68
+ case 'image': return '🖼️';
69
+ default: return '📎';
70
+ }
71
+ }
src/components/SectionList.jsx ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+ import ResourceList from './ResourceList';
3
+
4
+ export default function SectionList({ module, sections }) {
5
+ const [activeSectionId, setActiveSectionId] = useState(
6
+ sections[0]?.$id || null
7
+ );
8
+
9
+ const activeSection = sections.find(s => s.$id === activeSectionId);
10
+
11
+ return (
12
+ <div className="section-layout">
13
+ <nav className="section-nav">
14
+ <h3>Contents</h3>
15
+ <ul className="nav-list">
16
+ {sections.map((sec) => (
17
+ <li key={sec.$id}>
18
+ <button
19
+ className={`nav-button ${sec.$id === activeSectionId ? 'active' : ''}`}
20
+ onClick={() => setActiveSectionId(sec.$id)}
21
+ >
22
+ {sec.title}
23
+ </button>
24
+ </li>
25
+ ))}
26
+ </ul>
27
+ </nav>
28
+ <main className="section-content">
29
+ {activeSectionId && activeSection && (
30
+ <ResourceList
31
+ moduleId={module.$id}
32
+ sectionId={activeSectionId}
33
+ sectionTitle={activeSection.title}
34
+ />
35
+ )}
36
+ </main>
37
+ </div>
38
+ );
39
+ }
src/index.css ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ font-family: 'Google Sans', Roboto, Arial, sans-serif;
9
+ background-color: #f8f9fa;
10
+ color: #202124;
11
+ line-height: 1.6;
12
+ }
13
+
14
+ .app {
15
+ min-height: 100vh;
16
+ }
17
+
18
+ .module-page {
19
+ max-width: 1100px;
20
+ margin: 0 auto;
21
+ padding: 40px 24px;
22
+ }
23
+
24
+ .module-header {
25
+ margin-bottom: 40px;
26
+ padding-bottom: 24px;
27
+ border-bottom: 1px solid #e0e0e0;
28
+ }
29
+
30
+ .module-header h1 {
31
+ font-size: 2.5rem;
32
+ font-weight: 400;
33
+ color: #202124;
34
+ margin-bottom: 8px;
35
+ }
36
+
37
+ .module-meta {
38
+ font-size: 1rem;
39
+ color: #5f6368;
40
+ margin-bottom: 16px;
41
+ }
42
+
43
+ .module-description {
44
+ font-size: 1.1rem;
45
+ color: #3c4043;
46
+ max-width: 800px;
47
+ }
48
+
49
+ .section-layout {
50
+ display: flex;
51
+ gap: 32px;
52
+ }
53
+
54
+ .section-nav {
55
+ width: 240px;
56
+ flex-shrink: 0;
57
+ }
58
+
59
+ .section-nav h3 {
60
+ font-size: 0.75rem;
61
+ font-weight: 500;
62
+ text-transform: uppercase;
63
+ letter-spacing: 0.5px;
64
+ color: #5f6368;
65
+ margin-bottom: 12px;
66
+ padding-left: 12px;
67
+ }
68
+
69
+ .nav-list {
70
+ list-style: none;
71
+ }
72
+
73
+ .nav-button {
74
+ width: 100%;
75
+ text-align: left;
76
+ padding: 10px 12px;
77
+ border: none;
78
+ background: transparent;
79
+ font-size: 0.95rem;
80
+ color: #3c4043;
81
+ cursor: pointer;
82
+ border-radius: 0 24px 24px 0;
83
+ transition: background-color 0.2s ease;
84
+ }
85
+
86
+ .nav-button:hover {
87
+ background-color: #f1f3f4;
88
+ }
89
+
90
+ .nav-button.active {
91
+ background-color: #e8f0fe;
92
+ color: #1a73e8;
93
+ font-weight: 500;
94
+ }
95
+
96
+ .section-content {
97
+ flex: 1;
98
+ min-width: 0;
99
+ }
100
+
101
+ .resources-container h2 {
102
+ font-size: 1.5rem;
103
+ font-weight: 400;
104
+ color: #202124;
105
+ margin-bottom: 24px;
106
+ }
107
+
108
+ .no-resources {
109
+ color: #5f6368;
110
+ font-style: italic;
111
+ }
112
+
113
+ .resources-list {
114
+ list-style: none;
115
+ }
116
+
117
+ .resource-item {
118
+ margin-bottom: 16px;
119
+ padding: 16px;
120
+ background: white;
121
+ border-radius: 8px;
122
+ box-shadow: 0 1px 3px rgba(60, 64, 67, 0.1);
123
+ transition: box-shadow 0.2s ease;
124
+ }
125
+
126
+ .resource-item:hover {
127
+ box-shadow: 0 2px 8px rgba(60, 64, 67, 0.15);
128
+ }
129
+
130
+ .resource-link {
131
+ display: inline-flex;
132
+ align-items: center;
133
+ gap: 12px;
134
+ text-decoration: none;
135
+ color: #1a73e8;
136
+ font-weight: 500;
137
+ font-size: 1rem;
138
+ }
139
+
140
+ .resource-link:hover {
141
+ text-decoration: underline;
142
+ }
143
+
144
+ .resource-icon {
145
+ font-size: 1.25rem;
146
+ }
147
+
148
+ .resource-description {
149
+ margin-top: 8px;
150
+ margin-left: 36px;
151
+ font-size: 0.9rem;
152
+ color: #5f6368;
153
+ }
154
+
155
+ .loading {
156
+ display: flex;
157
+ justify-content: center;
158
+ align-items: center;
159
+ min-height: 200px;
160
+ color: #5f6368;
161
+ font-size: 1rem;
162
+ }
163
+
164
+ .error {
165
+ text-align: center;
166
+ padding: 60px 24px;
167
+ color: #5f6368;
168
+ }
169
+
170
+ @media (max-width: 768px) {
171
+ .section-layout {
172
+ flex-direction: column;
173
+ }
174
+
175
+ .section-nav {
176
+ width: 100%;
177
+ }
178
+
179
+ .nav-button {
180
+ border-radius: 4px;
181
+ margin-bottom: 4px;
182
+ }
183
+
184
+ .module-header h1 {
185
+ font-size: 1.75rem;
186
+ }
187
+ }
src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App.jsx';
4
+ import './index.css';
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>
10
+ );
vite.config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite';
2
+ import react from '@vitejs/plugin-react';
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ base: '/enise-site-2/',
7
+ });