Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
81b859aca9 | |||
c5c6fb97e4 | |||
c682ca2893 | |||
827841f9e6 | |||
932dd9264b | |||
44d87a8ffb | |||
b1f6c6cdd5 | |||
6b6d1a27d5 | |||
d5f40e43ed | |||
e8ece417da | |||
f85e94b1e1 | |||
a4ae52471a | |||
c4b47c86d5 | |||
045897fc7b | |||
cbac5ee395 | |||
e5cdab0fc1 | |||
575fa7e60e | |||
337bd3a900 | |||
82cba2e2c3 | |||
f76ac1e8fe | |||
631b00db0b | |||
bb97034d14 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -989,7 +989,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "orario-scolastico-itet"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"axum",
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "orario-scolastico-itet"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
373
LICENSE
Normal file
373
LICENSE
Normal file
@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
68
README.md
Normal file
68
README.md
Normal file
@ -0,0 +1,68 @@
|
||||
# Orario Scolastico
|
||||
|
||||
Orario Scolastico is a web application that allows students to view their school
|
||||
timetable in a more readable way. The application works by downloading the PDF
|
||||
file containing the timetable from the school website and then displaying it in
|
||||
a more user-friendly format.
|
||||
|
||||
## Requirements
|
||||
|
||||
### If you want to use Docker Compose
|
||||
|
||||
- [Git](https://git-scm.com/downloads) (optional)
|
||||
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||
|
||||
### If you don't want to use Docker Compose
|
||||
|
||||
- [Git](https://git-scm.com/downloads) (optional)
|
||||
- [Cargo](https://doc.rust-lang.org/stable/cargo/getting-started/installation.html)
|
||||
- [Python](https://www.python.org/downloads/)
|
||||
|
||||
## Usage
|
||||
|
||||
If you want to self-host the project, you can follow the steps below:
|
||||
|
||||
### With Docker Compose
|
||||
|
||||
```bash
|
||||
git clone https://git.riefolo.me/mariano/orario-scolastico-itet.git
|
||||
cd orario-scolastico-itet
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
### Without Docker Compose
|
||||
|
||||
If you prefer to build the project without Docker, you can follow the steps below:
|
||||
|
||||
```bash
|
||||
git clone https://git.riefolo.me/mariano/orario-scolastico-itet.git
|
||||
cd orario-scolastico-itet
|
||||
cargo build --release
|
||||
./target/release/orario-scolastico-itet
|
||||
```
|
||||
|
||||
Consider that this may not work if you are not on a Linux machine.
|
||||
|
||||
## Contributing
|
||||
|
||||
Pull requests are welcome. For major changes, please open an issue first
|
||||
to discuss what you would like to change.
|
||||
|
||||
Please make sure to update tests as appropriate.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Does this project DoS the school website?
|
||||
|
||||
The project features a cache system that prevents it from making too much
|
||||
requests. The list of PDFs is only fetched every 30 minutes only if a user asks for
|
||||
it, and the pdf files are only fetched once per file.
|
||||
|
||||
### Can I use this project with my school?
|
||||
|
||||
At the moment, the project is only compatible with the
|
||||
[ITET Cassandro Fermi Nervi](https://cassandroferminervi.edu.it/) institute in Italy.
|
||||
|
||||
## License
|
||||
|
||||
Orario Scolastico is released under the [Mozilla Public License 2.0](LICENSE)
|
@ -5,7 +5,7 @@ use axum::{
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use csv::StringRecord;
|
||||
use csv::{Reader, StringRecord};
|
||||
use failure::Error;
|
||||
use scraper::{Html, Selector};
|
||||
use serde::Deserialize;
|
||||
@ -27,7 +27,7 @@ pub fn get_routes() -> Router {
|
||||
)
|
||||
}
|
||||
|
||||
async fn get_pdf_links() -> Result<Vec<String>, reqwest::Error> {
|
||||
async fn get_pdf() -> Result<HashMap<String, Vec<String>>, reqwest::Error> {
|
||||
if let Ok(metadata) = fs::metadata("pdf.json") {
|
||||
let last_modified = metadata.modified().unwrap();
|
||||
let now = std::time::SystemTime::now();
|
||||
@ -44,11 +44,18 @@ async fn get_pdf_links() -> Result<Vec<String>, reqwest::Error> {
|
||||
.await?;
|
||||
|
||||
let dom = Html::parse_document(&response);
|
||||
let selector = Selector::parse(".wp-block-list > li > a").unwrap();
|
||||
let mut result = Vec::new();
|
||||
let selector = Selector::parse("a:has(> strong)[href*=pdf]").unwrap();
|
||||
let mut result: HashMap<String, Vec<String>> = HashMap::new();
|
||||
|
||||
for element in dom.select(&selector) {
|
||||
result.push(element.attr("href").unwrap().to_owned());
|
||||
result
|
||||
.entry(String::from("links"))
|
||||
.or_default()
|
||||
.push(element.attr("href").unwrap().to_owned());
|
||||
result
|
||||
.entry(String::from("names"))
|
||||
.or_default()
|
||||
.push(element.text().collect());
|
||||
}
|
||||
|
||||
let data = serde_json::to_string(&result).unwrap();
|
||||
@ -58,18 +65,18 @@ async fn get_pdf_links() -> Result<Vec<String>, reqwest::Error> {
|
||||
}
|
||||
|
||||
pub async fn pdf() -> impl IntoResponse {
|
||||
match get_pdf_links().await {
|
||||
Ok(x) => (StatusCode::OK, Json(json!({ "links": x}))),
|
||||
match get_pdf().await {
|
||||
Ok(x) => (StatusCode::OK, Json(json!(x["names"]))),
|
||||
Err(e) => (StatusCode::OK, Json(json!({ "error": e.to_string()}))),
|
||||
}
|
||||
}
|
||||
|
||||
async fn download_pdf(id: u8) -> Result<String, Error> {
|
||||
let links = get_pdf_links().await.unwrap();
|
||||
async fn get_csv(id: u8) -> Result<String, Error> {
|
||||
let links = get_pdf().await.unwrap();
|
||||
if id as usize >= links.len() {
|
||||
return Err(failure::err_msg("Invalid ID"));
|
||||
}
|
||||
let url = &links[id as usize];
|
||||
let url = &links["links"][id as usize];
|
||||
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(url.as_bytes());
|
||||
@ -82,7 +89,7 @@ async fn download_pdf(id: u8) -> Result<String, Error> {
|
||||
let pdf_path = format!("pdf/{}.pdf", filename);
|
||||
let csv_path = format!("csv/{}.csv", filename);
|
||||
|
||||
if fs::metadata(&pdf_path).is_err() {
|
||||
if fs::metadata(&csv_path).is_err() {
|
||||
let response = reqwest::get(url).await?;
|
||||
let body = response.bytes().await?;
|
||||
std::fs::write(&pdf_path, body)?;
|
||||
@ -92,7 +99,10 @@ async fn download_pdf(id: u8) -> Result<String, Error> {
|
||||
.arg(&pdf_path)
|
||||
.arg(&csv_path)
|
||||
.spawn()
|
||||
.expect("Failed to generate csv file");
|
||||
.expect("Failed to generate csv file")
|
||||
.wait()?;
|
||||
|
||||
fs::remove_file(&pdf_path)?;
|
||||
}
|
||||
|
||||
Ok(csv_path)
|
||||
@ -105,12 +115,12 @@ pub struct FilterByTeacherQuery {
|
||||
}
|
||||
|
||||
pub struct Header {
|
||||
teacher: u8,
|
||||
teacher: Option<u8>,
|
||||
weekdays: HashMap<String, Range<usize>>,
|
||||
}
|
||||
|
||||
pub async fn get_teacher(Query(params): Query<FilterByTeacherQuery>) -> impl IntoResponse {
|
||||
let filename = match download_pdf(params.id).await {
|
||||
let filename = match get_csv(params.id).await {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return (
|
||||
@ -122,11 +132,11 @@ pub async fn get_teacher(Query(params): Query<FilterByTeacherQuery>) -> impl Int
|
||||
let csv_content = fs::read_to_string(&filename).unwrap();
|
||||
|
||||
let mut rdr = csv::Reader::from_reader(csv_content.as_bytes());
|
||||
let header = get_header(rdr.headers().unwrap());
|
||||
let header = get_header_wrapper(&mut rdr);
|
||||
|
||||
for record in rdr.records() {
|
||||
let record = record.unwrap();
|
||||
if record[header.teacher as usize] == params.professore {
|
||||
if record[header.teacher.unwrap() as usize] == params.professore {
|
||||
let mut result: HashMap<String, Vec<&str>> = HashMap::new();
|
||||
for (i, cell) in record.iter().enumerate() {
|
||||
for (j, range) in header.weekdays.clone() {
|
||||
@ -152,7 +162,7 @@ pub struct FilterByClassQuery {
|
||||
}
|
||||
|
||||
pub async fn get_class(Query(params): Query<FilterByClassQuery>) -> impl IntoResponse {
|
||||
let filename = match download_pdf(params.id).await {
|
||||
let filename = match get_csv(params.id).await {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return (
|
||||
@ -164,7 +174,7 @@ pub async fn get_class(Query(params): Query<FilterByClassQuery>) -> impl IntoRes
|
||||
let csv_content = fs::read_to_string(&filename).unwrap();
|
||||
|
||||
let mut rdr = csv::Reader::from_reader(csv_content.as_bytes());
|
||||
let header = get_header(rdr.headers().unwrap());
|
||||
let header = get_header_wrapper(&mut rdr);
|
||||
|
||||
let mut result: HashMap<String, Vec<String>> = HashMap::new();
|
||||
|
||||
@ -182,7 +192,8 @@ pub async fn get_class(Query(params): Query<FilterByClassQuery>) -> impl IntoRes
|
||||
v.resize(range.end - range.start, String::new());
|
||||
v
|
||||
});
|
||||
value[i - range.start] = record[header.teacher as usize].to_string();
|
||||
value[i - range.start] =
|
||||
record[header.teacher.unwrap() as usize].to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -199,9 +210,13 @@ pub async fn get_class(Query(params): Query<FilterByClassQuery>) -> impl IntoRes
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_header(record: &StringRecord) -> Header {
|
||||
let mut docente: u8 = 0;
|
||||
pub fn get_header(record: &StringRecord, header: Option<Header>) -> Header {
|
||||
let mut teacher: Option<u8> = None;
|
||||
let mut weekdays = HashMap::new();
|
||||
if let Some(h) = header {
|
||||
teacher = h.teacher;
|
||||
weekdays = h.weekdays;
|
||||
}
|
||||
|
||||
for (i, field) in record.iter().enumerate() {
|
||||
let next_index = record
|
||||
@ -210,29 +225,36 @@ pub fn get_header(record: &StringRecord) -> Header {
|
||||
.position(|x| !x.is_empty())
|
||||
.unwrap_or(record.len());
|
||||
|
||||
let field = field.replace("ì", "i").replace(" ", "").to_uppercase();
|
||||
let field = field.replace(" ", "").to_uppercase();
|
||||
|
||||
if field == "DOCENTE" {
|
||||
docente = i as u8;
|
||||
} else if field == "LUNEDI" {
|
||||
teacher = Some(i as u8);
|
||||
} else if field.starts_with("LUN") {
|
||||
weekdays.insert("Lunedì".to_string(), i..next_index + i + 1);
|
||||
} else if field == "MARTEDI" {
|
||||
} else if field.starts_with("MAR") {
|
||||
weekdays.insert("Martedì".to_string(), i..next_index + i + 1);
|
||||
} else if field == "MERCOLEDI" {
|
||||
} else if field.starts_with("MER") {
|
||||
weekdays.insert("Mercoledì".to_string(), i..next_index + i + 1);
|
||||
} else if field == "GIOVEDI" {
|
||||
} else if field.starts_with("GIO") {
|
||||
weekdays.insert("Giovedì".to_string(), i..next_index + i + 1);
|
||||
} else if field == "VENERDI" {
|
||||
} else if field.starts_with("VEN") {
|
||||
weekdays.insert("Venerdì".to_string(), i..next_index + i + 1);
|
||||
} else if field == "SABATO" {
|
||||
} else if field.starts_with("SAB") {
|
||||
weekdays.insert("Sabato".to_string(), i..next_index + i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Header {
|
||||
teacher: docente,
|
||||
weekdays,
|
||||
Header { teacher, weekdays }
|
||||
}
|
||||
|
||||
pub fn get_header_wrapper(rdr: &mut Reader<&[u8]>) -> Header {
|
||||
let mut header = get_header(rdr.headers().unwrap(), None);
|
||||
let mut records = rdr.records();
|
||||
while header.teacher.is_none() {
|
||||
let record = records.next().unwrap().unwrap();
|
||||
header = get_header(&record, Some(header));
|
||||
}
|
||||
header
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -241,7 +263,7 @@ pub struct GetTeachersQuery {
|
||||
}
|
||||
|
||||
pub async fn get_teachers(Query(params): Query<GetTeachersQuery>) -> impl IntoResponse {
|
||||
let filename = match download_pdf(params.id).await {
|
||||
let filename = match get_csv(params.id).await {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return (
|
||||
@ -253,7 +275,7 @@ pub async fn get_teachers(Query(params): Query<GetTeachersQuery>) -> impl IntoRe
|
||||
|
||||
let csv_content = fs::read_to_string(&filename).unwrap();
|
||||
let mut rdr = csv::Reader::from_reader(csv_content.as_bytes());
|
||||
let header = get_header(rdr.headers().unwrap());
|
||||
let header = get_header_wrapper(&mut rdr);
|
||||
|
||||
let mut teachers = Vec::new();
|
||||
|
||||
@ -263,7 +285,7 @@ pub async fn get_teachers(Query(params): Query<GetTeachersQuery>) -> impl IntoRe
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if let Some(teacher) = record.get(header.teacher as usize) {
|
||||
if let Some(teacher) = record.get(header.teacher.unwrap() as usize) {
|
||||
if !teacher.is_empty() {
|
||||
teachers.push(teacher.to_owned());
|
||||
}
|
||||
@ -292,7 +314,7 @@ fn is_valid_class(class: &str) -> bool {
|
||||
}
|
||||
|
||||
pub async fn get_classes(Query(params): Query<GetClassesQuery>) -> impl IntoResponse {
|
||||
let filename = match download_pdf(params.id).await {
|
||||
let filename = match get_csv(params.id).await {
|
||||
Ok(x) => x,
|
||||
Err(_) => {
|
||||
return (
|
||||
@ -304,7 +326,8 @@ pub async fn get_classes(Query(params): Query<GetClassesQuery>) -> impl IntoResp
|
||||
let csv_content = fs::read_to_string(&filename).unwrap();
|
||||
|
||||
let mut rdr = csv::Reader::from_reader(csv_content.as_bytes());
|
||||
let header = get_header(rdr.headers().unwrap());
|
||||
let header = get_header_wrapper(&mut rdr);
|
||||
println!("weekdays: {:?}", header.weekdays);
|
||||
|
||||
let mut classes: Vec<HashMap<char, HashSet<String>>> = vec![HashMap::new(); 5];
|
||||
|
||||
|
@ -10,6 +10,7 @@ pub fn get_routes() -> axum::Router {
|
||||
axum::Router::new()
|
||||
.route("/", get(index))
|
||||
.nest_service("/static", ServeDir::new("static"))
|
||||
.fallback(not_found)
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
@ -21,3 +22,13 @@ async fn index() -> impl IntoResponse {
|
||||
let html = template.render().unwrap();
|
||||
(StatusCode::OK, Html(html).into_response())
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "404.html")]
|
||||
struct NotFoundTemplate {}
|
||||
|
||||
async fn not_found() -> impl IntoResponse {
|
||||
let template = NotFoundTemplate {};
|
||||
let html = template.render().unwrap();
|
||||
(StatusCode::NOT_FOUND, Html(html).into_response())
|
||||
}
|
||||
|
21
static/app.webmanifest
Normal file
21
static/app.webmanifest
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"id": "/",
|
||||
"start_url": "/",
|
||||
"scope": "/",
|
||||
"name": "Orario Scolastico (ITET Cassandro Fermi Nervi)",
|
||||
"short_name": "Orario ITET",
|
||||
"display": "standalone",
|
||||
"icons": [
|
||||
{
|
||||
"src": "icon_x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "maskable_icon_x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 475 KiB |
BIN
static/icon_x512.png
Normal file
BIN
static/icon_x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 237 KiB |
@ -1,21 +1,24 @@
|
||||
function load_links() {
|
||||
function load_pdfs() {
|
||||
fetch("/api/pdf")
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
let i = 0;
|
||||
data.links.forEach((el) => {
|
||||
const select = document.getElementById("links");
|
||||
if (data === undefined || data.length === 0) {
|
||||
show_error(null, "Nessun orario trovato");
|
||||
return;
|
||||
}
|
||||
data.forEach((el) => {
|
||||
const select = document.getElementById("pdf");
|
||||
const option = document.createElement("option");
|
||||
option.value = i;
|
||||
option.text = el.split("/").pop().split(".pdf")[0];
|
||||
option.text = el;
|
||||
select.appendChild(option);
|
||||
i++;
|
||||
});
|
||||
document.getElementById("links-label").removeAttribute("aria-busy");
|
||||
document.getElementById("pdf-label").removeAttribute("aria-busy");
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("Errore nel caricamento dei dati, riprova più tardi.");
|
||||
console.error("Error:", error);
|
||||
show_error(error);
|
||||
});
|
||||
}
|
||||
|
||||
@ -30,14 +33,14 @@ function load_teachers(id) {
|
||||
data.forEach((el) => {
|
||||
const option = document.createElement("option");
|
||||
option.value = el;
|
||||
option.text = el;
|
||||
list.appendChild(option);
|
||||
i++;
|
||||
});
|
||||
document.getElementById("teacher-label").removeAttribute("aria-busy");
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("Errore nel caricamento dei dati, riprova più tardi.");
|
||||
console.error("Error:", error);
|
||||
show_error(error);
|
||||
});
|
||||
}
|
||||
|
||||
@ -62,16 +65,19 @@ function load_classes(id) {
|
||||
document.getElementById("class-label").removeAttribute("aria-busy");
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("Errore nel caricamento dei dati, riprova più tardi.");
|
||||
console.error("Error:", error);
|
||||
show_error(error);
|
||||
});
|
||||
}
|
||||
|
||||
function make_teacher_table(id, teacher) {
|
||||
document.getElementById("table-title").innerText =
|
||||
`Orario del docente ${teacher}`;
|
||||
make_table(`/api/professore?id=${id}&professore=${teacher}`);
|
||||
}
|
||||
|
||||
function make_student_table(id, student_class) {
|
||||
document.getElementById("table-title").innerText =
|
||||
`Orario della classe ${student_class}`;
|
||||
make_table(`/api/classe?id=${id}&classe=${student_class}`);
|
||||
}
|
||||
|
||||
@ -138,19 +144,30 @@ function make_table(url) {
|
||||
document.getElementById("table").removeAttribute("aria-busy");
|
||||
})
|
||||
.catch((error) => {
|
||||
alert("Errore nel caricamento dei dati, riprova più tardi.");
|
||||
console.error("Error:", error);
|
||||
show_error(error);
|
||||
});
|
||||
}
|
||||
|
||||
function select_pdf() {
|
||||
Cookies.set("id", document.getElementById("links").value);
|
||||
Cookies.set("id", document.getElementById("pdf").value);
|
||||
reset_forms();
|
||||
document.getElementById("type").value = "none";
|
||||
document.getElementById("form-teacher").setAttribute("hidden", "");
|
||||
document.getElementById("form-classes").setAttribute("hidden", "");
|
||||
document.getElementById("form-type").removeAttribute("hidden");
|
||||
}
|
||||
|
||||
function reset_forms() {
|
||||
document.getElementById("year").value = "none";
|
||||
document.getElementById("section").value = "none";
|
||||
document.getElementById("major").value = "none";
|
||||
document.getElementById("teacher").value = "";
|
||||
}
|
||||
|
||||
function select_type() {
|
||||
let id = document.getElementById("links").value;
|
||||
let id = document.getElementById("pdf").value;
|
||||
let value = document.getElementById("type").value;
|
||||
reset_forms();
|
||||
if (value === "teacher") {
|
||||
load_teachers(id);
|
||||
document.getElementById("form-teacher").removeAttribute("hidden");
|
||||
@ -200,7 +217,7 @@ function load_major() {
|
||||
}
|
||||
|
||||
function select_teacher() {
|
||||
let teacher = document.getElementById("teacher").value;
|
||||
let teacher = document.getElementById("teachers").value;
|
||||
let teachers = JSON.parse(sessionStorage.getItem("teachers"));
|
||||
if (!teachers.includes(teacher)) {
|
||||
return false;
|
||||
@ -224,6 +241,36 @@ function delete_cookies() {
|
||||
Cookies.remove("class");
|
||||
}
|
||||
|
||||
function show_error(e, message) {
|
||||
let default_error = "Errore nel caricamento dei dati, riprova più tardi";
|
||||
console.error(e ?? default_error);
|
||||
document.getElementById("error").textContent = message ?? default_error;
|
||||
document.getElementById("error-dialog").setAttribute("open", "");
|
||||
}
|
||||
|
||||
function filter_teachers() {
|
||||
let input = document.getElementById("teacher");
|
||||
let filter = input.value.toUpperCase();
|
||||
let select = document.getElementById("teachers");
|
||||
let option = select.getElementsByTagName("option");
|
||||
let found_first = false;
|
||||
for (let i = 0; i < option.length; i++) {
|
||||
let txtValue = option[i].textContent;
|
||||
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
||||
option[i].style.display = "";
|
||||
if (!found_first) {
|
||||
select.selectedIndex = i;
|
||||
found_first = true;
|
||||
}
|
||||
} else {
|
||||
option[i].style.display = "none";
|
||||
if (option[i].hasAttribute("selected")) {
|
||||
option[i].removeAttribute("selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Cookies.get("id") && Cookies.get("type")) {
|
||||
if (Cookies.get("type") === "teacher" && Cookies.get("teacher")) {
|
||||
make_teacher_table(Cookies.get("id"), Cookies.get("teacher"));
|
||||
@ -234,5 +281,5 @@ if (Cookies.get("id") && Cookies.get("type")) {
|
||||
}
|
||||
} else {
|
||||
document.getElementById("forms").removeAttribute("hidden");
|
||||
load_links();
|
||||
load_pdfs();
|
||||
}
|
||||
|
BIN
static/maskable_icon_x512.png
Normal file
BIN
static/maskable_icon_x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 124 KiB |
11
templates/404.html
Normal file
11
templates/404.html
Normal file
@ -0,0 +1,11 @@
|
||||
{% extends "base.html" %} {% block title %}Pagina non trovata{% endblock %} {%
|
||||
block head %}
|
||||
<style>
|
||||
main {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
{% endblock %} {% block content %}
|
||||
<h1>404 Pagina non trovata</h1>
|
||||
<p>La pagina che stai cercando non esiste.</p>
|
||||
{% endblock %}
|
29
templates/base.html
Normal file
29
templates/base.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="icon" type="image/png" href="/static/favicon.png" />
|
||||
<link rel="manifest" href="/static/app.webmanifest" />
|
||||
<script async src="https://cdn.jsdelivr.net/npm/pwacompat" crossorigin="anonymous"></script>
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">{% block content %}{% endblock %}</main>
|
||||
<footer class="container">
|
||||
<p>
|
||||
Codice rilasciato sotto licenza
|
||||
<a
|
||||
href="https://git.riefolo.me/mariano/orario-scolastico-itet/src/branch/master/LICENSE"
|
||||
target="_blank"
|
||||
>MPL 2.0</a
|
||||
>
|
||||
</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
@ -1,86 +1,94 @@
|
||||
<!doctype html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Orario scolastico</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script>
|
||||
<script src="/static/index.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<main class="container">
|
||||
<noscript>
|
||||
<h1>
|
||||
Per utilizzare questa applicazione è necessario abilitare JavaScript.
|
||||
</h1>
|
||||
</noscript>
|
||||
<div id="forms" hidden>
|
||||
<form id="form-pdf">
|
||||
<fieldset>
|
||||
<label id="links-label" aria-busy="true"
|
||||
>Quale orario vuoi usare?</label
|
||||
>
|
||||
<select id="links" onchange="select_pdf()" required>
|
||||
<option selected disabled value="none">
|
||||
Seleziona un orario
|
||||
</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
</form>
|
||||
<form id="form-type" hidden>
|
||||
<formfield>
|
||||
<label>Sei un professore o uno studente?</label>
|
||||
<select id="type" onchange="select_type()">
|
||||
<option selected disabled value="none">
|
||||
Seleziona una categoria
|
||||
</option>
|
||||
<option value="teacher">Professore</option>
|
||||
<option value="student">Studente</option>
|
||||
</select>
|
||||
</formfield>
|
||||
</form>
|
||||
<form id="form-teacher" onsubmit="return select_teacher()" hidden>
|
||||
<formfield>
|
||||
<label id="teacher-label" aria-busy="true"
|
||||
>Seleziona il tuo cognome</label
|
||||
>
|
||||
<input id="teacher" type="text" list="teachers" />
|
||||
<datalist id="teachers"></datalist>
|
||||
</formfield>
|
||||
<input type="submit" value="Continua" />
|
||||
</form>
|
||||
<form id="form-classes" hidden>
|
||||
<formfield>
|
||||
<label id="class-label" aria-busy="true"
|
||||
>Seleziona la tua classe</label
|
||||
>
|
||||
<select id="year" onchange="load_section()">
|
||||
<option selected disabled value="none">Seleziona l'anno</option>
|
||||
</select>
|
||||
<select id="section" onchange="load_major()">
|
||||
<option selected disabled value="none">
|
||||
Seleziona la sezione
|
||||
</option>
|
||||
</select>
|
||||
<select id="major" onchange="select_class()">
|
||||
<option selected disabled value="none">
|
||||
Seleziona l'indirizzo
|
||||
</option>
|
||||
</select>
|
||||
<input type="submit" value="Continua" />
|
||||
</formfield>
|
||||
</form>
|
||||
</div>
|
||||
<div id="dashboard" hidden>
|
||||
<table id="table" class="striped" aria-busy="true"></table>
|
||||
<form>
|
||||
<input type="submit" onclick="delete_cookies()" value="Reimposta" />
|
||||
</form>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
{% extends "base.html" %} {% block title %}Orario Scolastico{% endblock %} {%
|
||||
block head %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.5/dist/js.cookie.min.js"></script>
|
||||
<script src="/static/index.js" defer></script>
|
||||
<style>
|
||||
@media (max-width: 1279px) {
|
||||
table thead tr {
|
||||
display: none;
|
||||
}
|
||||
|
||||
table td {
|
||||
display: block;
|
||||
}
|
||||
|
||||
table tr {
|
||||
counter-set: column;
|
||||
}
|
||||
|
||||
table tr td {
|
||||
counter-increment: column;
|
||||
}
|
||||
|
||||
table td::before {
|
||||
content: counter(column) " ora: ";
|
||||
}
|
||||
}
|
||||
|
||||
#table-title {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
{% endblock %} {%block content %}
|
||||
<noscript>
|
||||
<h1>Per utilizzare questa applicazione è necessario abilitare JavaScript.</h1>
|
||||
</noscript>
|
||||
<div id="forms" hidden>
|
||||
<form id="form-pdf">
|
||||
<fieldset>
|
||||
<label id="pdf-label" aria-busy="true">Seleziona il calendario</label>
|
||||
<select id="pdf" onchange="select_pdf()" required>
|
||||
<option selected disabled value="none">Seleziona un'opzione</option>
|
||||
</select>
|
||||
</fieldset>
|
||||
</form>
|
||||
<form id="form-type" hidden>
|
||||
<formfield>
|
||||
<label>Seleziona cosa visualizzare</label>
|
||||
<select id="type" onchange="select_type()">
|
||||
<option selected disabled value="none">Seleziona un'opzione</option>
|
||||
<option value="teacher">Orario docente</option>
|
||||
<option value="student">Orario classe</option>
|
||||
</select>
|
||||
</formfield>
|
||||
</form>
|
||||
<form id="form-teacher" onsubmit="return select_teacher()" hidden>
|
||||
<formfield>
|
||||
<label id="teacher-label" aria-busy="true">Seleziona docente</label>
|
||||
<input
|
||||
id="teacher"
|
||||
type="text"
|
||||
oninput="filter_teachers()"
|
||||
placeholder="Filtra"
|
||||
/>
|
||||
<select id="teachers"></select>
|
||||
</formfield>
|
||||
<input type="submit" value="Continua" />
|
||||
</form>
|
||||
<form id="form-classes" hidden>
|
||||
<formfield>
|
||||
<label id="class-label" aria-busy="true">Seleziona la tua classe</label>
|
||||
<select id="year" onchange="load_section()">
|
||||
<option selected disabled value="none">Seleziona l'anno</option>
|
||||
</select>
|
||||
<select id="section" onchange="load_major()">
|
||||
<option selected disabled value="none">Seleziona la sezione</option>
|
||||
</select>
|
||||
<select id="major" onchange="select_class()">
|
||||
<option selected disabled value="none">Seleziona l'indirizzo</option>
|
||||
</select>
|
||||
<input type="submit" value="Continua" />
|
||||
</formfield>
|
||||
</form>
|
||||
</div>
|
||||
<div id="dashboard" hidden>
|
||||
<h1 id="table-title"></h1>
|
||||
<table id="table" class="striped" aria-busy="true"></table>
|
||||
<form>
|
||||
<input type="submit" onclick="delete_cookies()" value="Reimposta" />
|
||||
</form>
|
||||
</div>
|
||||
<dialog id="error-dialog">
|
||||
<p id="error">Errore: non è stato possibile caricare l'orario.</p>
|
||||
</dialog>
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue
Block a user