Compare commits
3 Commits
32690e9f5d
...
8bc418ebb2
| Author | SHA1 | Date |
|---|---|---|
|
|
8bc418ebb2 | |
|
|
a41b5919f0 | |
|
|
3067b6e3f3 |
|
|
@ -19,7 +19,7 @@ use middlewares::inject_user_data;
|
|||
use google_oauth::{login, logout, google_auth_return};
|
||||
use routes::{about, contact, cottagecalendar, dashboard, index, profile, user_profile, useradmin};
|
||||
use user::{add_user_role, delete_user_role, UserData};
|
||||
use wishlist::{user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item, user_wishlist_received_item, wishlists};
|
||||
use wishlist::{user_wishlist, user_wishlist_add, user_wishlist_add_item, user_wishlist_bought_item, user_wishlist_edit_item, user_wishlist_received_item, user_wishlist_save_item, wishlists};
|
||||
//use email::send_emails;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
@ -60,6 +60,7 @@ async fn main() {
|
|||
.route("/wishlists", get(wishlists))
|
||||
.route("/userwishlist/:user_id", get(user_wishlist))
|
||||
.route("/userwishlist/add/:user_id", get(user_wishlist_add).post(user_wishlist_add_item))
|
||||
.route("/userwishlist/edit/:item_id", get(user_wishlist_edit_item).post(user_wishlist_save_item))
|
||||
.route("/userwishlist/bought/:user_id", get(user_wishlist_bought_item))
|
||||
.route("/userwishlist/received/:user_id", get(user_wishlist_received_item))
|
||||
.nest_service("/assets", ServeDir::new("templates/assets")
|
||||
|
|
|
|||
|
|
@ -162,6 +162,20 @@ pub async fn delete_user_role(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn get_user_wishlist_item_by_id(item_id: i64, db_pool: &SqlitePool) -> UserWishlistItem {
|
||||
// Get wish list items for the user
|
||||
let user_wishlist_item = sqlx::query_as(
|
||||
r#"select id, created_at, created_by, updated_at, updated_by, user_id, item, item_url, purchased_by, received_at
|
||||
from wishlist_items where id = ?"#
|
||||
)
|
||||
.bind(item_id)
|
||||
.fetch_one(db_pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
user_wishlist_item
|
||||
}
|
||||
|
||||
pub async fn get_user_wishlist_items(user_id: i64, db_pool: &SqlitePool) -> Vec<UserWishlistItem> {
|
||||
// Get wish list items for the user
|
||||
let user_wishlist_items = sqlx::query_as(
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use http::StatusCode;
|
|||
use serde::Deserialize;
|
||||
use sqlx::{SqlitePool, Row};
|
||||
|
||||
use crate::{middlewares::is_authorized, user::{get_user_roles_display, get_user_wishlist_items, UserData, UserWishlistItem}};
|
||||
use crate::{middlewares::is_authorized, user::{get_user_roles_display, get_user_wishlist_item_by_id, get_user_wishlist_items, UserData, UserWishlistItem}};
|
||||
|
||||
struct HtmlTemplate<T>(T);
|
||||
|
||||
|
|
@ -132,6 +132,16 @@ struct UserWishListAddTemplate {
|
|||
user_wishlist_items: Vec<crate::user::UserWishlistItem>,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "userwishlistedit.html")]
|
||||
struct UserWishListEditTemplate {
|
||||
logged_in: bool,
|
||||
name: String,
|
||||
user: UserData,
|
||||
user_roles: Vec<crate::user::UserRolesDisplay>,
|
||||
user_wishlist_item: crate::user::UserWishlistItem,
|
||||
}
|
||||
|
||||
pub async fn user_wishlist_add(
|
||||
Path(user_id): Path<i64>,
|
||||
State(db_pool): State<SqlitePool>,
|
||||
|
|
@ -206,6 +216,67 @@ pub async fn user_wishlist_add_item(
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn user_wishlist_edit_item(
|
||||
Path(item_id): Path<i64>,
|
||||
State(db_pool): State<SqlitePool>,
|
||||
Extension(user_data): Extension<Option<UserData>>,
|
||||
) -> impl IntoResponse {
|
||||
// Extract the user's name from the user data.
|
||||
let user_name = user_data.as_ref().map(|s| s.name.clone());
|
||||
let logged_in = user_data.is_some();
|
||||
let name = user_name.unwrap_or_default();
|
||||
|
||||
let userid = user_data.as_ref().map(|s| s.id.clone()).unwrap_or_default();
|
||||
|
||||
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
||||
// Get user roles
|
||||
let user_roles = get_user_roles_display(userid, &db_pool.clone()).await;
|
||||
|
||||
// Get user wishlist items
|
||||
let user_wishlist_item = get_user_wishlist_item_by_id(item_id, &db_pool.clone()).await;
|
||||
|
||||
// Create the wishlist template.
|
||||
let template = UserWishListEditTemplate {
|
||||
logged_in,
|
||||
name,
|
||||
user: user_data.unwrap(),
|
||||
user_roles,
|
||||
user_wishlist_item,
|
||||
};
|
||||
return HtmlTemplate(template).into_response();
|
||||
} else {
|
||||
Redirect::to("/").into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn user_wishlist_save_item(
|
||||
Path(item_id): Path<i64>,
|
||||
State(db_pool): State<SqlitePool>,
|
||||
Extension(user_data): Extension<Option<UserData>>,
|
||||
Form(item_form): Form<ItemForm>
|
||||
) -> impl IntoResponse {
|
||||
if is_authorized("/wishlist", user_data.clone(), db_pool.clone()).await {
|
||||
// Insert new item to database
|
||||
let now = Utc::now().timestamp();
|
||||
|
||||
sqlx::query("update wishlist_items set updated_at = ?, updated_by = ?, item = ?, item_url = ? where id = ?")
|
||||
.bind(now) // Updated now
|
||||
.bind(user_data.as_ref().unwrap().id) // Updated by current user
|
||||
.bind(item_form.item)
|
||||
.bind(item_form.item_url)
|
||||
.bind(item_id)
|
||||
.execute(&db_pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user_id = user_data.as_ref().unwrap().id;
|
||||
let redirect_string = format!("/userwishlist/{user_id}");
|
||||
Redirect::to(&redirect_string).into_response()
|
||||
} else {
|
||||
Redirect::to("/").into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn user_wishlist_bought_item(
|
||||
Path(user_id): Path<i64>,
|
||||
State(db_pool): State<SqlitePool>,
|
||||
|
|
|
|||
|
|
@ -11,47 +11,75 @@
|
|||
<meta name="author" content="">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css'>
|
||||
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;500;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: "Montserrat", sans-serif;
|
||||
}
|
||||
</style>
|
||||
{% block links %}{% endblock links %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="row fixed-top sticky-top">
|
||||
<nav class="navbar navbar-expand-sm bg-light">
|
||||
<!-- Links -->
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/contactus">Contact Us</a>
|
||||
</li>
|
||||
{% if logged_in %}
|
||||
<li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/profile">{{ name }}</a></li>
|
||||
{% else %}
|
||||
<li class="nav-item"><a class="nav-link" href="/login">Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="#">Jean-Marie Family</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/about">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/contactus">Contact Us</a>
|
||||
</li>
|
||||
{% if logged_in %}
|
||||
<li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/profile">{{ name }}</a></li>
|
||||
{% else %}
|
||||
<li class="nav-item"><a class="nav-link" href="/login">Login</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div class="row flex-grow-1">
|
||||
{% block content %}{% endblock content %}
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="row fixed-bottom sticky-bottom">
|
||||
<div class="container-fluid text-center bg-light">
|
||||
<!-- FOOTER -->
|
||||
<footer>
|
||||
<p>© 2024 Jean-Marie family</p>
|
||||
</footer>
|
||||
</div><!-- /.container -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS Bundle with Popper -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||
crossorigin="anonymous"></script>
|
||||
{% block scripts %}{% endblock scripts %}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,8 +1,452 @@
|
|||
{% extends "authorized.html" %}
|
||||
{% block links %}
|
||||
<link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/@fullcalendar/core@4.2.0/main.min.css'>
|
||||
<link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/@fullcalendar/daygrid@4.3.0/main.min.css'>
|
||||
|
||||
<style>
|
||||
#calendar {
|
||||
max-width: 800px;
|
||||
margin: 40px auto;
|
||||
background: #fff;
|
||||
padding: 15px;
|
||||
|
||||
}
|
||||
|
||||
.fc-event {
|
||||
border: 1px solid #eee !important;
|
||||
}
|
||||
|
||||
.fc-content {
|
||||
padding: 3px !important;
|
||||
}
|
||||
|
||||
.fc-content .fc-title {
|
||||
display: block !important;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.fc-customButton-button {
|
||||
font-size: 13px !important;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group>label {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#delete-modal .modal-footer>.btn {
|
||||
|
||||
border-radius: 3px !important;
|
||||
padding: 0px 8px !important;
|
||||
font-size: 15px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.fc-scroller {
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.3);
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
/* .context-menu.show {
|
||||
display: block;
|
||||
} */
|
||||
|
||||
.context-menu ul {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
}
|
||||
|
||||
.context-menu ul>li {
|
||||
display: block;
|
||||
;
|
||||
padding: 5px 15px;
|
||||
list-style-type: none;
|
||||
color: #333;
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
margin: 0 auto;
|
||||
transition: 0.10s;
|
||||
font-size: 13px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.context-menu ul>li:hover {
|
||||
color: #fff;
|
||||
background-color: #007bff;
|
||||
border-radius: 2px;
|
||||
|
||||
}
|
||||
|
||||
.fa,
|
||||
.fas {
|
||||
font-size: 13px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
||||
{% endblock links %}
|
||||
{% block center %}
|
||||
<h1>Cottage Calendar</h1>
|
||||
<div class="ratio ratio-4x3">
|
||||
<!--<iframe src="https://calendar.google.com/calendar/embed?src=jeanmarie.cottage%40gmail.com&ctz=America%2FToronto" style="border: 0" width="800" height="400" frameborder="0" scrolling="no"></iframe>-->
|
||||
<iframe width="300" height="430" src="https://nextcloud.jean-marie.ca/index.php/apps/calendar/embed/c3AGT6MXBPs8tzAC"></iframe>
|
||||
<div id='calendar'></div>
|
||||
|
||||
<!-- Add modal -->
|
||||
|
||||
<div class="modal fade edit-form" id="form" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header border-bottom-0">
|
||||
<h5 class="modal-title" id="modal-title">Add Event</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<form id="myForm">
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-danger " role="alert" id="danger-alert" style="display: none;">
|
||||
End date should be greater than start date.
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="event-title">Event name <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="event-title" placeholder="Enter event name"
|
||||
required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="start-date">Start date <span class="text-danger">*</span></label>
|
||||
<input type="date" class="form-control" id="start-date" placeholder="start-date" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="end-date">End date - <small class="text-muted">Optional</small></label>
|
||||
<input type="date" class="form-control" id="end-date" placeholder="end-date">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="event-color">Color</label>
|
||||
<input type="color" class="form-control" id="event-color" value="#3788d8">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-top-0 d-flex justify-content-center">
|
||||
<button type="submit" class="btn btn-success" id="submit-button">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock center %}
|
||||
|
||||
<!-- Delete Modal -->
|
||||
<div class="modal fade" id="delete-modal" tabindex="-1" role="dialog" aria-labelledby="delete-modal-title"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="delete-modal-title">Confirm Deletion</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body text-center" id="delete-modal-body">
|
||||
Are you sure you want to delete the event?
|
||||
</div>
|
||||
<div class="modal-footer border-0">
|
||||
<button type="button" class="btn btn-secondary rounded-sm" data-dismiss="modal"
|
||||
id="cancel-button">Cancel</button>
|
||||
<button type="button" class="btn btn-danger rounded-lg" id="delete-button">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock center %}
|
||||
{% block scripts %}
|
||||
<script src='https://cdn.jsdelivr.net/npm/@fullcalendar/core@4.2.0/main.min.js'></script>
|
||||
<script src='https://cdn.jsdelivr.net/npm/@fullcalendar/daygrid@4.2.0/main.js'></script>
|
||||
<script src='https://cdn.jsdelivr.net/npm/@fullcalendar/interaction@4.2.0/main.js'></script>
|
||||
<script src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js'></script>
|
||||
<script src='https://cdn.jsdelivr.net/npm/uuid@8.3.2/dist/umd/uuidv4.min.js'></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const calendarEl = document.getElementById('calendar');
|
||||
const myModal = new bootstrap.Modal(document.getElementById('form'));
|
||||
const dangerAlert = document.getElementById('danger-alert');
|
||||
const close = document.querySelector('.btn-close');
|
||||
|
||||
|
||||
|
||||
|
||||
const myEvents = JSON.parse(localStorage.getItem('events')) || [
|
||||
{
|
||||
id: uuidv4(),
|
||||
title: `Edit Me`,
|
||||
start: '2023-04-11',
|
||||
backgroundColor: 'red',
|
||||
allDay: false,
|
||||
editable: false,
|
||||
},
|
||||
{
|
||||
id: uuidv4(),
|
||||
title: `Delete me`,
|
||||
start: '2023-04-17',
|
||||
end: '2023-04-21',
|
||||
|
||||
allDay: false,
|
||||
editable: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
const calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
customButtons: {
|
||||
customButton: {
|
||||
text: 'Add Event',
|
||||
click: function () {
|
||||
myModal.show();
|
||||
const modalTitle = document.getElementById('modal-title');
|
||||
const submitButton = document.getElementById('submit-button');
|
||||
modalTitle.innerHTML = 'Add Event'
|
||||
submitButton.innerHTML = 'Add Event'
|
||||
submitButton.classList.remove('btn-primary');
|
||||
submitButton.classList.add('btn-success');
|
||||
|
||||
|
||||
|
||||
close.addEventListener('click', () => {
|
||||
myModal.hide()
|
||||
})
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
header: {
|
||||
center: 'customButton', // add your custom button here
|
||||
right: 'today, prev,next '
|
||||
},
|
||||
plugins: ['dayGrid', 'interaction'],
|
||||
allDay: false,
|
||||
editable: true,
|
||||
selectable: true,
|
||||
unselectAuto: false,
|
||||
displayEventTime: false,
|
||||
events: myEvents,
|
||||
eventRender: function (info) {
|
||||
info.el.addEventListener('contextmenu', function (e) {
|
||||
e.preventDefault();
|
||||
let existingMenu = document.querySelector('.context-menu');
|
||||
existingMenu && existingMenu.remove();
|
||||
let menu = document.createElement('div');
|
||||
menu.className = 'context-menu';
|
||||
menu.innerHTML = `<ul>
|
||||
<li><i class="fas fa-edit"></i>Edit</li>
|
||||
<li><i class="fas fa-trash-alt"></i>Delete</li>
|
||||
</ul>`;
|
||||
|
||||
const eventIndex = myEvents.findIndex(event => event.id === info.event.id);
|
||||
|
||||
|
||||
document.body.appendChild(menu);
|
||||
menu.style.top = e.pageY + 'px';
|
||||
menu.style.left = e.pageX + 'px';
|
||||
|
||||
// Edit context menu
|
||||
|
||||
menu.querySelector('li:first-child').addEventListener('click', function () {
|
||||
menu.remove();
|
||||
|
||||
const editModal = new bootstrap.Modal(document.getElementById('form'));
|
||||
const modalTitle = document.getElementById('modal-title');
|
||||
const titleInput = document.getElementById('event-title');
|
||||
const startDateInput = document.getElementById('start-date');
|
||||
const endDateInput = document.getElementById('end-date');
|
||||
const colorInput = document.getElementById('event-color');
|
||||
const submitButton = document.getElementById('submit-button');
|
||||
const cancelButton = document.getElementById('cancel-button');
|
||||
modalTitle.innerHTML = 'Edit Event';
|
||||
titleInput.value = info.event.title;
|
||||
startDateInput.value = moment(info.event.start).format('YYYY-MM-DD');
|
||||
endDateInput.value = moment(info.event.end, 'YYYY-MM-DD').subtract(1, 'day').format('YYYY-MM-DD');
|
||||
colorInput.value = info.event.backgroundColor;
|
||||
submitButton.innerHTML = 'Save Changes';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
editModal.show();
|
||||
|
||||
submitButton.classList.remove('btn-success')
|
||||
submitButton.classList.add('btn-primary')
|
||||
|
||||
// Edit button
|
||||
|
||||
submitButton.addEventListener('click', function () {
|
||||
const updatedEvents = {
|
||||
id: info.event.id,
|
||||
title: titleInput.value,
|
||||
start: startDateInput.value,
|
||||
end: moment(endDateInput.value, 'YYYY-MM-DD').add(1, 'day').format('YYYY-MM-DD'),
|
||||
backgroundColor: colorInput.value
|
||||
}
|
||||
|
||||
if (updatedEvents.end <= updatedEvents.start) { // add if statement to check end date
|
||||
dangerAlert.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
const eventIndex = myEvents.findIndex(event => event.id === updatedEvents.id);
|
||||
myEvents.splice(eventIndex, 1, updatedEvents);
|
||||
|
||||
localStorage.setItem('events', JSON.stringify(myEvents));
|
||||
|
||||
// Update the event in the calendar
|
||||
const calendarEvent = calendar.getEventById(info.event.id);
|
||||
calendarEvent.setProp('title', updatedEvents.title);
|
||||
calendarEvent.setStart(updatedEvents.start);
|
||||
calendarEvent.setEnd(updatedEvents.end);
|
||||
calendarEvent.setProp('backgroundColor', updatedEvents.backgroundColor);
|
||||
|
||||
|
||||
|
||||
editModal.hide();
|
||||
|
||||
})
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
||||
// Delete menu
|
||||
menu.querySelector('li:last-child').addEventListener('click', function () {
|
||||
const deleteModal = new bootstrap.Modal(document.getElementById('delete-modal'));
|
||||
const modalBody = document.getElementById('delete-modal-body');
|
||||
const cancelModal = document.getElementById('cancel-button');
|
||||
modalBody.innerHTML = `Are you sure you want to delete <b>"${info.event.title}"</b>`
|
||||
deleteModal.show();
|
||||
|
||||
const deleteButton = document.getElementById('delete-button');
|
||||
deleteButton.addEventListener('click', function () {
|
||||
myEvents.splice(eventIndex, 1);
|
||||
localStorage.setItem('events', JSON.stringify(myEvents));
|
||||
calendar.getEventById(info.event.id).remove();
|
||||
deleteModal.hide();
|
||||
menu.remove();
|
||||
|
||||
});
|
||||
|
||||
cancelModal.addEventListener('click', function () {
|
||||
deleteModal.hide();
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
});
|
||||
document.addEventListener('click', function () {
|
||||
menu.remove();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
eventDrop: function (info) {
|
||||
let myEvents = JSON.parse(localStorage.getItem('events')) || [];
|
||||
const eventIndex = myEvents.findIndex(event => event.id === info.event.id);
|
||||
const updatedEvent = {
|
||||
...myEvents[eventIndex],
|
||||
id: info.event.id,
|
||||
title: info.event.title,
|
||||
start: moment(info.event.start).format('YYYY-MM-DD'),
|
||||
end: moment(info.event.end).format('YYYY-MM-DD'),
|
||||
backgroundColor: info.event.backgroundColor
|
||||
};
|
||||
myEvents.splice(eventIndex, 1, updatedEvent); // Replace old event data with updated event data
|
||||
localStorage.setItem('events', JSON.stringify(myEvents));
|
||||
console.log(updatedEvent);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
calendar.on('select', function (info) {
|
||||
|
||||
const startDateInput = document.getElementById('start-date');
|
||||
const endDateInput = document.getElementById('end-date');
|
||||
startDateInput.value = info.startStr;
|
||||
const endDate = moment(info.endStr, 'YYYY-MM-DD').subtract(1, 'day').format('YYYY-MM-DD');
|
||||
endDateInput.value = endDate;
|
||||
if (startDateInput.value === endDate) {
|
||||
endDateInput.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
calendar.render();
|
||||
|
||||
const form = document.querySelector('form');
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault(); // prevent default form submission
|
||||
|
||||
// retrieve the form input values
|
||||
const title = document.querySelector('#event-title').value;
|
||||
const startDate = document.querySelector('#start-date').value;
|
||||
const endDate = document.querySelector('#end-date').value;
|
||||
const color = document.querySelector('#event-color').value;
|
||||
const endDateFormatted = moment(endDate, 'YYYY-MM-DD').add(1, 'day').format('YYYY-MM-DD');
|
||||
const eventId = uuidv4();
|
||||
|
||||
console.log(eventId);
|
||||
|
||||
if (endDateFormatted <= startDate) { // add if statement to check end date
|
||||
dangerAlert.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
const newEvent = {
|
||||
id: eventId,
|
||||
title: title,
|
||||
start: startDate,
|
||||
end: endDateFormatted,
|
||||
allDay: false,
|
||||
backgroundColor: color
|
||||
};
|
||||
|
||||
// add the new event to the myEvents array
|
||||
myEvents.push(newEvent);
|
||||
|
||||
// render the new event on the calendar
|
||||
calendar.addEvent(newEvent);
|
||||
|
||||
// save events to local storage
|
||||
localStorage.setItem('events', JSON.stringify(myEvents));
|
||||
|
||||
myModal.hide();
|
||||
form.reset();
|
||||
});
|
||||
|
||||
myModal._element.addEventListener('hide.bs.modal', function () {
|
||||
dangerAlert.style.display = 'none';
|
||||
form.reset();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
{% endblock scripts %}
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Item</th>
|
||||
<th scope="col">Link</th>
|
||||
<th scope="col">State</th>
|
||||
<th scope="col">Action</th>
|
||||
</tr>
|
||||
|
|
@ -23,7 +24,12 @@
|
|||
<tbody>
|
||||
{% for user_wishlist_item in user_wishlist_items %}
|
||||
<tr>
|
||||
<td><a href="{{ user_wishlist_item.item_url }}">{{ user_wishlist_item.item }}</a></td>
|
||||
{% if my_wishlist %}
|
||||
<td><a href="/userwishlist/edit/{{ user_wishlist_item.id }}">{{ user_wishlist_item.item }}</a></td>
|
||||
{% else %}
|
||||
<td>{{ user_wishlist_item.item }}</td>
|
||||
{% endif %}
|
||||
<td><a href="{{ user_wishlist_item.item_url }}">URL</a></td>
|
||||
{% if user_wishlist_item.received_at > 0 %}
|
||||
<td>Got it!</td>
|
||||
{% else %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
{% extends "authorized.html" %}
|
||||
{% block title %}Edit item in {{ user.given_name }} Wishlist{% endblock %}
|
||||
{% block center %}
|
||||
<h1>Add Item to Wishlist</h1>
|
||||
<form method="post">
|
||||
<div class="my-3">
|
||||
<label for="item">Item</label>
|
||||
<input type="text" class="form-control my-1" id="item" name="item" value="{{ user_wishlist_item.item }}" required>
|
||||
</div>
|
||||
<div class="my-3">
|
||||
<label for="item_url">URL</label>
|
||||
<input type="text" class="form-control my-1" id="item_url" name="item_url" value="{{ user_wishlist_item.item_url }}">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
<br/>
|
||||
{% endblock center %}
|
||||
Loading…
Reference in New Issue