diff --git a/css/custom.css b/css/custom.css new file mode 100644 index 0000000..146cdb9 --- /dev/null +++ b/css/custom.css @@ -0,0 +1,16 @@ +/* default branding values */ +h1, h2, h3, h4, h5, h6, ol, ul, li, dl, dt, dd, p, button, input, select, legend, fieldset, label, navbar { + color: #333333; + font-family: 'Trebuchet MS'; + text-align: left; +} + +/* adaptions of w3 css */ +.w3-button{ + background-color: #E89212!important; + color: white; +} +.w3-button:hover { + color: #000!important; + background-color: #C95802!important; +} diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..ffe88a1 --- /dev/null +++ b/css/style.css @@ -0,0 +1,59 @@ +/* Form: Label */ +label { + display: block; + font-weight: bold; + margin: 0 0 0 10px; /* direction: N O S W */ +} +/* fieldset, surrounds form */ +fieldset { + border: 1px solid #C95802; + background-color: white; + margin-top: 10px; +} +/* legend, title of a fieldset */ +legend { + font-size: 110%; + font-weight: bold; + color: #FFFFFF; /* font color */ + text-transform: uppercase; + background-color: #C95802; + padding: 5px 10px 5px 10px; + margin-top: 10px; +} + +/* aside image */ +.aside-image { + max-width: 100%; + height: auto; +} + +/* navbar */ +.navbar { + background-color: #E89212!important; + color: white; + font-size: 125%; +} + +/* flexbox styling */ +#parent { + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: center; +} +/* text description element */ +#parent div:nth-child(1) { + flex-basis: 100%; +} +/* symbol elements */ +#parent div { + flex-basis: 50%; +} + +/* spacing */ +.between-spacing { + margin-bottom: 1cm; +} +.bottom-spacing { + margin-bottom: 3cm; + } diff --git a/css/style_confirmation.css b/css/style_confirmation.css new file mode 100644 index 0000000..11596e7 --- /dev/null +++ b/css/style_confirmation.css @@ -0,0 +1,49 @@ +/* define vertical gridbox container */ +.confirmation-container { + height: 300px; + display: grid; + grid-template-rows: 1fr 1fr 1fr 1fr 1fr; + grid-row-gap: 20px; + font-family: 'Trebuchet MS'; +} +/* top container for confirmation headline */ +confirmation-header { + grid-column: 1; + grid-row: 1; + align-items: start; +} +/* containers for details below headline */ +confirmation-details-1 { + grid-column: 1; + grid-row: 2/3; + justify-items: start; +} +confirmation-details-2 { + grid-column: 1; + grid-row: 3/4; + justify-items: start; +} +confirmation-details-3 { + grid-column: 1; + grid-row: 4/5; + justify-items: start; +} +/* bottom container for action buttons */ +confirmation-footer { + grid-column: 1; + grid-row: 5; + justify-items: end; +} + +/* classes for successful / failed renting */ +.success { + color: green; +} +.failure { + color: red; +} + +/* confirmation symbol */ +.confirmation-symbol { + margin: 10px; +} \ No newline at end of file diff --git a/js/canvas.js b/js/canvas.js new file mode 100644 index 0000000..36670a5 --- /dev/null +++ b/js/canvas.js @@ -0,0 +1,141 @@ +/************ GLOBAL VARS ***************/ +let incrementStepsAnimation = 0.15; +let timeBetweenRedrawAnimationMS = 10; + +/****************************************/ +/************ CROSS SYMBOL **************/ +/****************************************/ +let crossProperties; + +/* function that draws a Cross Symbol */ +function drawCross(canvasElementId, size, canvasSize) { + // get canvas element from HTML + let canvas = document.getElementById(canvasElementId); + let c = canvas.getContext('2d'); + // calculate some relative positions according to size: + // relativeSizeExclusions = 0...1 relative size of exclusions from the circle + // lineWidth = width of the stroke + let relativeSizeExclusions = 0.7; let lineWidth = 0.05*size; + // d = distance from canvas border + let d = lineWidth; + // clear canvas + c.clearRect(0,0,canvasSize,canvasSize); + + // draw circle + c.lineWidth = lineWidth; + c.fillStyle = "red"; + c.beginPath(); + c.arc(canvasSize/2, canvasSize/2, size/2-d, 0, 2 * Math.PI, false); + c.stroke(); + c.fill(); + + // draw exclusions + c.beginPath(); + c.arc(canvasSize/2, canvasSize/2, relativeSizeExclusions*(size/2-d), 1.85 * Math.PI, 0.65 * Math.PI, false); + c.closePath(); + c.fillStyle = "white"; + c.lineWidth = lineWidth; + c.stroke(); + c.fill(); + c.beginPath(); + c.arc(canvasSize/2, canvasSize/2, relativeSizeExclusions*(size/2-d), 1.65 * Math.PI, 0.85 * Math.PI, true); + c.closePath(); + c.fillStyle = "white"; + c.lineWidth = lineWidth; + c.stroke(); + c.fill(); +} + +/* recurring function that draws the animation of the Cross Symbol */ +function drawAnimatedCross() { + setTimeout(drawAnimatedCross, timeBetweenRedrawAnimationMS); + calculateNewSize(crossProperties); + drawCross(crossProperties.canvasElementId, crossProperties.currentSize, crossProperties.maxSize); +} + +/* initiates an animation of the Cross Symbol */ +function animateCross(canvasElementId, size, maxSize){ + crossProperties = { + canvasElementId: canvasElementId, + minSize: size, + maxSize: maxSize, + currentSize: size, + growing: true + } + drawAnimatedCross(); +} + +/****************************************/ +/********** CHECKMARK SYMBOL ***********/ +/****************************************/ +let checkmarkProperties; + +/* function that draws a Checkmark Symbol */ +function drawCheckmark(canvasElementId, size, canvasSize) { + // get canvas element from HTML + let canvas = document.getElementById(canvasElementId); + let c = canvas.getContext('2d'); + // calculate some relative positions according to size: + // xPosKink = x position of kink of the checkmark + // yHeightHook = y position of highest point of the hook + // lineWidth = width of the stroke + let xPosKink = 0.25*size; let yHeightHook = 0.5*size; let lineWidth = 0.05*size; + // d = distance from canvas border + // t = thickness of checkmark + let d = (canvasSize-size)/2+lineWidth; let t = 0.2*size; + // clear canvas + c.clearRect(0,0,canvasSize,canvasSize); + + // draw checkmark + c.lineWidth = lineWidth; + c.fillStyle = "green"; + c.beginPath(); + c.moveTo(xPosKink + d, canvasSize-d); + c.lineTo(canvasSize-d, d); + c.lineTo(canvasSize-d-t, d); + c.lineTo(xPosKink+d, canvasSize-d-t-lineWidth/2); + c.lineTo(d, yHeightHook+d-lineWidth/2); + c.lineTo(d, yHeightHook+d+t); + c.lineTo(xPosKink + d, canvasSize - d); + c.closePath(); + c.stroke(); + c.fill(); +} + +/* recurring function that draws the animation of the Checkmark Symbol */ +function drawAnimatedCheckmark() { + setTimeout(drawAnimatedCheckmark, timeBetweenRedrawAnimationMS); + calculateNewSize(checkmarkProperties); + drawCheckmark(checkmarkProperties.canvasElementId, checkmarkProperties.currentSize, checkmarkProperties.maxSize); +} + +/* initiates an animation of the Checkmark Symbol */ +function animateCheckmark(canvasElementId, size, maxSize){ + checkmarkProperties = { + canvasElementId: canvasElementId, + minSize: size, + maxSize: maxSize, + currentSize: maxSize, + growing: true + } + drawAnimatedCheckmark(); // start timed function +} + +/****************************************/ +/*************** HELPERS ***************/ +/****************************************/ + +/* Function to calculate the new size of a canvas symbol. Requires object created in 'animateX' function. */ +function calculateNewSize(symbol){ + if(symbol.growing && symbol.currentSize < symbol.maxSize){ + symbol.currentSize += incrementStepsAnimation; + }else if(symbol.growing && symbol.currentSize >= symbol.maxSize){ + symbol.growing = false; + symbol.currentSize -= incrementStepsAnimation; + }else if(symbol.growing == false && symbol.currentSize > symbol.minSize){ + symbol.currentSize -= incrementStepsAnimation; + }else if(symbol.growing == false && symbol.currentSize <= symbol.minSize){ + symbol.growing = true; + symbol.currentSize += incrementStepsAnimation; + } +} \ No newline at end of file diff --git a/js/validation.js b/js/validation.js new file mode 100644 index 0000000..3c7a641 --- /dev/null +++ b/js/validation.js @@ -0,0 +1,63 @@ +// Validation of form entry +function isValid() { + console.log("Validation started...."); + /* VALIDATE RENTAL ARTICLE NUMBER */ + let rental_art_nr = document.getElementById("mietart_nr").value; + if(rental_art_nr == ''){ // check if rental art nr is present. + setErrorPanel("Bitte eine Mietartikel Nummer angeben."); return false; + } + console.log("Testing if " + rental_art_nr[0] + " is a letter. IsLetter=" + rental_art_nr[0].match(/[a-z]/i)); + if(rental_art_nr[0].match(/[a-z]/i) == null){ // check if first character is a letter. all article numbers start with a letter. + setErrorPanel("Bitte eine gültige Mietartikel Nummer angeben."); return false; + } + + /* VALIDATE START DATE */ + let start_date_string = document.getElementById("von").value; + let start_date = new Date(start_date_string); + if(start_date_string == '' || start_date == null){ // check if start date is present and valid. + setErrorPanel("Bitte ein valides Startdatum angeben."); return false; + } + + /* VALIDATE END DATE */ + let end_date_string = document.getElementById("bis").value; + let end_date = new Date(end_date_string); + if(end_date_string == '' || end_date == null){ // check if end date is present and valid. + setErrorPanel("Bitte ein valides Enddatum angeben."); return false; + } + + /* VALIDATE START DATE IS BEFORE END DATE */ + console.log("Testing if start date " + start_date.toISOString() + " is before end date " + end_date.toISOString() + " Is=" + (end_date >= start_date)); + if(end_date < start_date){ // check if start date is before end date. + setErrorPanel("Startdatum muss vor Enddatum liegen."); return false; + } + + /* VALIDATE START DATE IS AFTER TODAY */ + let now = new Date(); // need to remove time from the "now" date object, therefore create new one with only date: + let now_dateOnly = new Date(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()); + console.log("Testing if start date " + start_date.toISOString() + " is after now " + now_dateOnly.toISOString() + ". IsTodayOrLater=" + (start_date >= now_dateOnly)); + if(start_date < now_dateOnly){ // check if start date in the past. today is ok. + setErrorPanel("Startdatum liegt in der Vergangenheit."); return false; + } + + /* VALIDATE CUSTOMER NUMBER */ + let customer_nr = document.getElementById("kunden_nr").value; + if(customer_nr == ''){ // check if customer number empty + setErrorPanel("Bitte eine Kundennummer angeben."); return false; + } + console.log("Testing if customer number " + customer_nr + " is a number. IsInteger=" + (isNaN(customer_nr) == false)); + if(isNaN(customer_nr) == true){ // check if customer number is a number + setErrorPanel("Kundennummer ist ungültig."); return false; + } + + console.log("Validation sucessful"); + return true; // valid +} + +// Unhides error panel and shows text +function setErrorPanel(errorText) { + let error_panel = document.getElementById("error-panel"); + let error_text = document.getElementById("error-text"); + + error_text.innerText = errorText; + error_panel.hidden = false; +} \ No newline at end of file diff --git a/rental.html b/rental.html new file mode 100644 index 0000000..bff5867 --- /dev/null +++ b/rental.html @@ -0,0 +1,106 @@ + + + + Mietanfrage + + + + + + + + + + +
+

Mietanfrage Jonas Arnold Eventtechnik

+ +
+ +
+ +
+

Information

+ +
+

Beschreibung

+ + Auf dieser Webseite können Sie als registrierter Kunde, Material von Jonas Arnold Eventtechnik mieten. Sie erhalten direkt eine Bestätigung, ob das Material zum gewünschten Zeitpunkt noch verfügbar ist.
+ Sofern es verfügbar ist, wird es für Sie reserviert und wir nehmen im Anschluss Kontakt mit Ihnen auf.
+ Wenn Sie mehrere Materialien mieten möchten, füllen Sie das untenstehende Formular mehrmals aus. Die Bestellnummer bleibt während einer Stunde dieselbe.
+ Die verfügbaren Mietartikel und deren Nummer finden Sie hier:
+
+ +

+ +

Bestätigung Reservation

+ +
+
+ Die Mietanfrage wird mit der Datenbank abgeglichen. Sofern das Material verfügbar ist, wird es für Sie reserviert. Ansonsten werden Sie darüber informiert, dass das Material bereits vermietet ist.

+
+
+ Erfolgreiche Reservation
+ + Ihr Webbrowser unterstützt kein Canvas. + +
+
+ Reservation nicht möglich
+ + Ihr Webbrowser unterstützt kein Canvas. + +
+
+
+
+ +

+ + +
+
+ +

Mietanfrage Formular

+
+ +
+
+
+ Mietartikel + +
+ + + + +
+
+
+
+
+ Kundenangaben + +
+
+
+ + +
+
+
+
+

+
+ + + + \ No newline at end of file diff --git a/rental_action.php b/rental_action.php new file mode 100644 index 0000000..3fe91be --- /dev/null +++ b/rental_action.php @@ -0,0 +1,202 @@ + + + + Mietanfrage + + + + + + + + +
+

Rückmeldung Mietanfrage

+
+
+ +
+ + +Bestellnummer: ".$order_num.""; + echo "Gemietet von: ".$start_date." bis ".$end_date.""; + echo "Material: ".$material_num." - ".$material_name; + if($material_desc != null){ + echo "
Beschreibung: ".$material_desc."
"; + } else{ + echo ""; + } +} + +/* builds html content of the grid-element "confirmation details" a failed (unsuccessful) response */ +function buildFailedConfirmationDetailsSection($text) { + echo "".$text.""; +} + +/* main PHP */ +// initialize variables +$failure_text = "Unbekannter Fehler"; +$material_name = ""; +$material_desc = ""; +$order_nr = 0; +// get cookie with order nr, otherwise set it to 0 +if(isset($_COOKIE['OrderNr'])){ + $order_nr = $_COOKIE['OrderNr']; +} + +$rentalSuccess = false; +// start validation of parameters +if(validateParameters()){ + // open SQL connection + $conn = mysqli_connect("localhost", "root", "", "rental_system"); + if (!$conn) { + $failure_text = "Datenbankverbindung fehlgeschlagen."; + } else { + do{ // do once loop to be able to make breaks everywhere + $rental_art_nr = trim($_POST['rental_article_nr']); + $start_date = trim($_POST['start_date']); + $end_date = trim($_POST['end_date']); + $customer_nr = trim(intval($_POST['customer_nr'])); + + // check if rental article number exists in article database + $query = "SELECT * FROM articles WHERE article_number = ?"; + $stmt = mysqli_prepare($conn, $query); + mysqli_stmt_bind_param($stmt, 's', $rental_art_nr); + mysqli_stmt_execute($stmt); + $res = mysqli_stmt_get_result($stmt); + if($res){ + $row = mysqli_fetch_assoc($res); //expecting only one row since article_number is unique in SQL + if($row == null){ // row == null means there is no material with this number in the database + $failure_text = "Materialnummer existiert nicht in Datenbank."; + break; + } + $material_name = $row['article_name']; + $material_desc = $row['html_description']; + } else{ + $failure_text = "Abfrage der Materialnummer fehlgeschlagen."; + break; + } + + // check if the material is not yet rented out in the selected time range + $query = "SELECT * FROM rental_entries WHERE article_number = ? AND start_day <= ? AND end_day >= ?"; + $stmt = mysqli_prepare($conn, $query); + mysqli_stmt_bind_param($stmt, 'sss', $rental_art_nr, $end_date, $start_date); + mysqli_stmt_execute($stmt); + $res = mysqli_stmt_get_result($stmt); + if($res){ + $row = mysqli_fetch_assoc($res); + if($row != null){ // row == null means the material is not rented out in this time range + $failure_text = "Material ist bereits vermietet von ".$row['start_day']." bis ".$row['end_day']."."; + break; + } + } else{ + $failure_text = "Abfrage der vermieteten Artikel fehlgeschlagen."; + break; + } + + // if no order number exists yet => create one + if($order_nr == 0){ + $order_nr = $customer_nr.".".date("YmdHis"); // Create unique order number with customer number and current time (full date + time up to seconds) + setcookie("OrderNr", $order_nr, time() + 3600); // valid for 1hr. + } + + // enter the rental + $query = "INSERT INTO rental_entries (rental_id, article_number, start_day, end_day, order_number, customer_number) VALUES (NULL, ?, ?, ?, ?, ?);"; + $stmt = mysqli_prepare($conn, $query); + mysqli_stmt_bind_param($stmt, 'ssssi', $rental_art_nr, $start_date, $end_date, $order_nr, $customer_nr); + $res = mysqli_stmt_execute($stmt); + if($res){ + $rentalSuccess = true; + } else{ + $failure_text = "Eintragen der Mietanfrage fehlgeschlagen. SQL query was:\n".$query; + break; + } + + }while(0); + + mysqli_close($conn); // close database connection if it was connected + } +} + +/* final check for success, generate according defauls section */ +if($rentalSuccess){ + echo ""; + echo "

Mietanfrage erfolgreich erfasst

"; + buildSuccessfulConfirmationDetailsSection($start_date, $end_date, $order_nr, $customer_nr, $rental_art_nr, $material_name, $material_desc); +} else{ + echo ""; + echo "

Mietanfrage konnte nicht erfasst werden

"; + buildFailedConfirmationDetailsSection($failure_text); +} + +?> + + + + +
+
+ + \ No newline at end of file diff --git a/var/aside_image.jpg b/var/aside_image.jpg new file mode 100644 index 0000000..e7dddbf Binary files /dev/null and b/var/aside_image.jpg differ