Commit 38643bd9 by Administrator

Merge branch 'evolution_bdm' into 'dev_cooperatic'

Integration Evolution bdm

See merge request !87
parents 8dddab8e 4cf3417f
Pipeline #1557 passed with stage
in 1 minute 32 seconds
/static/
/outils/static/
\ No newline at end of file
......@@ -41,7 +41,7 @@ module.exports = {
"block-scoped-var": "off",
"block-spacing": "warn",
"brace-style": "warn",
"callback-return": "warn",
"callback-return": "off",
"camelcase": "off",
"capitalized-comments": "off",
"class-methods-use-this": "error",
......
......@@ -17,3 +17,5 @@ db.sqlite3
shop/shop_admin_settings.json
shop/errors.log
.idea
members/settings.json
.DS_Store
......@@ -2,6 +2,7 @@
MAG_NAME = 'Cleme'
OFFICE_NAME = ''
MAX_BEGIN_HOUR = '19:00'
COMPANY_CODE = 'lacagette'
COMPANY_NAME = 'La Cagette'
WELCOME_ENTRANCE_MSG = 'Bienvenue à La Cagette !'
WELCOME_MAIL_SUBJECT = 'Dernière étape de votre inscription à la Cagette.'
......@@ -54,6 +55,10 @@ EM_URL = ''
RECEPTION_MERGE_ORDERS_PSWD = 'jpsrcp'
RECEPTION_PB = "Ici, vous pouvez signaler toute anomalie lors d'une réception, les produits non commandés, cassés ou pourris. \
Merci d'indiquer un maximum d'informations, le nom du produit et son code barre."
DISPLAY_COL_AUTRES = False
DAV_PATH = '/shared_dir/dav/'
TOOLS_SERVER = 'https://outils.lacagette-coop.fr'
......@@ -91,7 +96,7 @@ DISCOUNT_SHELFS_IDS = [74]
FL_SHELFS = [16, 17, 18]
VRAC_SHELFS = [20, 38]
SHIFT_EXCHANGE_DAYS_TO_HIDE = ''
SHIFT_EXCHANGE_DAYS_TO_HIDE = '0'
SHIFT_INFO = """A la cagette, un service est une plage de trois heures un jour en particulier, par exemple : le mardi 25/09/2018 à 13h15.
<br />A l'inverse, un créneau est une plage de trois heures régulière, par exemple, tous les mardi de semaine A à 13h15."""
PB_INSTRUCTIONS = """Si j'ai un problème, que je suis désinscrit, que je veux changer de créneaux ou quoi que ce soit, merci de vous rendre dans la section \"J'ai un problème\" sur le site web de <a href=\"https://lacagette-coop.fr/?MonEspaceMembre\">La Cagette</a>"""
......@@ -99,10 +104,23 @@ PB_INSTRUCTIONS = """Si j'ai un problème, que je suis désinscrit, que je veux
ENTRANCE_COME_FOR_SHOPING_MSG = "Hey coucou toi ! Cet été nous sommes plus de <strong>1000 acheteur·euses</strong> pour seulement <strong>300 coopérateur·rice·s</strong> en service. <br />Tu fais tes courses à La Cagette cet été ?<br/> Inscris-toi sur ton espace membre !"
ENTRANCE_EXTRA_BUTTONS_DISPLAY = False
ENTRANCE_EASY_SHIFT_VALIDATE = True
ENTRANCE_MISSED_SHIFT_BEGIN_MSG = """La période pendant laquelle il est possible de s'enregistrer est close.<br />
Merci de remplir le formulaire <em>"arrivé·e en retard"</em> <br/>
que vous trouverez <em>sur le site internet de La Cagette</em>
dans la rubrique<br />
"Espace Membre" > "J\'ai un problème ou une demande".<br/>
Le bureau des membres traitera votre demande !'
"""
ENTRANCE_EASY_SHIFT_VALIDATE_MSG = """Si vous faites un service dans un comité, merci de <br/>
valider votre présence en cherchant<br/>
votre nom ou numéro ci-dessous
"""
# Members space / shifts
UNSUBSCRIBED_FORM_LINK = 'https://docs.google.com/forms/d/e/1FAIpQLScWcpls-ruYIp7HdrjRF1B1TyuzdqhvlUIcUWynbEujfj3dTg/viewform'
UNSUBSCRIBED_MSG = 'Vous êtes désincrit·e, merci de remplir <a href="https://docs.google.com/forms/d/e/1FAIpQLSfPiC2PkSem9x_B5M7LKpoFNLDIz0k0V5I2W3Mra9AnqnQunw/viewform">ce formulaire</a> pour vous réinscrire sur un créneau.<br />Vous pouvez également contacter le Bureau des Membres en remplissant <a href="https://docs.google.com/forms/d/e/1FAIpQLSeZP0m5-EXPVJxEKJk6EjwSyZJtnbiGdYDuAeFI3ENsHAOikg/viewform">ce formulaire</a>'
CONFIRME_PRESENT_BTN = 'Clique ici pour valider ta présence'
RECEPTION_PB = "Ici, vous pouvez signaler toute anomalie lors d'une réception, les produits non commandés, cassés ou pourris. \
Merci d'indiquer un maximum d'informations, le nom du produit et son code barre. \
Dans le cas de produits déteriorés, merci d'envoyer une photo avec votre téléphone à [Adresse_email]"
......@@ -110,5 +128,11 @@ RECEPTION_PB = "Ici, vous pouvez signaler toute anomalie lors d'une réception,
# display or not column "Autres" in reception process
DISPLAY_COL_AUTRES = False
# Should block service exchange if old service is happening in less than 24h
BLOCK_SERVICE_EXCHANGE_24H_BEFORE = True
# URL to the metabase dashboard for orders helper
ORDERS_HELPER_METABASE_URL = "https://metabase.lacagette-coop.fr/dashboard/16"
ORDERS_HELPER_METABASE_URL = "url_meta_base"
# New members space
USE_NEW_MEMBERS_SPACE = True
START_DATE_FOR_SHIFTS_HISTORY = "2018-01-01"
AMNISTIE_DATE= "2021-11-24 00:00:00"
......@@ -6,6 +6,7 @@ OPEN_ON_SUNDAY = True
MAG_NAME = ''
OFFICE_NAME = ''
COMPANY_CODE = 'lgds'
COMPANY_NAME = 'Les Grains de Sel'
MAX_BEGIN_HOUR = '19:00'
WELCOME_ENTRANCE_MSG = 'Bienvenue aux Grains de Sel!'
......@@ -92,3 +93,6 @@ ENTRANCE_FTOP_BUTTON_DISPLAY = False
CUSTOM_CSS_FILES = {'all': ['common_lgds.css'],
'members': ['inscription_lgds.css','member_lgds.css']}
# Should block service exchange if old service is happening in less than 24h
BLOCK_SERVICE_EXCHANGE_24H_BEFORE = False
\ No newline at end of file
"""Company specific data values."""
COMPANY_CODE = 'supercafoutch'
"""Odoo coop specific constants ."""
EMAIL_DOMAIN = 'supercafoutch.fr'
......@@ -88,3 +89,6 @@ PROMOTE_SHELFS_IDS = []
DISCOUNT_SHELFS_IDS = []
FL_SHELFS = []
VRAC_SHELFS = []
# Should block service exchange if old service is happening in less than 24h
BLOCK_SERVICE_EXCHANGE_24H_BEFORE = False
\ No newline at end of file
......@@ -36,14 +36,16 @@ video {max-width:none;}
#barcode_base {width:50px;float:left;}
.coop-info {min-width: 300px;padding:5px;}
#lat_menu button {margin-bottom:5px;}
.col-6.big {font-size:200%; border: 2px solid red; padding:10px; text-align:center; background: #ffffff;}
.col-6.big {font-size:200%; border: 2px solid red; padding:10px; text-align:center; background: #FFF;}
#cooperative_state {font-size:150%; font-weight:bold;}
h1 .member_name {font-weight: bold;}
#current_shift_title, .members_list
{border:1px solid #000; border-radius: 5px; padding:5px; margin-bottom:15px;background:#FFF;}
.members_list {list-style: none;}
.members_list li {display:block;margin-bottom:5px;}
.members_list li.btn--inverse {background: #449d44 !important; cursor:not-allowed;}
.members_list li.btn--inverse {background: #449d44; cursor:not-allowed; color: #FFF; }
.members_list li.btn--inverse.late {background-color: #de9b00; color: white}
#service_entry_success {font-size: x-large;}
#service_entry_success .explanations {margin: 25px 0; font-size: 18px;}
......
.page_body{
position: relative;
}
.header {
margin: 1.5rem 0;
}
.login_area {
position: absolute;
top: 0;
left: 0;
right: 0;
}
.tabs {
margin-top: 1em;
margin-bottom: 1em;
overflow: hidden;
}
.tabs .tab {
background-color: #f1f1f1;
border: 1px solid #ccc;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}
.tabs .tab:hover {
background-color: #ccc;
}
.tabs .active {
background-color: transparent;
border: 1px solid #ccc;
border-width: 1px 0 0 0;
}
.tabs .active:hover {
background-color: white;
}
.tab_content {
animation: fadeEffect 1s; /* Fading effect takes 1 second */
}
/* Go from zero to full opacity */
@keyframes fadeEffect {
from {opacity: 0;}
to {opacity: 1;}
}
#tab_makeups_content {
padding: 2rem 0;
}
#table_top_area {
display: flex;
justify-content: space-between;
}
#decrement_selected_members_makeups {
display: none;
}
.table_area {
margin-top: 20px;
}
.decrement_makeup {
margin-left: 10px;
}
.decrement_makeup, .increment_makeup {
padding: 0.4rem 1.25rem;
}
.select_member_cb {
cursor: pointer;
}
/* Search membres area */
#add_members_area {
margin-top: 30px;
}
#add_members_form_area {
display:flex;
align-items: center;
}
#search_member_form {
margin-left: 10px;
}
.search_member_results_area {
margin-top: 15px;
display: flex;
align-items: center;
}
.search_member_results {
display: flex;
flex-wrap: wrap;
}
.btn_possible_member {
margin: 0 1rem;
}
\ No newline at end of file
......@@ -55,10 +55,10 @@ sync.on('change', function (info) {
update_completed_count();
online = true;
})
.on('denied', function (err) {
.on('denied', function () {
// a document failed to replicate (e.g. due to permissions)
})
.on('complete', function (info) {
.on('complete', function () {
// handle complete
})
.on('error', function (err) {
......@@ -87,9 +87,7 @@ function new_coop_validation() {
coop_list_view.hide();
schoice_view.hide();
ncoop_view.hide();
var barcode_base = current_coop.barcode_base;
var st = get_shift_name(current_coop.shift_template.data);
//coop_registration_details.find('.numbox').text('N° '+ barcode_base);
coop_registration_details.find('.shift_template').text(st);
process_state.html(current_coop.firstname + ' ' +current_coop.lastname);
......@@ -148,12 +146,13 @@ function _really_save_new_coop(email, fname, lname, cap, pm, cn, bc, msex) {
if (email != current_email) {
//delete current_coop after copying revelant data
dbc.remove(current_email, coop._rev, function(err, response) {
dbc.remove(current_email, coop._rev, function(err) {
if (err) {
return console.log(err);
}
//console.log(response);
return null;
});
delete coop._rev;
......@@ -414,6 +413,8 @@ function get_latest_odoo_coop_bb() {
return latest_odoo_coop_bb;
}
return null;
}
function generate_email() {
......@@ -461,7 +462,7 @@ function setLocalInProcess(lip) {
localStorage.setItem("in_process", JSON.stringify(lip));
}
function keep_in_process_work(event) {
function keep_in_process_work() {
//If data registration is in process, save it in localStorage
if (current_coop != null && typeof (current_coop.shift_template) == "undefined") {
local_in_process = getLocalInProcess();
......
......@@ -9,7 +9,7 @@ if (coop_is_connected()) {
env_template = $('#templates [data-type="envelops"]');
// PouchDB sync actions listeners
sync.on('change', function (info) {
sync.on('change', function () {
// handle change
......@@ -27,10 +27,10 @@ if (coop_is_connected()) {
//update_completed_count();
online = true;
})
.on('denied', function (err) {
.on('denied', function () {
// a document failed to replicate (e.g. due to permissions)
})
.on('complete', function (info) {
.on('complete', function () {
// handle complete
})
.on('error', function (err) {
......@@ -333,7 +333,7 @@ if (coop_is_connected()) {
dsha1 = sha1(jss),
member = {_id: dsha1, data: data, hash: dsha1 };
dbc.put(member, function callback(err, result) {
dbc.put(member, function callback(err) {
if (!err) {
closeModal();
page_cleaning();
......@@ -409,6 +409,7 @@ if (coop_is_connected()) {
$('.item').click(display_possible_actions);
// dispatch_coops_in_boxes();
return null;
});
console.log(problematic_members);
......
......@@ -33,7 +33,6 @@ var shift_members = $('#current_shift_members');
var service_validation = $('#service_validation');
var validation_last_call = 0;
var rattrapage_wanted = $('[data-next="rattrapage_1"]');
var rattrapage_validation = $('#rattrapage_validation');
var webcam_is_attached = false;
var photo_advice = $('#photo_advice');
var photo_studio = $('#photo_studio');
......@@ -92,7 +91,7 @@ function fill_member_slide(member) {
}
html_elts.image_medium.html('<img src="'+img_src+'" width="128" />');
html_elts.cooperative_state.html(member.cooperative_state);
if (member.cooperative_state == 'Désinscrit(e)') coop_info.addClass('b_red');
if (member.cooperative_state == 'Désinscrit(e)' || member.cooperative_state == 'Rattrapage') coop_info.addClass('b_red');
else if (member.cooperative_state == 'En alerte' || member.cooperative_state == 'Délai accordé') coop_info.addClass('b_orange');
if (member.shifts.length > 0) {
......@@ -264,12 +263,18 @@ function fill_service_entry(s) {
if (s.members) {
m_list = '<ul class="members_list">';
// if (typeof s.late != "undefined" && s.late == true) {
// m_list = '<ul class="members_list late">';
// }
$.each(s.members, function(i, e) {
var li_class = "btn";
var li_data = "";
if (e.state == "done") {
li_class += "--inverse";
if (e.is_late == true) {
li_class += " late";
}
} else {
li_data = ' data-rid="'+e.id+'" data-mid="'+e.partner_id[0]+'"';
}
......@@ -392,7 +397,7 @@ function fill_service_entry_sucess(member) {
var points = member.display_std_points;
if (member.in_ftop_team == true) {
if (member.shift_type == 'ftop') {
points = member.display_ftop_points;
}
pages.service_entry_success.find('span.points').text(points);
......@@ -408,7 +413,7 @@ function fill_service_entry_sucess(member) {
var service_verb = 'est prévu';
if (member.next_shift) {
if (member.in_ftop_team == true
if (member.shift_type == 'ftop'
&& member.next_shift.shift_type == "ftop") {
var start_elts = member.next_shift.start.split(' à ');
......@@ -451,6 +456,8 @@ function record_service_presence() {
goto_page(pages.service_entry_success);
} else if (rData.res.error) {
alert(rData.res.error);
} else {
alert("Un problème est survenu. S'il persiste merci de le signaler à un responsable du magasin.");
}
}
loading2.hide();
......@@ -464,7 +471,7 @@ function fill_rattrapage_2() {
var msg = "Bienvenue pour ton rattrapage !";
var shift_ticket_id = selected_service.shift_ticket_ids[0];
if (current_displayed_member.in_ftop_team == true) {
if (current_displayed_member.shift_type == 'ftop') {
msg ="Bienvenue dans ce service !";
if (selected_service.shift_ticket_ids[1])
shift_ticket_id = selected_service.shift_ticket_ids[1];
......@@ -488,20 +495,6 @@ function fill_rattrapage_2() {
function init_webcam() {
try {
/*
Webcam.set({
width: $('#img_width').val(),
height: $('#img_height').val(),
dest_width: $('#img_dest_width').val(),
dest_height: $('#img_dest_height').val(),
crop_width: $('#img_crop_width').val(),
crop_height: $('#img_crop_height').val(),
image_format: 'jpeg',
jpeg_quality: 90
});
*/
Webcam.set({
width: 320,
height: 240,
......@@ -646,7 +639,7 @@ shift_members.on("click", '.btn[data-rid]', function() {
});
pages.shopping_entry.on('css', function(e) {
pages.shopping_entry.on('css', function() {
photo_advice.hide();
photo_studio.hide();
search_box_clear_html_elts();
......@@ -654,14 +647,14 @@ pages.shopping_entry.on('css', function(e) {
move_search_box(pages.rattrapage_1, pages.shopping_entry);
});
pages.service_entry.on('css', function(e) {
pages.service_entry.on('css', function() {
photo_advice.hide();
photo_studio.hide();
clean_service_entry();
get_service_entry_data();
});
pages.rattrapage_1.on('css', function(e) {
pages.rattrapage_1.on('css', function() {
search_box_clear_html_elts();
var msg = "Vous venez pour un rattrapage.";
......@@ -691,7 +684,7 @@ function ask_for_easy_shift_validation() {
{
coop_id: operator.id
},
function(err, result) {
function(err) {
if (!err) {
alert("1 point volant vient d'être ajouté.");
clean_search_for_easy_validate_zone();
......@@ -795,11 +788,10 @@ $(document).ready(function() {
let search_str = sm_search_member_input.val();
$.ajax({
url: '/members/search/' + search_str,
url: '/members/search/' + search_str + '/' + window.committees_shift_id,
dataType : 'json',
success: function(data) {
members_search_results = [];
for (member of data.res) {
if (member.shift_type == 'ftop') {
members_search_results.push(member);
......@@ -808,7 +800,7 @@ $(document).ready(function() {
display_possible_members();
},
error: function(data) {
error: function() {
err = {
msg: "erreur serveur lors de la recherche de membres",
ctx: 'easy_validate.search_members'
......
......@@ -46,10 +46,10 @@ sync.on('change', function (info) {
// replicate resumed (e.g. new changes replicating, user went back online)
online = true;
})
.on('denied', function (err) {
.on('denied', function () {
// a document failed to replicate (e.g. due to permissions)
})
.on('complete', function (info) {
.on('complete', function () {
// handle complete
})
.on('error', function (err) {
......@@ -113,7 +113,7 @@ function put_current_coop_in_buffer_db(callback) {
var can_continue = true;
if (typeof current_coop._old_id != "undefined") {
dbc.remove(current_coop._old_id, current_coop._rev, function(err, response) {
dbc.remove(current_coop._old_id, current_coop._rev, function(err) {
if (err) {
console.log(err); can_continue = false;
}
......@@ -131,7 +131,6 @@ function put_current_coop_in_buffer_db(callback) {
function process_new_warning(event) {
event.preventDefault();
var msg = warning_msg.val();
var btn = $(event.target).find('button');
openModal();
if (msg.length > 0) {
......@@ -208,7 +207,7 @@ function submit_full_coop_form() {
}
post_form(
'/members/coop_validated_data', form_data,
function(err, result) {
function(err) {
if (!err) {
setTimeout(after_save, 1500);
} else {
......@@ -551,6 +550,8 @@ function open_coop_form(e) {
console.error(error);
report_JS_error(error, 'prepa-odoo');
}
return null;
}
function ask_for_deletion() {
......@@ -693,6 +694,8 @@ function retrieve_all_coops() {
return b.timestamp - a.timestamp;
});
dispatch_coops_in_boxes();
return null;
});
} catch (err) {
error = {msg: err.name + ' : ' + err.message, ctx: 'retrieve_all_coops'};
......
......@@ -46,7 +46,8 @@ function display_current_coop_form() {
// form.find('[name="barcode_base"]').val(current_coop.barcode_base);
form.find('[name="email"]').val(current_coop._id);
if (current_coop.shift_template &&
current_coop.shift_template.data.type == 2) {
current_coop.shift_template.data.type == 2 &&
typeof manage_ftop != "undefined" && manage_ftop == true) {
$('#choosen_shift input').hide();
ftop_shift.val('Volant');
ftop_shift.show();
......
......@@ -92,7 +92,7 @@ function process_form_submission(event) {
openModal();
post_form(
'/members/coop_validated_data', form_data,
function(err, result) {
function(err) {
closeModal();
if (!err) {
......@@ -119,7 +119,7 @@ function process_form_submission(event) {
openModal();
post_form(
'/members/coop_warning_msg', data,
function(err, result) {
function(err) {
closeModal();
if (!err) {
$('#main_content').remove();
......
......@@ -36,11 +36,11 @@ urlpatterns = [
url(r'^verify_final_state$', views.verify_final_state),
url(r'^update_couchdb_barcodes$', views.update_couchdb_barcodes),
# Borne accueil
url(r'^search/(.+)', views.search),
url(r'^search/([^\/.]+)/?([0-9]*)', views.search),
url(r'^save_photo/([0-9]+)$', views.save_photo, name='save_photo'),
url(r'^services_at_time/([0-9TZ\-\: \.]+)/([0-9\-]+)$', views.services_at_time),
url(r'^service_presence/$', views.record_service_presence),
url(r'^record_absences$', views.record_absences),
url(r'^record_absences/?([0-9\-\ \:]*)$', views.record_absences),
url(r'^close_ftop_service$', views.close_ftop_service),
url(r'^get_credentials$', views.get_credentials),
url(r'^remove_data_from_couchdb$', views.remove_data_from_CouchDB),
......@@ -49,4 +49,11 @@ urlpatterns = [
url(r'^easy_validate_shift_presence$', views.easy_validate_shift_presence),
# conso / groupe recherche / socio
url(r'^panel_get_purchases$', views.panel_get_purchases),
# BDM
url(r'^save_partner_info$', views.save_partner_info),
# BDM - members admin
url(r'^admin$', admin.admin),
url(r'^get_makeups_members$', admin.get_makeups_members),
url(r'^update_members_makeups$', admin.update_members_makeups),
]
......@@ -28,8 +28,11 @@ def index(request):
"La période pendant laquelle il est possible de s'enregistrer est close."),
'ENTRANCE_EASY_SHIFT_VALIDATE_MSG': getattr(settings, 'ENTRANCE_EASY_SHIFT_VALIDATE_MSG',
'Je valide mon service "Comité"'),
'CONFIRME_PRESENT_BTN' : getattr(settings, 'CONFIRME_PRESENT_BTN', 'Présent.e')
'CONFIRME_PRESENT_BTN' : getattr(settings, 'CONFIRME_PRESENT_BTN', 'Présent.e'),
'LATE_MODE': getattr(settings, 'ENTRANCE_WITH_LATE_MODE', False),
'ENTRANCE_VALIDATE_PRESENCE_MESSAGE' : getattr(settings, 'ENTRANCE_VALIDATE_PRESENCE_MESSAGE', '')
}
for_shoping_msg = getattr(settings, 'ENTRANCE_COME_FOR_SHOPING_MSG', '')
msettings = MConfig.get_settings('members')
......@@ -39,6 +42,13 @@ def index(request):
context['ftop_btn_display'] = getattr(settings, 'ENTRANCE_FTOP_BUTTON_DISPLAY', True)
context['extra_btns_display'] = getattr(settings, 'ENTRANCE_EXTRA_BUTTONS_DISPLAY', True)
context['easy_shift_validate'] = getattr(settings, 'ENTRANCE_EASY_SHIFT_VALIDATE', False)
if context['easy_shift_validate'] is True:
committees_shift_id = CagetteServices.get_committees_shift_id()
if committees_shift_id is None:
return HttpResponse("Le créneau des comités n'est pas configuré dans Odoo !")
else:
context['committees_shift_id'] = committees_shift_id
if 'no_picture_member_advice' in msettings:
if len(msettings['no_picture_member_advice']['value']) > 0:
context['no_picture_member_advice'] = msettings['no_picture_member_advice']['value']
......@@ -86,6 +96,7 @@ def inscriptions(request, type=1):
'open_on_sunday': getattr(settings, 'OPEN_ON_SUNDAY', False),
'POUCHDB_VERSION': getattr(settings, 'POUCHDB_VERSION', ''),
'max_chq_nb': getattr(settings, 'MAX_CHQ_NB', 12),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'db': settings.COUCHDB['dbs']['member']}
response = HttpResponse(template.render(context, request))
......@@ -114,6 +125,7 @@ def prepa_odoo(request):
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'db': settings.COUCHDB['dbs']['member']}
# with_addr_complement
......@@ -147,6 +159,7 @@ def validation_inscription(request, email):
'ask_for_sex': getattr(settings, 'SUBSCRIPTION_ASK_FOR_SEX', False),
'ask_for_street2': getattr(settings, 'SUBSCRIPTION_ADD_STREET2', False),
'ask_for_second_phone': getattr(settings, 'SUBSCRIPTION_ADD_SECOND_PHONE', False),
'show_ftop_button': getattr(settings, 'SHOW_FTOP_BUTTON', True),
'em_url': settings.EM_URL,
'WELCOME_ENTRANCE_MSG': settings.WELCOME_ENTRANCE_MSG,
'WELCOME_SUBTITLE_ENTRANCE_MSG': getattr(settings, 'WELCOME_SUBTITLE_ENTRANCE_MSG', '')}
......@@ -222,7 +235,7 @@ def update_couchdb_barcodes(request):
# Borne accueil
def search(request, needle):
def search(request, needle, shift_id):
"""Search member has been requested."""
try:
key = int(needle)
......@@ -234,7 +247,7 @@ def search(request, needle):
key = needle
k_type = 'name'
res = CagetteMember.search(k_type, key)
res = CagetteMember.search(k_type, key, shift_id)
return JsonResponse({'res': res})
......@@ -262,7 +275,15 @@ def record_service_presence(request):
mid = int(request.POST.get("mid", 0)) # member id
sid = int(request.POST.get("sid", 0)) # shift id
stid = int(request.POST.get("stid", 0)) # shift_ticket_id
app_env = getattr(settings, 'APP_ENV', "prod")
if (rid > -1 and mid > 0):
overrided_date = ""
if app_env != "prod":
import re
o_date = re.search(r'/([^\/]+?)$', request.META.get('HTTP_REFERER'))
if o_date:
overrided_date = re.sub(r'(%20)',' ', o_date.group(1))
# rid = 0 => C'est un rattrapage, sur le service
if sid > 0 and stid > 0:
# Add member to service and take presence into account
......@@ -270,7 +291,7 @@ def record_service_presence(request):
if res['rattrapage'] is True:
res['update'] = 'ok'
else:
if (CagetteServices.registration_done(rid) is True):
if (CagetteServices.registration_done(rid, overrided_date) is True):
res['update'] = 'ok'
else:
res['update'] = 'ko'
......@@ -305,8 +326,8 @@ def easy_validate_shift_presence(request):
else:
return JsonResponse(res, safe=False)
def record_absences(request):
return JsonResponse({'res': CagetteServices.record_absences()})
def record_absences(request, date):
return JsonResponse({'res': CagetteServices.record_absences(date)})
def close_ftop_service(request):
"""Close the closest past FTOP service"""
......@@ -369,3 +390,23 @@ def panel_get_purchases(request):
message += ' ' + str(res['params'])
response = HttpResponse(message)
return response
# # # BDM # # #
def save_partner_info(request):
""" Endpoint the front-end will call for saving partner information """
res = {}
credentials = CagetteMember.get_credentials(request)
if ('success' in credentials):
data = {}
for post in request.POST:
if post != "idPartner" and data != "verif_token" :
data[post]= request.POST[post]
cm = CagetteMember(int(request.POST['idPartner']))
result = cm.save_partner_info(int(request.POST['idPartner']),data)
res['success']= result
return JsonResponse(res)
else:
res['error'] = "Forbidden"
return JsonResponse(res, safe=False)
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class MembersSpaceConfig(AppConfig):
name = 'members_space'
from django.db import models
from outils.common_imports import *
from members.models import CagetteServices
from outils.common import OdooAPI
class CagetteMembersSpace(models.Model):
"""Class to manage othe members space"""
def __init__(self):
"""Init with odoo id."""
self.o_api = OdooAPI()
def is_comite(self, partner_id):
"""Check if partner is from comite."""
cond = [['partner_id.id', '=', partner_id]]
fields = ['shift_template_id', 'is_current']
shiftTemplate = self.o_api.search_read('shift.template.registration', cond, fields)
answer = False
if (shiftTemplate and len(shiftTemplate) > 0):
s_t_id = None
for s_t in shiftTemplate:
if s_t['is_current'] is True:
s_t_id = s_t['shift_template_id'][0]
if s_t_id == CagetteServices.get_committees_shift_id():
answer = True
return answer
def get_shifts_history(self, partner_id, limit, offset, date_from):
""" Get partner shifts history """
res = []
paginated_res = []
today = str(datetime.date.today())
try:
cond = [
['partner_id', '=', partner_id],
['create_date', '>', date_from],
['date_begin', '<', today],
['state', '!=', 'draft'],
['state', '!=', 'open'],
['state', '!=', 'waiting'],
['state', '!=', 'replaced'],
['state', '!=', 'replacing'],
]
f = ['create_date', 'date_begin', 'shift_id', 'name', 'state', 'is_late', 'is_makeup']
marshal_none_error = 'cannot marshal None unless allow_none is enabled'
try:
res = self.o_api.search_read('shift.registration', cond, f, order='date_begin DESC')
except Exception as e:
if not (marshal_none_error in str(e)):
print(str(e))
coop_logger.error(repr(e) + ' : %s', str(partner_id))
else:
res = []
# Get committees shifts
committees_shifts_name = getattr(settings, 'ENTRANCE_ADD_PT_EVENT_NAME', 'Validation service comité')
cond = [
['partner_id', '=', partner_id],
['name', '=', committees_shifts_name]
]
f = ['create_date']
try:
res_committees_shifts = self.o_api.search_read('shift.counter.event', cond, f, order='create_date DESC')
for committee_shift in res_committees_shifts:
item = {
"create_date": committee_shift["create_date"],
"date_begin": committee_shift["create_date"],
"shift_id": False,
"name": "Services des comités",
"state": "done",
"is_late": False,
"is_makeup": False,
}
res.append(item)
except Exception as e:
if not (marshal_none_error in str(e)):
print(str(e))
coop_logger.error(repr(e) + ' : %s', str(partner_id))
else:
res = res + []
# Add amnesty line
is_amnesty = getattr(settings, 'AMNISTIE_DATE', 'false')
company_code = getattr(settings, 'COMPANY_CODE', '')
if is_amnesty and company_code == "lacagette":
amnesty={}
amnesty['is_amnesty'] = True
amnesty['create_date'] = is_amnesty
amnesty['date_begin'] = is_amnesty
amnesty['shift_name'] = 'Amnistie'
amnesty['state'] = ''
res.append(amnesty)
# Ordering here is necessary for pagination
res.sort(key = lambda x: datetime.datetime.strptime(x['date_begin'], '%Y-%m-%d %H:%M:%S'), reverse=True)
# Paginate
end_index = offset + limit
paginated_res = res[offset:end_index]
except Exception as e:
print(str(e))
return paginated_res
\ No newline at end of file
#faqBDM {
font-size: 1.8rem;
}
#faqBDM .block {
width: 100%;
}
.info_slots_shifts {
margin: 2rem 0;
}
.grp_text{
margin-top: 25px;
}
.param {margin-bottom: 15px;}
.param label {font-weight: bold;}
input.link {min-width: 50em;}
.submit_button {margin-bottom: 10px;}
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
text-align: left;
border: none;
outline: none;
transition: 0.4s;
}
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.active, .accordion:hover {
background-color: #ccc;
}
/* Style the accordion panel. Note: hidden by default */
.panel {
padding: 0 18px;
padding-bottom: 10px;
background-color: white;
display: none;
overflow: hidden;
border-left: 1px solid #E5E5E5;
border-right: 1px solid #E5E5E5;
border-bottom: 1px solid #E5E5E5;
border-bottom-left-radius: 15px;
border-bottom-right-radius: 15px;
}
button.accordion::after {
content: '\002B';
color: #777;
font-weight: bold;
float: right;
margin-left: 5px;
}
button.accordion.active::after {
content: "\2212";
}
.btn_faq{
white-space: normal;
border-radius: 5px;
margin-top: 3px;
padding-right: 25px;
display: flex;
align-items: flex-end;
}
.full_width{
width: 100%;
}
.faq_link_button_area {
margin-top: 10px;
height: 100%;
}
.faq_link_button {
white-space: normal;
border-radius: 5px;
}
.faq_link_button:hover {
color: #fff;
text-decoration: none;
}
.faq_intro_texts {
margin-bottom: 30px;
}
\ No newline at end of file
/* Add a black background color to the top navigation */
.topnav {
background-color: white;
overflow: hidden;
border-bottom: 1px solid #e7e9ed;
}
/* Style the links inside the navigation bar */
.topnav a {
float: left;
display: block;
color: #333;
text-align: center;
padding: 2rem 1.5rem;
text-decoration: none;
font-size: 17px;
font-weight: bold;
}
/* Change the color of links on hover */
.topnav a:hover {
background-color: #e7e9ed;
color: #333;
}
/* Add an active class to highlight the current page */
.topnav a.active {
background-color: #00a573;
color: white;
}
/* Hide the link that should open and close the topnav on small screens */
.topnav .icon {
display: none;
}
.pairs_info {
background-color: #00a573;
color: white;
padding: 1.5rem 1.2rem;
display: none;
}
@media screen and (max-width: 1146px) {
/* When the screen is less than 1146px pixels wide, hide all links, except for the first one ("Home"). Show the link that contains should open and close the topnav (.icon) */
.topnav a:not(:first-child) {display: none;}
.topnav a.icon {
float: right;
display: block;
}
/* The "responsive" class is added to the topnav with JavaScript when the user clicks on the icon. This class makes the topnav look good on small screens (display the links vertically instead of horizontally) */
.topnav.responsive {position: relative;}
.topnav.responsive a.icon {
position: absolute;
right: 0;
top: 0;
}
.topnav.responsive a {
float: none;
display: block;
text-align: left;
}
#deconnect {
float: none;
}
}
/* We override some styles defined on the home page for the specific needs of the My Info page */
#my_info {
font-size: 2rem;
}
#my_info_content {
display: flex;
flex-direction: column;
margin: 2rem 0;
}
.my_info_line {
display: flex;
flex-wrap: wrap;
width: 100%;
padding: 1.5rem 0;
}
@media screen and (min-width: 351px) and (max-width: 435px) {
.my_info_line {
font-size: 90%;
padding: 2vw 0;
}
}
@media screen and (max-width: 350px) {
.my_info_line {
font-size: 3.5vw;
padding: 2vw 0;
}
}
.my_info_line_left {
width: 50%;
text-align: right;
padding-right: 2rem;
font-weight: bold;
}
.my_info_line_right {
width: 50%;
padding-left: 2rem;
max-width: 100%;
word-break: break-all;
}
.my_info_line_middle {
width: 70%;
max-width: 100%;
word-break: normal;
text-align: center;
margin: 3rem 0;
}
#my_info #member_status_action,
#my_info .member_shift_name_area,
#my_info .member_coop_number_area {
margin-bottom: 0;
}
#my_info .member_shift_name_area {
margin-top: 0;
}
#attached_info {
display: flex;
flex-direction: column;
}
.member_phone_area {
display: flex;
flex-direction: column;
gap: 10px;
}
.edit-btn{
cursor: pointer;
}
#edit_address_form {
display: none ;
}
#edit_address_form #zip_form {
margin: 5px 0;
}
#edit_phone_form {
display: none ;
}
#edit_phone_form #mobile_form {
margin: 5px 0;
}
@media screen and (max-width: 992px) {
#my_info {
font-size: 1.7rem;
}
.my_info_line_left {
width: 30%;
padding-right: 1rem;
}
.my_info_line_right {
width: 70%;
padding-left: 1rem;
}
#attached_info .my_info_line_left {
width: 100%;
text-align: left;
padding: 0;
}
#attached_info .my_info_line_right {
width: 100%;
padding: 0;
}
#my_info .choose_makeups,
#my_info .unsuscribed_form_link {
white-space: normal;
}
#my_info .delay_date_stop_container {
white-space: nowrap;
}
#my_info .member_coop_number_area,
#my_info .member_shift_name_area {
align-items: flex-start;
}
}
.status_info_image {
display: block;
margin: 5rem auto 5rem auto;
width: 50%;
}
#my_shifts {
font-size: 1.8rem;
}
#incoming_shifts {
height: 100%;
flex-direction: column;
display: none;
}
.loading-history, .loading-incoming-shifts {
margin: 2rem 0;
}
#history {
display: flex;
flex-direction: column;
display: none;
}
table.dataTable tbody td {
text-overflow: ellipsis;
}
table.dataTable thead .sorting,
table.dataTable thead .sorting_asc,
table.dataTable thead .sorting_desc {
background : none;
}
table.dataTable.dtr-inline.collapsed>tbody>tr>td:first-child:before,
table.dataTable.dtr-inline.collapsed>tbody>tr>th:first-child:before {
color: #0275d8;
background-color: white;
font-weight: bold;
/* border: none;
font-size: 1.6rem;
height: 16px;
width: 16px;
border-radius: 2em; */
}
@media screen {
}
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td:first-child:before,
table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th:first-child:before {
color: #d8534f;
background-color: white;
font-weight: bold;
/* border: none;
font-size: 1.6rem;
height: 16px;
width: 16px;
border-radius: 2em; */
}
.loading-more-history {
display: none;
}
.more_history {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
font-size: 2rem;
}
.more_history_button {
height: 50px;
width: 50px;
border-radius: 50%;
}
table.dataTable.display tbody tr.row_partner_ok {
background-color: #8feb8b;
}
table.dataTable.display tbody tr.row_partner_late {
background-color: #ffdf7d;
}
table.dataTable.display tbody tr.row_partner_absent {
background-color: #ff847b;
}
table.dataTable.display tbody tr.row_partner_amnistie {
background-color: rgb(78, 78, 78);
color: white;
}
table.dataTable.display tbody tr td {
border-top: 1px solid rgb(119, 119, 119);
}
\ No newline at end of file
.shifts_exchange_page_content {
width: 95%;
margin: 3rem auto;
display: flex;
flex-direction: column;
position: relative;
}
/* -- Suspended screen */
#suspended_content, #unsuscribed_content {
align-items: center;
text-align: center;
}
#shifts_exchange .select_makeups, #shifts_exchange .unsuscribed_form_link, .cant_have_delay_form_link {
margin: 1.5rem 0;
}
/* -- Suspended can't have delay screen */
#suspended_cant_have_delay_content {
align-items: center;
text-align: center;
width: 50%;
}
@media screen and (max-width:992px) {
#suspended_cant_have_delay_content {
align-items: center;
text-align: center;
width: 90%;
}
}
/* -- Calendar screen, area on top of the calendar */
#calendar_top_info {
display: flex;
justify-content: space-between;
}
@media screen and (max-width:992px) {
#calendar_top_info {
display: flex;
flex-direction: column;
justify-content: space-between;
}
}
/* -- Calendar screen, shifts list */
#shifts_list {
flex-direction: column;
display: none;
width: min-content;
max-width: 100%;
white-space: nowrap;
}
@media screen and (max-width:992px) {
#partner_shifts_list {
display: flex;
flex-direction: column;
align-items: center;
}
}
.selectable_shift_line {
display: flex;
align-items: center;
margin-left: 15px;
margin: 0.75rem 0;
border-radius: 5px;
}
.selectable_shift_line .checkbox {
margin-right: 10px;
}
.selectable_shift_line.btn {
cursor: not-allowed;
}
/* -- Calendar screen, makeups message */
#need_to_select_makeups_message {
display: none;
align-self: center;
background-color: #d9534f;
color: white;
margin: 0 1rem 2rem 1rem;
padding: 1rem 1.25rem;
text-align: center;
}
.makeups_warning {
margin-right: 3px;
}
@media screen and (max-width:992px) {
.select_makeups_message_block {
display: block;
}
}
/* -- Calendar screen, calendar */
#calendar {
margin: 2rem 1rem;
}
.loading-calendar {
margin: 3rem auto;
display: none;
}
@media screen and (max-width:992px) {
#calendar {
display: none;
}
}
.fc .fc-event {
cursor: pointer;
margin: 1px 10px !important;
}
.fc-event {
background-color: #008AD9;
border-color: #008AD9;
color: white;
}
td{
--fc-list-event-hover-bg-color:#005BA6;
}
.fc-event.shift_booked {
background-color: #585858;
cursor: auto;
border-color: #585858;
}
.fc-event.shift_booked td {
--fc-list-event-hover-bg-color:#585858;
}
.fc-list-event.shift_booked {
color: white;
}
#calendar .fc-list-table {
table-layout: auto;
}
.resp-header-toolbar {
display: flex;
flex-direction: column;
}
.resp-header-toolbar .fc-toolbar-chunk {
text-align: center;
margin: 0.25rem;
}
.date_old_shift, .time_old_shift, .date_new_shift, .time_new_shift {
font-weight: bold;
}
/* -- Explainations */
#calendar_explaination_area {
max-width: 33%;
border: 2px solid #585858;
border-radius: 15px;
padding: 1rem;
}
.example-event {
max-width: 200px;
margin: 2rem 0 0.5rem 0;
font-size: 1.4rem !important;
padding: 0 !important;
}
@media screen and (max-width:992px) {
.example-event {
margin: 2rem auto 0.5rem auto;
}
}
.arrow_explanation_numbers {
margin: 0 3px;
}
#calendar_explaination_button {
max-width: 60%;
margin: 2rem auto 0.5rem auto;
}
\ No newline at end of file
body {
margin: 0;
}
.page_title {
margin: 35px 0 30px 0;
}
@media screen and (max-width: 435px) {
.page_title {
margin: 4vw 0 3vw 0;
}
}
/* -- Tiles */
.tiles_container {
display: flex;
flex-wrap: wrap;
}
@media screen and (max-width: 992px) {
.tiles_container {
flex-direction: column;
}
}
.tile {
flex: 1 0 45%;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 30px;
margin: 1rem 1rem;
box-shadow: 2px 2px 3px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);
}
.high_tile {
min-height: 350px;
}
.small_tile {
min-height: 250px;
}
.full_width_tile {
flex: 1 0 90%;
min-height: 100px;
}
.tile_title {
display: flex;
justify-content: center;
align-items: center;
border-bottom: 1px solid #e7e9ed;
font-size: 2.4rem;
padding: 2rem 0;
width: 80%;
}
.tile_content {
position: relative;
margin: 3rem 0;
width: 80%;
display: flex;
height: 100%;
}
#home_tile_services_exchange .tile_content {
height: 100%;
flex-direction: column;
align-items: center;
text-align: center;
}
/* -- My Shifts tile */
#home_tile_my_services .tile_content {
height: 100%;
flex-direction: column;
margin: auto;
padding: 2rem 0;
}
@media screen and (min-width: 769px) {
#home_tile_my_services .tile_content {
width: 50%;
}
}
#home_incoming_services {
min-height: 80px;
display: flex;
flex-direction: column;
}
.shift_line {
margin-left: 15px;
line-height: 2;
}
.shift_line_chevron {
color: #D9534F;
margin-right: 5px;
}
#go_to_shift_history_area {
width: 100%;
display: flex;
justify-content: center;
}
#home_go_to_shift_history {
width: 100%;
margin-top: 30px;
}
/* -- My Info tile */
#home_tile_my_info {
position: relative;
}
#home_tile_my_info .tile_content {
margin: 2rem 0;
}
.tile_icon {
margin-right: 15px;
color: #00a573;
}
#home_tile_my_info .tile_content {
height: 100%;
flex-direction: column;
align-items: center;
font-size: 1.6rem;
}
@media screen and (max-width: 576px) {
#home_tile_my_info .tile_content {
font-size: 1.4rem !important;
}
}
#home .member_info {
font-weight: bold;
}
.member_status_text_container {
margin-bottom: 5px;
}
#member_status_action {
display: flex;
margin-bottom: 20px;
}
@media screen and (max-width: 992px) {
#member_status_action {
margin-top: 5px;
margin-bottom: 10px;
}
}
.choose_makeups {
display: none;
font-size: 1.5rem;
}
.unsuscribed_form_link {
display: none;
text-decoration: none;
font-size: 1.7rem;
word-break: normal;
}
.unsuscribed_form_link:hover {
text-decoration: none;
}
@media (max-width: 435px) {
.unsuscribed_form_link {
font-size: 90%;
line-height: 7vw;
}
}
.member_status_up_to_date,
.member_status_exempted {
color: #5cb85c;
}
.member_status_alert,
.member_status_delay {
color: #f0ad4e;
}
.member_status_suspended,
.member_status_unsubscribed {
color: #d9534f;
}
.member_shift_name_area,
.member_coop_number_area {
margin-bottom: 10px;
}
.member_associated_partner_area {
line-height: 1.3;
}
@media screen and (max-width: 992px) {
.member_associated_partner_area {
display: flex;
flex-direction: column;
align-items: center;
}
}
.delay_date_stop_container {
color: #f0ad4e;
margin-top: -1rem;
margin-bottom: 1rem;
display: none;
}
#see_more_info {
white-space: normal;
}
#see_more_info_link {
width: 100%;
}
/* --Shifts exchange tile tile */
.home_link_button_area {
width: 100%;
display: flex;
justify-content: center;
height: 100%;
}
.home_link_button {
width: 80%;
margin: 30px auto auto auto;
white-space: normal;
}
/* -- I have a question tile */
#go_to_forms {
text-decoration: none;
}
#go_to_forms:hover {
color: white;
}
#go_to_forms.active {
color: white !important;
}
/* -- Shop info tile */
#shop_info_content {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
width: 80%;
margin: auto;
}
.shop_info_item {
width: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 2rem;
flex: 1 0 50%;
}
.shop_info_item h1,h2,h3,h4,h5,h6 {
font-size: 2rem;
}
.opening_hours_title {
margin-bottom: 10px;
font-size: 2.3rem;
font-weight: bold;
}
.shop_message_content {
text-align: center;
}
@media screen and (min-width: 769px) {
.shop_info_item {
padding: 0 4rem;
}
.shop_message {
border-left: 1px solid #e7e9ed;
margin: 3rem 0;
}
}
@media screen and (max-width: 992px) {
#shop_info_content {
flex-direction: column;
}
.shop_info_item {
flex: 1 0 50%;
width: 100%;
font-size: 1.6rem;
padding: 1.5rem 0;
}
.shop_info_item h1,h2,h3,h4,h5,h6 {
font-size: 1.6rem;
}
.opening_hours_title {
font-size: 1.9rem;
}
.shop_message {
border-top: 1px solid #e7e9ed;
}
.shop_message_content {
width: 90%;
}
}
/* - No content page */
.message_error {
padding: 7vw;
font-size: 1.8rem;
}
.no_content_title {
margin-bottom: 1.5rem;
}
function init_faq() {
$("#unsuscribe_form_link_btn").prop("href", unsuscribe_form_link);
$("#unsuscribe_form_link_btn2").prop("href", unsuscribe_form_link);
$("#change_template_form_link_btn").prop("href", change_template_form_link);
$("#template_unsubscribe_form_link_btn").prop("href", template_unsubscribe_form_link);
$("#late_service_form_link_btn").prop("href", late_service_form_link);
$("#sick_leave_form_link_btn").prop("href", sick_leave_form_link);
$("#associated_subscribe_form_link_btn").prop("href", associated_subscribe_form_link);
$("#associated_unsubscribe_form_link_btn").prop("href", associated_unsubscribe_form_link);
$("#covid_form_link_btn").prop("href", covid_form_link);
$("#covid_end_form_link_btn").prop("href", covid_end_form_link);
$("#underage_subscribe_form_link_btn").prop("href", underage_subscribe_form_link);
$("#change_email_form_link_btn").prop("href", change_email_form_link);
$("#coop_unsubscribe_form_link_btn").prop("href", coop_unsubscribe_form_link);
$("#helper_subscribe_form_link_btn").prop("href", helper_subscribe_form_link);
$("#helper_unsubscribe_form_link_btn").prop("href", helper_unsubscribe_form_link);
$("#request_form_link_btn2").prop("href", request_form_link);
$("#request_form_link_btn").prop("href", request_form_link);
}
$(document).on('click', '.accordion', function() {
/* Toggle between adding and removing the "active" class,
to highlight the button that controls the panel */
this.classList.toggle("active");
/* Toggle between hiding and showing the active panel */
var panel = this.nextElementSibling;
if (panel.style.display === "block") {
panel.style.display = "none";
} else {
panel.style.display = "block";
}
$("#shift_exchange_btn").on("click", () => {
goto('echange-de-services');
});
});
\ No newline at end of file
/**
* Toggle the navbar on mobile screens
*/
function toggleHeader() {
var x = document.getElementById("topnav");
if (x.className === "topnav") {
x.className += " responsive";
} else {
x.className = "topnav";
}
}
$(document).ready(function() {
// Navbar redirections
$('#nav_home').on('click', (e) => {
e.preventDefault();
if (current_location !== "home") {
goto('home');
}
if (document.getElementById("topnav").className !== "topnav") {
toggleHeader();
}
});
$('#nav_my_info').on('click', (e) => {
e.preventDefault();
if (current_location !== "my_info") {
goto('mes-infos');
}
toggleHeader();
});
$('#nav_my_shifts').on('click', (e) => {
e.preventDefault();
if (current_location !== "my_shifts") {
goto('mes-services');
}
toggleHeader();
});
$('#nav_faq').on('click', (e) => {
e.preventDefault();
if (current_location !== "faq") {
goto('faq');
}
toggleHeader();
});
$('#nav_shifts_exchange').on('click', (e) => {
e.preventDefault();
if (current_location !== "shifts_exchange") {
goto('echange-de-services');
}
toggleHeader();
});
$('#nav_calendar').prop("href", abcd_calendar_link);
$('#nav_calendar').on('click', () => {
toggleHeader();
});
if (partner_data.is_associated_people === "True") {
$(".pairs_info").show();
}
});
/**
* Request a 6 month delay
*/
function request_delay() {
return new Promise((resolve) => {
let today = new Date();
const delay_start = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
let today_plus_six_month = new Date();
today_plus_six_month.setMonth(today_plus_six_month.getMonth()+6);
const diff_time = Math.abs(today_plus_six_month - today);
const diff_days = Math.ceil(diff_time / (1000 * 60 * 60 * 24));
$.ajax({
type: 'POST',
url: "/shifts/request_delay",
dataType:"json",
data: {
verif_token: partner_data.verif_token,
idPartner: partner_data.partner_id,
start_date: delay_start,
duration: diff_days
},
success: function() {
partner_data.cooperative_state = 'delay';
partner_data.date_delay_stop = today_plus_six_month.getFullYear()+'-'+(today_plus_six_month.getMonth()+1)+'-'+today_plus_six_month.getDate();
resolve();
},
error: function(data) {
if (data.status == 403
&& typeof data.responseJSON != 'undefined'
&& data.responseJSON.message === "delays limit reached") {
closeModal();
let msg_template = $("#cant_have_delay_msg_template");
openModal(
msg_template.html(),
() => {
window.location =member_cant_have_delay_form_link;
},
"J'accède au formulaire",
true,
false
);
} else {
err = {msg: "erreur serveur lors de la création du délai", ctx: 'request_delay'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members_space.home');
closeModal();
alert('Erreur lors de la création du délai.');
}
}
});
});
}
function init_my_shifts_tile() {
if (incoming_shifts.length === 0) {
$("#home_tile_my_services #home_incoming_services").text("Aucun service à venir...");
} else {
$("#home_tile_my_services #home_incoming_services").empty();
let cpt = 0;
for (shift of incoming_shifts) {
if (cpt === 3) {
break;
} else {
let shift_line_template = prepare_shift_line_template(shift.date_begin);
$("#home_tile_my_services #home_incoming_services").append(shift_line_template.html());
cpt++;
}
}
}
}
function init_home() {
$("#go_to_shifts_calendar").on("click", () => {
goto('echange-de-services');
});
$("#home_go_to_shift_history").on("click", () => {
goto('mes-services');
});
$("#see_more_info_link").on('click', (e) => {
e.preventDefault();
goto('mes-infos');
});
// $("#go_to_forms").prop("href", "forms_link");
$("#go_to_forms").on('click', (e) => {
e.preventDefault();
goto('faq');
});
if (partner_data.is_in_association === false) {
$("#home .member_associated_partner_area").hide();
} else {
if (partner_data.is_associated_people === "True") {
$(".member_associated_partner").text(partner_data.parent_name);
} else if (partner_data.associated_partner_id !== "False") {
$(".member_associated_partner").text(partner_data.associated_partner_name);
}
}
// TODO vérif tile my info avec données binomes + rattrapage et délai
// Init my info tile
init_my_info_data();
if (incoming_shifts !== null) {
init_my_shifts_tile();
} else {
load_partner_shifts(partner_data.concerned_partner_id)
.then(init_my_shifts_tile);
}
}
\ No newline at end of file
function init_my_info() {
init_my_info_data();
$(".member_email").text(partner_data.email);
if (partner_data.is_in_association === false) {
$("#attached_info_area").hide();
}
if (partner_data.is_associated_people === "True") {
$(".attached_partner_name").text(partner_data.parent_name);
} else if (partner_data.associated_partner_id !== "False") {
$(".attached_partner_name").text(partner_data.associated_partner_name);
}
$(".member_address").empty();
if (partner_data.street !== "" && partner_data.street !== "False") {
$(".member_address")
.append(partner_data.street + "<br/>");
if (partner_data.street2 !== "" && partner_data.street2 !== "False") {
$(".member_address")
.append(partner_data.street2 + "<br/>");
}
$(".member_address")
.append(partner_data.zip + " " + partner_data.city);
} else {
$(".member_address_line").hide();
}
$(".member_mobile").empty();
if (partner_data.mobile !== "" && partner_data.mobile !== "False" && partner_data.mobile !== false && partner_data.mobile !== null) {
$(".member_mobile")
.append(partner_data.mobile)
.show();
} else {
$(".member_mobile").hide();
}
$(".member_phone").empty();
if (partner_data.phone !== "" && partner_data.phone !== "False" && partner_data.phone !== false && partner_data.phone !== null) {
$(".member_phone")
.append(partner_data.phone)
.show();
} else {
$(".member_phone").hide();
}
if ($(".member_mobile").text() === "" && $(".member_phone").text() === "") {
$(".member_phone_line").hide();
}
$('#edit_address').off('click')
.on('click', () => {
$("#street_form").val(partner_data.street.replace(/&#39;/g, "'"));
// $("#street2_form").val(partner_data.street2);
$("#zip_form").val(partner_data.zip.replace(/&#39;/g, "'"));
$("#city_form").val(partner_data.city.replace(/&#39;/g, "'"));
$('#edit_address_value').hide();
$('#edit_address_form').show();
});
$('#cancel_edit_address').
on('click', () => {
$('#edit_address_form').hide();
$('#edit_address_value').show();
});
$('#save_edit_address').off('click')
.on('click', () => {
data= [];
data['street']= $("#street_form").val();
// data['street2']= $("#street2_form").val();
data['zip']= $("#zip_form").val();
data['city']= $("#city_form").val();
saveInfo(data, 'address');
});
$('#edit_phone').off('click')
.on('click', () => {
if (partner_data.phone === "False") partner_data.phone = "";
if (partner_data.mobile === "False") partner_data.mobile = "";
$("#phone_form").val(partner_data.phone);
$("#mobile_form").val(partner_data.mobile);
$('#edit_phone_value').hide();
$('#edit_phone_form').show();
});
$('#cancel_edit_phone').off('click')
.on('click', () => {
$('#edit_phone_form').hide();
$('#edit_phone_value').show();
});
$('#save_edit_phone').off('click')
.on('click', () => {
console.log('ici');
data =[];
data['phone']= $("#phone_form").val();
data['mobile']= $("#mobile_form").val();
saveInfo(data, 'phone');
});
}
function saveInfo(data, field) {
tData = '&idPartner=' + partner_data.partner_id
+ '&shift_type=' + partner_data.shift_type
+ '&verif_token=' + partner_data.verif_token;
for (d in data) {
tData+="&"+d+"="+data[d];
}
tUrl = '/members/save_partner_info';
$.ajax({
type: 'POST',
url: tUrl,
dataType:"json",
data: tData,
timeout: 3000,
success: function() {
for (d in data) {
partner_data[d]=data[d];
}
init_my_info();
if (field == 'address') {
$('#edit_address_form').hide();
$('#edit_address_value').show();
}
if (field == 'phone') {
$('#edit_phone_form').hide();
$('#edit_phone_value').show();
}
},
error: function(error) {
console.log(error);
}
});
}
var history_table = null;
const history_items_limit = 10;
/**
* Load the partner points history
*/
function load_partner_history(offset = 0) {
return new Promise((resolve) => {
$.ajax({
type: 'GET',
url: "/members_space/get_shifts_history",
data: {
partner_id: partner_data.concerned_partner_id,
verif_token: partner_data.verif_token,
limit: history_items_limit,
offset: offset
},
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
formatted_data = prepare_server_data(data.data);
resolve(formatted_data);
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération de l'historique", ctx: 'load_partner_history'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members_space.my_shifts');
closeModal();
// TODO Notify
alert('Erreur lors de la récupération de votre historique.');
}
});
});
}
/**
* Format history data to insert in the table
*
* @param {Array} data
* @returns formated data array
*/
function prepare_server_data(data) {
res = [];
for (history_item of data) {
if (history_item.is_amnesty !== undefined) {
let shift_datetime = new Date(history_item.date_begin);
let str_shift_datetime = `${("0" + shift_datetime.getDate()).slice(-2)}/${("0" + (shift_datetime.getMonth() + 1)).slice(-2)}/${shift_datetime.getFullYear()}`;
history_item.shift_name = `${history_item.shift_name} du ${str_shift_datetime}`;
} else {
history_item.shift_name = (history_item.shift_id === false) ? '' : history_item.shift_id[1];
if (history_item.name === "Services des comités") {
let shift_datetime = new Date(history_item.date_begin);
let str_shift_datetime = `${("0" + shift_datetime.getDate()).slice(-2)}/${("0" + (shift_datetime.getMonth() + 1)).slice(-2)}/${shift_datetime.getFullYear()}`;
str_shift_datetime = str_shift_datetime + " " + shift_datetime.toLocaleTimeString("fr-fr", time_options);
history_item.shift_name = `Services des comités ${str_shift_datetime}`;
}
}
history_item.details = '';
if (history_item.state === 'excused' || history_item.state === 'absent') {
history_item.details = "Absent.e";
} else if (history_item.state === 'done' && history_item.is_late != false) {
history_item.details = "Présent.e (En Retard)";
} else if (history_item.state === 'done') {
history_item.details = "Présent.e";
} else if (history_item.state === 'cancel') {
history_item.details = "Annulé";
}
}
return data;
}
/**
* Init the History section: display the history table
*/
function init_history() {
$(".loading-history").hide();
$("#history").show();
if (partner_history.length === 0) {
$("#history").empty()
.text("Aucun historique... pour l'instant !");
} else {
history_table = $('#history_table').DataTable({
data: partner_history,
columns: [
{
data: "date_begin",
title: "",
visible: false
},
{
data: "shift_name",
title: "<spans class='dt-body-center'>Service</span>",
width: "60%",
orderable: false
},
{
data: "details",
title: "Détails",
className: "tablet-l desktop",
orderable: false
}
],
iDisplayLength: -1,
order: [
[
0,
"desc"
]
],
language: {url : '/static/js/datatables/french.json'},
dom: "t",
responsive: true,
createdRow: function(row) {
for (var i = 0; i < row.cells.length; i++) {
const cell = $(row.cells[i]);
if (cell.text() === "Présent.e") {
$(row).addClass('row_partner_ok');
} else if (cell.text() === "Retard") {
$(row).addClass('row_partner_late');
} else if (cell.text() === "Absent.e") {
$(row).addClass('row_partner_absent');
} else if (cell.text().includes("Amnistie")) {
$(row).addClass('row_partner_amnistie');
}
}
}
});
}
}
/**
* Init the Incoming shifts section: display them
*/
function init_incoming_shifts() {
$(".loading-incoming-shifts").hide();
$("#incoming_shifts").show();
if (incoming_shifts.length === 0) {
$("#incoming_shifts").text("Aucun service à venir...");
} else {
$("#incoming_shifts").empty();
for (shift of incoming_shifts) {
let shift_line_template = prepare_shift_line_template(shift.date_begin);
$("#incoming_shifts").append(shift_line_template.html());
}
}
}
function init_my_shifts() {
if (incoming_shifts !== null) {
init_incoming_shifts();
} else {
load_partner_shifts(partner_data.concerned_partner_id)
.then(init_incoming_shifts);
}
if (partner_history !== null) {
init_history();
} else {
load_partner_history()
.then((data) => {
partner_history = data;
for (d of data) {
d.create_date = Date.parse(d.create_date);
}
// Sort by date desc
partner_history.sort((a, b) => b.create_date - a.create_date);
if (partner_history.length>0 && partner_history[partner_history.length-1].is_amnesty != undefined) {
partner_history.pop();
}
init_history();
});
}
$(".more_history_button").on("click", function() {
// Hide button & display loading
$('.more_history_button').hide();
$('.loading-more-history').show();
load_partner_history(partner_history.length)
.then((data) => {
partner_history = partner_history.concat(data);
if (history_table) {
history_table.rows.add(data).draw(false);
}
$('.loading-more-history').hide();
// Show "load more" if there is more to load
if (data.length === history_items_limit) {
$('.more_history_button').show();
}
});
});
}
/**
* Common logic between pages
*/
var base_location = null,
current_location = null,
incoming_shifts = null,
partner_history = null;
var date_options = {weekday: "long", year: "numeric", month: "long", day: "numeric"};
var time_options = {hour: '2-digit', minute:'2-digit'};
const possible_cooperative_state = {
suspended: "Rattrapage",
exempted: "Exempté.e",
alert: "En alerte",
up_to_date: "À jour",
unsubscribed: "Désinscrit.e des créneaux",
delay: "En délai"
};
/* - Data */
/**
* Load the shifts the member is registered to
* @param {int} partner_id either the members id, or its parent's if s.he's attached
*/
function load_partner_shifts(partner_id) {
return new Promise((resolve) => {
$.ajax({
type: 'GET',
url: "/shifts/get_list_shift_partner/" + partner_id,
dataType:"json",
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
incoming_shifts = data;
resolve();
},
error: function(data) {
err = {msg: "erreur serveur lors de la récupération des services", ctx: 'load_partner_shifts'};
if (typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
err.msg += ' : ' + data.responseJSON.error;
}
report_JS_error(err, 'members_space.index');
closeModal();
// TODO Notify
alert('Erreur lors de la récupération de vos services.');
}
});
});
}
/* - Navigation */
/**
* @param {String} page home | mes-infos | mes-services | echange-de-services | faq
*/
function goto(page) {
if (window.location.pathname === base_location) {
history.pushState({}, '', page);
} else {
history.replaceState({}, '', page);
}
update_dom();
}
/**
* Define which html content to load from server depending on the window location
*
* WARNING: For the routing system to work,
* public urls (those the users will see & navigate to) must be different than the server urls used to fetch resources
* (ex: public url: /members_space/mes-info ; server url: /members_space/my_info)
*/
function update_dom() {
$(".nav_item").removeClass('active');
if (window.location.pathname === base_location || window.location.pathname === base_location + "home") {
current_location = "home";
$("#main_content").load("/members_space/homepage", update_content);
$("#nav_home").addClass("active");
} else if (window.location.pathname === base_location + "mes-infos") {
current_location = "my_info";
$("#main_content").load("/members_space/my_info", update_content);
$("#nav_my_info").addClass("active");
} else if (window.location.pathname === base_location + "mes-services") {
current_location = "my_shifts";
$("#main_content").load("/members_space/my_shifts", update_content);
$("#nav_my_shifts").addClass("active");
} else if (window.location.pathname === base_location + "faq") {
current_location = "faq";
$("#main_content").load("/members_space/faqBDM", update_content);
$("#nav_faq").addClass("active");
} else if (window.location.pathname === base_location + "echange-de-services") {
current_location = "shifts_exchange";
$("#main_content").load("/members_space/shifts_exchange", update_content);
$("#nav_shifts_exchange").addClass("active");
} else {
$("#main_content").load("/members_space/no_content");
}
}
/**
* Update the data displayed depending on the current location
* (ex: insert personal data in the DOM when on the 'My Info' page)
*/
function update_content() {
switch (current_location) {
case 'home':
init_home();
break;
case 'my_info':
init_my_info();
break;
case 'my_shifts':
init_my_shifts();
break;
case 'faq':
init_faq();
break;
case 'shifts_exchange':
init_shifts_exchange();
break;
default:
console.log(`Bad input`);
}
}
/* - Shifts */
/**
* Prepare a shift line to insert into the DOM.
* Is used in: Home - My Shifts tile ; My Shifts - Incoming shifts section
*
* @param {String} date_begin beginning datetime of the shift
* @returns JQuery node object of the formatted template
*/
function prepare_shift_line_template(date_begin) {
let shift_line_template = $("#shift_line_template");
let datetime_shift_start = new Date(date_begin.replace(/\s/, 'T'));
let f_date_shift_start = datetime_shift_start.toLocaleDateString("fr-fr", date_options);
f_date_shift_start = f_date_shift_start.charAt(0).toUpperCase() + f_date_shift_start.slice(1);
shift_line_template.find(".shift_line_date").text(f_date_shift_start);
shift_line_template.find(".shift_line_time").text(datetime_shift_start.toLocaleTimeString("fr-fr", time_options));
return shift_line_template;
}
/* - Member info */
/**
* Init common personal data between screens
*/
function init_my_info_data() {
$(".choose_makeups").off();
$(".unsuscribed_form_link").off();
$(".member_shift_name").text(partner_data.regular_shift_name);
let pns = partner_data.name.split(" - ");
let name = pns.length > 1 ? pns[1] : pns[0];
$(".member_name").text(name);
// Status related
$(".member_status")
.text(possible_cooperative_state[partner_data.cooperative_state])
.addClass("member_status_" + partner_data.cooperative_state);
if (partner_data.cooperative_state === 'delay' && partner_data.date_delay_stop !== 'False') {
const d = new Date(Date.parse(partner_data.date_delay_stop));
const f_date_delay_stop = d.getDate()+'/'+("0" + (d.getMonth() + 1)).slice(-2)+'/'+d.getFullYear();
$(".delay_date_stop").text(f_date_delay_stop);
$(".delay_date_stop_container").show();
} else if (partner_data.cooperative_state === 'unsubscribed') {
$(".member_shift_name").text('X');
$(".unsuscribed_form_link")
.show()
.attr('href', unsuscribe_form_link)
.on('click', function() {
setTimeout(500, () => {
$(this).removeClass('active');
});
});
} else if (partner_data.cooperative_state === 'exempted') {
const d = new Date(Date.parse(partner_data.leave_stop_date));
const f_date_delay_stop = d.getDate()+'/'+("0" + (d.getMonth() + 1)).slice(-2)+'/'+d.getFullYear();
$(".delay_date_stop").text(f_date_delay_stop);
$(".delay_date_stop_container").show();
}
if (
partner_data.makeups_to_do > 0
&& partner_data.is_associated_people === "False"
&& partner_data.cooperative_state !== 'unsubscribed'
) {
$(".choose_makeups").show();
if (
partner_data.cooperative_state === 'suspended'
&& partner_data.date_delay_stop === 'False') {
// If the member is suspended & doesn't have a delay
$(".choose_makeups").on('click', () => {
// Create 6 month delay
request_delay()
.then(() => {
// Then redirect to calendar
goto('echange-de-services');
});
});
} else {
$(".choose_makeups").on('click', () => {
goto('echange-de-services');
});
}
}
$(".member_coop_number").text(partner_data.barcode_base);
}
$(document).ready(function() {
// TODO essayer de ne charger les js que au besoin
$.ajaxSetup({ headers: { "X-CSRFToken": getCookie('csrftoken') } });
// If partner is associated (attached), display the pair's main partner shift data
partner_data.concerned_partner_id =
(partner_data.is_associated_people === "True")
? partner_data.parent_id
: partner_data.partner_id;
partner_data.is_in_association =
partner_data.is_associated_people === "True" || partner_data.associated_partner_id !== "False";
// For associated people, their parent name is attached in their display name
let partner_name_split = partner_data.name.split(', ');
partner_data.name = partner_name_split[partner_name_split.length - 1];
base_location = (app_env === 'dev') ? '/members_space/' : '/';
update_dom();
window.onpopstate = function() {
update_dom();
};
});
(function($, sr) {
// debouncing function from John Hann
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
var debounce = function (func, threshold, execAsap) {
var timeout;
return function debounced () {
var obj = this, args = arguments;
function delayed () {
if (!execAsap)
func.apply(obj, args);
timeout = null;
}
if (timeout)
clearTimeout(timeout);
else if (execAsap)
func.apply(obj, args);
timeout = setTimeout(delayed, threshold || 100);
};
};
// smartresize
jQuery.fn[sr] = function(fn) {
return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr);
};
})(jQuery, 'smartresize');
\ No newline at end of file
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
\ No newline at end of file
from django.test import SimpleTestCase
class TestUrls(SimpleTestCase):
def test_list_url_is_resolved(self):
assert 1==1
\ No newline at end of file
from django.test import SimpleTestCase
\ No newline at end of file
"""."""
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index),
url(r'^homepage$', views.home), # These endpoints must be different than in-app url
url(r'^my_info$', views.my_info),
url(r'^my_shifts$', views.my_shifts),
url(r'^shifts_exchange$', views.shifts_exchange),
url(r'^faqBDM$', views.faqBDM),
url(r'^no_content$', views.no_content),
url(r'^get_shifts_history$', views.get_shifts_history),
url('/*$', views.index), # Urls unknown from the server will redirect to index
]
......@@ -22,6 +22,10 @@
Used to draw weeks planning
- COMPANY_NAME = 'lgds'
Used for company spesific code
- COMPANY_NAME = 'Les Grains de Sel'
- ADMIN_IDS = [13]
......@@ -114,6 +118,14 @@
Character which by used to separate every 2 phone figures (04.67.23.89.21 for example)
Default is " "
- SHOW_FTOP_BUTTON = True (by default)
If True, in shift_template calendar choice view, "Volant" button is included
- USE_STANDARD_SHIFT = True (by default)
La Cagette use False to implement custom rules
### Scales and labels files generation
- DAV_PATH = '/data/dav/cagette'
......@@ -244,6 +256,23 @@
(makes sens if ENTRANCE_EASY_SHIFT_VALIDATE is True)
- ENTRANCE_WITH_LATE_MODE = True
(If member is coming within the grace delay)
- ENTRANCE_VALIDATION_GRACE_DELAY = 60
(if not set, 60 minutes is the default)
- ENTRANCE_VALIDATE_PRESENCE_MESSAGE = """
<div class="explanations">
Ta présence a bien été validée ! Merci de te diriger au fond du magasin pour le lancement du créneau !
</div>
Ton prochain service <span class="service_verb">est prévu</span> le <span class="next_shift"></span>
"""
(La Cagette message, where no point data is displayed)
### Member space
- EM_URL = ''
......@@ -263,7 +292,7 @@
If not set, default view is 'dayGridMonth'
- SHIFT_EXCHANGE_DAYS_TO_HIDE = ''
- SHIFT_EXCHANGE_DAYS_TO_HIDE = '0'
By default, if this variable is not set, sunday is hidden
To hide Sunday and Monday, set this to "0,1"
......@@ -277,6 +306,7 @@
- PB_INSTRUCTIONS = """Si j'ai un problème, que je suis désinscrit, que je veux changer de créneaux ou quoi que ce soit, merci de vous rendre dans la section \"J'ai un problème\" sur le site web de <a href=\"https://lacagette-coop.fr/?MonEspaceMembre\">La Cagette</a>"""
- UNSUBSCRIBED_FORM_LINK = 'https://docs.google.com/forms/d/e/1FAIpQLScWcpls-ruYIp7HdrjRF1B1TyuzdqhvlUIcUWynbEujfj3dTg/viewform'
- UNSUBSCRIBED_MSG = 'Vous êtes désincrit·e, merci de remplir <a href="https://docs.google.com/forms/d/e/1FAIpQLSfPiC2PkSem9x_B5M7LKpoFNLDIz0k0V5I2W3Mra9AnqnQunw/viewform">ce formulaire</a> pour vous réinscrire sur un créneau.<br />Vous pouvez également contacter le Bureau des Membres en remplissant <a href="https://docs.google.com/forms/d/e/1FAIpQLSeZP0m5-EXPVJxEKJk6EjwSyZJtnbiGdYDuAeFI3ENsHAOikg/viewform">ce formulaire</a>'
Message shown to people when they connect to the Member Space
......@@ -325,7 +355,16 @@
- MEALS_PICKING_TYPE_ID = 10
### New members space
- USE_NEW_MEMBERS_SPACE = True
Should be set to False by default if parameter not set
- START_DATE_FOR_SHIFTS_HISTORY = "2018-01-01"
- AMNISTIE_DATE = "2021-11-24 00:00:00"
In members_space history display a special activity about amnistie
### Miscellious
......
......@@ -21,5 +21,7 @@ def custom_css(request):
def context_setting(request):
"""adding settings variable to context (can be overloaded in views)."""
context = {'odoo': settings.ODOO['url']}
context = {'odoo': settings.ODOO['url'],
'app_env': getattr(settings, 'APP_ENV', "prod"),
'company_code': getattr(settings, 'COMPANY_CODE', '')}
return context
\ No newline at end of file
......@@ -53,6 +53,7 @@ INSTALLED_APPS = (
'shop',
'shelfs',
'sales',
'members_space',
# 'tests'
)
......@@ -100,6 +101,7 @@ STATICFILES_DIRS = (
"shop/static",
"shelfs/static",
"orders/static",
"members_space/static",
# "tests/static"
)
......@@ -225,3 +227,5 @@ DEBUG = True
CORS_ORIGIN_ALLOW_ALL = True # Needed to make dev test with different IP and ports
ADMIN_IDS = [1]
APP_ENV = 'dev' # Default is prod
\ No newline at end of file
......@@ -25,6 +25,7 @@ footer { position: fixed;
color: white;
width:100%;
text-align: center;
z-index: 10;
}
#deconnect, #password_change {float:right; margin-left: 5px;}
......@@ -59,6 +60,11 @@ footer { position: fixed;
margin: auto;
padding:15px;
}
@media screen and (max-width:768px) {
.overlay-content .mconfirm {
width: 100%;
}
}
.overlay-content .mconfirm button {margin:5px;}
.overlay-content > em {
color: #fff;
......
table.dataTable.dtr-inline.collapsed>tbody>tr>td.child,table.dataTable.dtr-inline.collapsed>tbody>tr>th.child,table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty{cursor:default !important}table.dataTable.dtr-inline.collapsed>tbody>tr>td.child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th.child:before,table.dataTable.dtr-inline.collapsed>tbody>tr>td.dataTables_empty:before{display:none !important}table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control,table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control{position:relative;padding-left:30px;cursor:pointer}table.dataTable.dtr-inline.collapsed>tbody>tr>td.dtr-control:before,table.dataTable.dtr-inline.collapsed>tbody>tr>th.dtr-control:before{top:50%;left:5px;height:1em;width:1em;margin-top:-9px;display:block;position:absolute;color:white;border:.15em solid white;border-radius:1em;box-shadow:0 0 .2em #444;box-sizing:content-box;text-align:center;text-indent:0 !important;font-family:"Courier New",Courier,monospace;line-height:1em;content:"+";background-color:#31b131}table.dataTable.dtr-inline.collapsed>tbody>tr.parent>td.dtr-control:before,table.dataTable.dtr-inline.collapsed>tbody>tr.parent>th.dtr-control:before{content:"-";background-color:#d33333}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td.dtr-control,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th.dtr-control{padding-left:27px}table.dataTable.dtr-inline.collapsed.compact>tbody>tr>td.dtr-control:before,table.dataTable.dtr-inline.collapsed.compact>tbody>tr>th.dtr-control:before{left:4px;height:14px;width:14px;border-radius:14px;line-height:14px;text-indent:3px}table.dataTable.dtr-column>tbody>tr>td.dtr-control,table.dataTable.dtr-column>tbody>tr>th.dtr-control,table.dataTable.dtr-column>tbody>tr>td.control,table.dataTable.dtr-column>tbody>tr>th.control{position:relative;cursor:pointer}table.dataTable.dtr-column>tbody>tr>td.dtr-control:before,table.dataTable.dtr-column>tbody>tr>th.dtr-control:before,table.dataTable.dtr-column>tbody>tr>td.control:before,table.dataTable.dtr-column>tbody>tr>th.control:before{top:50%;left:50%;height:.8em;width:.8em;margin-top:-0.5em;margin-left:-0.5em;display:block;position:absolute;color:white;border:.15em solid white;border-radius:1em;box-shadow:0 0 .2em #444;box-sizing:content-box;text-align:center;text-indent:0 !important;font-family:"Courier New",Courier,monospace;line-height:1em;content:"+";background-color:#31b131}table.dataTable.dtr-column>tbody>tr.parent td.dtr-control:before,table.dataTable.dtr-column>tbody>tr.parent th.dtr-control:before,table.dataTable.dtr-column>tbody>tr.parent td.control:before,table.dataTable.dtr-column>tbody>tr.parent th.control:before{content:"-";background-color:#d33333}table.dataTable>tbody>tr.child{padding:.5em 1em}table.dataTable>tbody>tr.child:hover{background:transparent !important}table.dataTable>tbody>tr.child ul.dtr-details{display:inline-block;list-style-type:none;margin:0;padding:0}table.dataTable>tbody>tr.child ul.dtr-details>li{border-bottom:1px solid #efefef;padding:.5em 0}table.dataTable>tbody>tr.child ul.dtr-details>li:first-child{padding-top:0}table.dataTable>tbody>tr.child ul.dtr-details>li:last-child{border-bottom:none}table.dataTable>tbody>tr.child span.dtr-title{display:inline-block;min-width:75px;font-weight:bold}div.dtr-modal{position:fixed;box-sizing:border-box;top:0;left:0;height:100%;width:100%;z-index:100;padding:10em 1em}div.dtr-modal div.dtr-modal-display{position:absolute;top:0;left:0;bottom:0;right:0;width:50%;height:50%;overflow:auto;margin:auto;z-index:102;overflow:auto;background-color:#f5f5f7;border:1px solid black;border-radius:.5em;box-shadow:0 12px 30px rgba(0, 0, 0, 0.6)}div.dtr-modal div.dtr-modal-content{position:relative;padding:1em}div.dtr-modal div.dtr-modal-close{position:absolute;top:6px;right:6px;width:22px;height:22px;border:1px solid #eaeaea;background-color:#f9f9f9;text-align:center;border-radius:3px;cursor:pointer;z-index:12}div.dtr-modal div.dtr-modal-close:hover{background-color:#eaeaea}div.dtr-modal div.dtr-modal-background{position:fixed;top:0;left:0;right:0;bottom:0;z-index:101;background:rgba(0, 0, 0, 0.6)}@media screen and (max-width: 767px){div.dtr-modal div.dtr-modal-display{width:95%}}
#main_content {text-align: center;}
.param {margin-bottom: 15px;}
.param label {font-weight: bold;}
input.link {min-width: 50em;}
.submit_button {margin-bottom: 10px;}
/* Style the buttons that are used to open and close the accordion panel */
.accordion {
background-color: #eee;
color: #444;
cursor: pointer;
padding: 18px;
width: 100%;
text-align: left;
border: none;
outline: none;
transition: 0.4s;
}
/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
.active, .accordion:hover {
background-color: #ccc;
}
/* Style the accordion panel. Note: hidden by default */
.panel {
padding: 0 18px;
background-color: white;
display: none;
overflow: hidden;
}
button.accordion::after {
content: '\002B';
color: #777;
font-weight: bold;
float: right;
margin-left: 5px;
}
button.accordion.active::after {
content: "\2212";
}
MIT License
Copyright (c) 2021 Adam Shaw
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# FullCalendar
A full-sized drag & drop JavaScript event calendar
- [Project website and demos](http://fullcalendar.io/)
- [Documentation](http://fullcalendar.io/docs)
- [Support](http://fullcalendar.io/support)
- [Contributing](CONTRIBUTING.md)
- [Changelog](CHANGELOG.md)
- [License](LICENSE.txt)
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
},
initialDate: '2020-09-12',
navLinks: true, // can click day/week names to navigate views
businessHours: true, // display business hours
editable: true,
selectable: true,
events: [
{
title: 'Business Lunch',
start: '2020-09-03T13:00:00',
constraint: 'businessHours'
},
{
title: 'Meeting',
start: '2020-09-13T11:00:00',
constraint: 'availableForMeeting', // defined below
color: '#257e4a'
},
{
title: 'Conference',
start: '2020-09-18',
end: '2020-09-20'
},
{
title: 'Party',
start: '2020-09-29T20:00:00'
},
// areas where "Meeting" must be dropped
{
groupId: 'availableForMeeting',
start: '2020-09-11T10:00:00',
end: '2020-09-11T16:00:00',
display: 'background'
},
{
groupId: 'availableForMeeting',
start: '2020-09-13T10:00:00',
end: '2020-09-13T16:00:00',
display: 'background'
},
// red areas where no events can be dropped
{
start: '2020-09-24',
end: '2020-09-28',
overlap: false,
display: 'background',
color: '#ff9f89'
},
{
start: '2020-09-06',
end: '2020-09-08',
overlap: false,
display: 'background',
color: '#ff9f89'
}
]
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prevYear,prev,next,nextYear today',
center: 'title',
right: 'dayGridMonth,dayGridWeek,dayGridDay'
},
initialDate: '2020-09-12',
navLinks: true, // can click day/week names to navigate views
editable: true,
dayMaxEvents: true, // allow "more" link when too many events
events: [
{
title: 'All Day Event',
start: '2020-09-01'
},
{
title: 'Long Event',
start: '2020-09-07',
end: '2020-09-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-16T16:00:00'
},
{
title: 'Conference',
start: '2020-09-11',
end: '2020-09-13'
},
{
title: 'Meeting',
start: '2020-09-12T10:30:00',
end: '2020-09-12T12:30:00'
},
{
title: 'Lunch',
start: '2020-09-12T12:00:00'
},
{
title: 'Meeting',
start: '2020-09-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2020-09-12T17:30:00'
},
{
title: 'Dinner',
start: '2020-09-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2020-09-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2020-09-28'
}
]
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var srcCalendarEl = document.getElementById('source-calendar');
var destCalendarEl = document.getElementById('destination-calendar');
var srcCalendar = new FullCalendar.Calendar(srcCalendarEl, {
editable: true,
initialDate: '2020-09-12',
events: [
{
title: 'event1',
start: '2020-09-11T10:00:00',
end: '2020-09-11T16:00:00'
},
{
title: 'event2',
start: '2020-09-13T10:00:00',
end: '2020-09-13T16:00:00'
}
],
eventLeave: function(info) {
console.log('event left!', info.event);
}
});
var destCalendar = new FullCalendar.Calendar(destCalendarEl, {
initialDate: '2020-09-12',
editable: true,
droppable: true, // will let it receive events!
eventReceive: function(info) {
console.log('event received!', info.event);
}
});
srcCalendar.render();
destCalendar.render();
});
</script>
<style>
body {
margin: 20px 0 0 20px;
font-size: 14px;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
}
#source-calendar,
#destination-calendar {
float: left;
width: 600px;
margin: 0 20px 20px 0;
}
</style>
</head>
<body>
<div id='source-calendar'></div>
<div id='destination-calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
/* initialize the external events
-----------------------------------------------------------------*/
var containerEl = document.getElementById('external-events-list');
new FullCalendar.Draggable(containerEl, {
itemSelector: '.fc-event',
eventData: function(eventEl) {
return {
title: eventEl.innerText.trim()
}
}
});
//// the individual way to do it
// var containerEl = document.getElementById('external-events-list');
// var eventEls = Array.prototype.slice.call(
// containerEl.querySelectorAll('.fc-event')
// );
// eventEls.forEach(function(eventEl) {
// new FullCalendar.Draggable(eventEl, {
// eventData: {
// title: eventEl.innerText.trim(),
// }
// });
// });
/* initialize the calendar
-----------------------------------------------------------------*/
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
},
editable: true,
droppable: true, // this allows things to be dropped onto the calendar
drop: function(arg) {
// is the "remove after drop" checkbox checked?
if (document.getElementById('drop-remove').checked) {
// if so, remove the element from the "Draggable Events" list
arg.draggedEl.parentNode.removeChild(arg.draggedEl);
}
}
});
calendar.render();
});
</script>
<style>
body {
margin-top: 40px;
font-size: 14px;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
}
#external-events {
position: fixed;
left: 20px;
top: 20px;
width: 150px;
padding: 0 10px;
border: 1px solid #ccc;
background: #eee;
text-align: left;
}
#external-events h4 {
font-size: 16px;
margin-top: 0;
padding-top: 1em;
}
#external-events .fc-event {
margin: 3px 0;
cursor: move;
}
#external-events p {
margin: 1.5em 0;
font-size: 11px;
color: #666;
}
#external-events p input {
margin: 0;
vertical-align: middle;
}
#calendar-wrap {
margin-left: 200px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='wrap'>
<div id='external-events'>
<h4>Draggable Events</h4>
<div id='external-events-list'>
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
<div class='fc-event-main'>My Event 1</div>
</div>
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
<div class='fc-event-main'>My Event 2</div>
</div>
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
<div class='fc-event-main'>My Event 3</div>
</div>
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
<div class='fc-event-main'>My Event 4</div>
</div>
<div class='fc-event fc-h-event fc-daygrid-event fc-daygrid-block-event'>
<div class='fc-event-main'>My Event 5</div>
</div>
</div>
<p>
<input type='checkbox' id='drop-remove' />
<label for='drop-remove'>remove after drop</label>
</p>
</div>
<div id='calendar-wrap'>
<div id='calendar'></div>
</div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
height: '100%',
expandRows: true,
slotMinTime: '08:00',
slotMaxTime: '20:00',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
},
initialView: 'dayGridMonth',
initialDate: '2020-09-12',
navLinks: true, // can click day/week names to navigate views
editable: true,
selectable: true,
nowIndicator: true,
dayMaxEvents: true, // allow "more" link when too many events
events: [
{
title: 'All Day Event',
start: '2020-09-01',
},
{
title: 'Long Event',
start: '2020-09-07',
end: '2020-09-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-16T16:00:00'
},
{
title: 'Conference',
start: '2020-09-11',
end: '2020-09-13'
},
{
title: 'Meeting',
start: '2020-09-12T10:30:00',
end: '2020-09-12T12:30:00'
},
{
title: 'Lunch',
start: '2020-09-12T12:00:00'
},
{
title: 'Meeting',
start: '2020-09-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2020-09-12T17:30:00'
},
{
title: 'Dinner',
start: '2020-09-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2020-09-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2020-09-28'
}
]
});
calendar.render();
});
</script>
<style>
html, body {
overflow: hidden; /* don't do scrollbars */
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.fc-header-toolbar {
/*
the calendar will be butting up against the edges,
but let's scoot in the header's buttons
*/
padding-top: 1em;
padding-left: 1em;
padding-right: 1em;
}
</style>
</head>
<body>
<div id='calendar-container'>
<div id='calendar'></div>
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,listYear'
},
displayEventTime: false, // don't show the time column in list view
// THIS KEY WON'T WORK IN PRODUCTION!!!
// To make your own Google API key, follow the directions here:
// http://fullcalendar.io/docs/google_calendar/
googleCalendarApiKey: 'AIzaSyDcnW6WejpTOCffshGDDb4neIrXVUA1EAE',
// US Holidays
events: 'en.usa#holiday@group.v.calendar.google.com',
eventClick: function(arg) {
// opens events in a popup window
window.open(arg.event.url, 'google-calendar-event', 'width=700,height=600');
arg.jsEvent.preventDefault() // don't navigate in main tab
},
loading: function(bool) {
document.getElementById('loading').style.display =
bool ? 'block' : 'none';
}
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#loading {
display: none;
position: absolute;
top: 10px;
right: 10px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='loading'>loading...</div>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='https://github.com/mozilla-comm/ical.js/releases/download/v1.4.0/ical.js'></script>
<script src='../lib/main.js'></script>
<script src='../packages/icalendar/main.global.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
displayEventTime: false,
initialDate: '2019-04-01',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,listYear'
},
events: {
url: 'ics/feed.ics',
format: 'ics',
failure: function() {
document.getElementById('script-warning').style.display = 'block';
}
},
loading: function(bool) {
document.getElementById('loading').style.display =
bool ? 'block' : 'none';
}
});
calendar.render();
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#script-warning {
display: none;
background: #eee;
border-bottom: 1px solid #ddd;
padding: 0 10px;
line-height: 40px;
text-align: center;
font-weight: bold;
font-size: 12px;
color: red;
}
#loading {
display: none;
position: absolute;
top: 10px;
right: 10px;
}
#calendar {
max-width: 1100px;
margin: 40px auto;
padding: 0 10px;
}
</style>
</head>
<body>
<div id='script-warning'>
<code>ics/feed.ics</code> must be servable
</div>
<div id='loading'>loading...</div>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
},
initialDate: '2020-09-12',
editable: true,
navLinks: true, // can click day/week names to navigate views
dayMaxEvents: true, // allow "more" link when too many events
events: {
url: 'php/get-events.php',
failure: function() {
document.getElementById('script-warning').style.display = 'block'
}
},
loading: function(bool) {
document.getElementById('loading').style.display =
bool ? 'block' : 'none';
}
});
calendar.render();
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#script-warning {
display: none;
background: #eee;
border-bottom: 1px solid #ddd;
padding: 0 10px;
line-height: 40px;
text-align: center;
font-weight: bold;
font-size: 12px;
color: red;
}
#loading {
display: none;
position: absolute;
top: 10px;
right: 10px;
}
#calendar {
max-width: 1100px;
margin: 40px auto;
padding: 0 10px;
}
</style>
</head>
<body>
<div id='script-warning'>
<code>php/get-events.php</code> must be running.
</div>
<div id='loading'>loading...</div>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
height: 'auto',
// stickyHeaderDates: false, // for disabling
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'listMonth,listYear'
},
// customize the button names,
// otherwise they'd all just say "list"
views: {
listMonth: { buttonText: 'list month' },
listYear: { buttonText: 'list year' }
},
initialView: 'listYear',
initialDate: '2020-09-12',
navLinks: true, // can click day/week names to navigate views
editable: true,
events: [
{
title: 'repeating event 1',
daysOfWeek: [ 1, 2, 3 ],
duration: '00:30'
},
{
title: 'repeating event 2',
daysOfWeek: [ 1, 2, 3 ],
duration: '00:30'
},
{
title: 'repeating event 3',
daysOfWeek: [ 1, 2, 3 ],
duration: '00:30'
}
]
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'listDay,listWeek'
},
// customize the button names,
// otherwise they'd all just say "list"
views: {
listDay: { buttonText: 'list day' },
listWeek: { buttonText: 'list week' }
},
initialView: 'listWeek',
initialDate: '2020-09-12',
navLinks: true, // can click day/week names to navigate views
editable: true,
dayMaxEvents: true, // allow "more" link when too many events
events: [
{
title: 'All Day Event',
start: '2020-09-01'
},
{
title: 'Long Event',
start: '2020-09-07',
end: '2020-09-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-16T16:00:00'
},
{
title: 'Conference',
start: '2020-09-11',
end: '2020-09-13'
},
{
title: 'Meeting',
start: '2020-09-12T10:30:00',
end: '2020-09-12T12:30:00'
},
{
title: 'Lunch',
start: '2020-09-12T12:00:00'
},
{
title: 'Meeting',
start: '2020-09-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2020-09-12T17:30:00'
},
{
title: 'Dinner',
start: '2020-09-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2020-09-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2020-09-28'
}
]
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script src='../lib/locales-all.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var initialLocaleCode = 'en';
var localeSelectorEl = document.getElementById('locale-selector');
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
},
initialDate: '2020-09-12',
locale: initialLocaleCode,
buttonIcons: false, // show the prev/next text
weekNumbers: true,
navLinks: true, // can click day/week names to navigate views
editable: true,
dayMaxEvents: true, // allow "more" link when too many events
events: [
{
title: 'All Day Event',
start: '2020-09-01'
},
{
title: 'Long Event',
start: '2020-09-07',
end: '2020-09-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-16T16:00:00'
},
{
title: 'Conference',
start: '2020-09-11',
end: '2020-09-13'
},
{
title: 'Meeting',
start: '2020-09-12T10:30:00',
end: '2020-09-12T12:30:00'
},
{
title: 'Lunch',
start: '2020-09-12T12:00:00'
},
{
title: 'Meeting',
start: '2020-09-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2020-09-12T17:30:00'
},
{
title: 'Dinner',
start: '2020-09-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2020-09-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2020-09-28'
}
]
});
calendar.render();
// build the locale selector's options
calendar.getAvailableLocaleCodes().forEach(function(localeCode) {
var optionEl = document.createElement('option');
optionEl.value = localeCode;
optionEl.selected = localeCode == initialLocaleCode;
optionEl.innerText = localeCode;
localeSelectorEl.appendChild(optionEl);
});
// when the selected option changes, dynamically change the calendar option
localeSelectorEl.addEventListener('change', function() {
if (this.value) {
calendar.setOption('locale', this.value);
}
});
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#top {
background: #eee;
border-bottom: 1px solid #ddd;
padding: 0 10px;
line-height: 40px;
font-size: 12px;
}
#calendar {
max-width: 1100px;
margin: 40px auto;
padding: 0 10px;
}
</style>
</head>
<body>
<div id='top'>
Locales:
<select id='locale-selector'></select>
</div>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialDate: '2020-09-12',
editable: true,
selectable: true,
businessHours: true,
dayMaxEvents: true, // allow "more" link when too many events
events: [
{
title: 'All Day Event',
start: '2020-09-01'
},
{
title: 'Long Event',
start: '2020-09-07',
end: '2020-09-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-16T16:00:00'
},
{
title: 'Conference',
start: '2020-09-11',
end: '2020-09-13'
},
{
title: 'Meeting',
start: '2020-09-12T10:30:00',
end: '2020-09-12T12:30:00'
},
{
title: 'Lunch',
start: '2020-09-12T12:00:00'
},
{
title: 'Meeting',
start: '2020-09-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2020-09-12T17:30:00'
},
{
title: 'Dinner',
start: '2020-09-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2020-09-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2020-09-28'
}
]
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
initialDate: '2020-09-12',
initialView: 'timeGridWeek',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
},
height: 'auto',
navLinks: true, // can click day/week names to navigate views
editable: true,
selectable: true,
selectMirror: true,
nowIndicator: true,
events: [
{
title: 'All Day Event',
start: '2020-09-01',
},
{
title: 'Long Event',
start: '2020-09-07',
end: '2020-09-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-16T16:00:00'
},
{
title: 'Conference',
start: '2020-09-11',
end: '2020-09-13'
},
{
title: 'Meeting',
start: '2020-09-12T10:30:00',
end: '2020-09-12T12:30:00'
},
{
title: 'Lunch',
start: '2020-09-12T12:00:00'
},
{
title: 'Meeting',
start: '2020-09-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2020-09-12T17:30:00'
},
{
title: 'Dinner',
start: '2020-09-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2020-09-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2020-09-28'
}
]
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay'
},
initialDate: '2020-09-12',
navLinks: true, // can click day/week names to navigate views
selectable: true,
selectMirror: true,
select: function(arg) {
var title = prompt('Event Title:');
if (title) {
calendar.addEvent({
title: title,
start: arg.start,
end: arg.end,
allDay: arg.allDay
})
}
calendar.unselect()
},
eventClick: function(arg) {
if (confirm('Are you sure you want to delete this event?')) {
arg.event.remove()
}
},
editable: true,
dayMaxEvents: true, // allow "more" link when too many events
events: [
{
title: 'All Day Event',
start: '2020-09-01'
},
{
title: 'Long Event',
start: '2020-09-07',
end: '2020-09-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-16T16:00:00'
},
{
title: 'Conference',
start: '2020-09-11',
end: '2020-09-13'
},
{
title: 'Meeting',
start: '2020-09-12T10:30:00',
end: '2020-09-12T12:30:00'
},
{
title: 'Lunch',
start: '2020-09-12T12:00:00'
},
{
title: 'Meeting',
start: '2020-09-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2020-09-12T17:30:00'
},
{
title: 'Dinner',
start: '2020-09-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2020-09-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2020-09-28'
}
]
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='https://use.fontawesome.com/releases/v5.0.6/css/all.css' rel='stylesheet'>
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script src='js/theme-chooser.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar;
initThemeChooser({
init: function(themeSystem) {
calendar = new FullCalendar.Calendar(calendarEl, {
themeSystem: themeSystem,
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
},
initialDate: '2020-09-12',
weekNumbers: true,
navLinks: true, // can click day/week names to navigate views
editable: true,
selectable: true,
nowIndicator: true,
dayMaxEvents: true, // allow "more" link when too many events
// showNonCurrentDates: false,
events: [
{
title: 'All Day Event',
start: '2020-09-01'
},
{
title: 'Long Event',
start: '2020-09-07',
end: '2020-09-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-16T16:00:00'
},
{
title: 'Conference',
start: '2020-09-11',
end: '2020-09-13'
},
{
title: 'Meeting',
start: '2020-09-12T10:30:00',
end: '2020-09-12T12:30:00'
},
{
title: 'Lunch',
start: '2020-09-12T12:00:00'
},
{
title: 'Meeting',
start: '2020-09-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2020-09-12T17:30:00'
},
{
title: 'Dinner',
start: '2020-09-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2020-09-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2020-09-28'
}
]
});
calendar.render();
},
change: function(themeSystem) {
calendar.setOption('themeSystem', themeSystem);
}
});
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-size: 14px;
}
#top,
#calendar.fc-theme-standard {
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
}
#calendar.fc-theme-bootstrap {
font-size: 14px;
}
#top {
background: #eee;
border-bottom: 1px solid #ddd;
padding: 0 10px;
line-height: 40px;
font-size: 12px;
color: #000;
}
#top .selector {
display: inline-block;
margin-right: 10px;
}
#top select {
font: inherit; /* mock what Boostrap does, don't compete */
}
.left { float: left }
.right { float: right }
.clear { clear: both }
#calendar {
max-width: 1100px;
margin: 40px auto;
padding: 0 10px;
}
</style>
</head>
<body>
<div id='top'>
<div class='left'>
<div id='theme-system-selector' class='selector'>
Theme System:
<select>
<option value='bootstrap' selected>Bootstrap 4</option>
<option value='standard'>unthemed</option>
</select>
</div>
<div data-theme-system="bootstrap" class='selector' style='display:none'>
Theme Name:
<select>
<option value='' selected>Default</option>
<option value='cerulean'>Cerulean</option>
<option value='cosmo'>Cosmo</option>
<option value='cyborg'>Cyborg</option>
<option value='darkly'>Darkly</option>
<option value='flatly'>Flatly</option>
<option value='journal'>Journal</option>
<option value='litera'>Litera</option>
<option value='lumen'>Lumen</option>
<option value='lux'>Lux</option>
<option value='materia'>Materia</option>
<option value='minty'>Minty</option>
<option value='pulse'>Pulse</option>
<option value='sandstone'>Sandstone</option>
<option value='simplex'>Simplex</option>
<option value='sketchy'>Sketchy</option>
<option value='slate'>Slate</option>
<option value='solar'>Solar</option>
<option value='spacelab'>Spacelab</option>
<option value='superhero'>Superhero</option>
<option value='united'>United</option>
<option value='yeti'>Yeti</option>
</select>
</div>
<span id='loading' style='display:none'>loading theme...</span>
</div>
<div class='right'>
<span class='credits' data-credit-id='bootstrap-standard' style='display:none'>
<a href='https://getbootstrap.com/docs/3.3/' target='_blank'>Theme by Bootstrap</a>
</span>
<span class='credits' data-credit-id='bootstrap-custom' style='display:none'>
<a href='https://bootswatch.com/' target='_blank'>Theme by Bootswatch</a>
</span>
</div>
<div class='clear'></div>
</div>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var initialTimeZone = 'local';
var timeZoneSelectorEl = document.getElementById('time-zone-selector');
var loadingEl = document.getElementById('loading');
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
timeZone: initialTimeZone,
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
},
initialDate: '2020-09-12',
navLinks: true, // can click day/week names to navigate views
editable: true,
selectable: true,
dayMaxEvents: true, // allow "more" link when too many events
events: {
url: 'php/get-events.php',
failure: function() {
document.getElementById('script-warning').style.display = 'inline'; // show
}
},
loading: function(bool) {
if (bool) {
loadingEl.style.display = 'inline'; // show
} else {
loadingEl.style.display = 'none'; // hide
}
},
eventTimeFormat: { hour: 'numeric', minute: '2-digit', timeZoneName: 'short' },
dateClick: function(arg) {
console.log('dateClick', calendar.formatIso(arg.date));
},
select: function(arg) {
console.log('select', calendar.formatIso(arg.start), calendar.formatIso(arg.end));
}
});
calendar.render();
// load the list of available timezones, build the <select> options
// it's HIGHLY recommended to use a different library for network requests, not this internal util func
FullCalendar.requestJson('GET', 'php/get-time-zones.php', {}, function(timeZones) {
timeZones.forEach(function(timeZone) {
var optionEl;
if (timeZone !== 'UTC') { // UTC is already in the list
optionEl = document.createElement('option');
optionEl.value = timeZone;
optionEl.innerText = timeZone;
timeZoneSelectorEl.appendChild(optionEl);
}
});
}, function() {
// TODO: handle error
});
// when the timezone selector changes, dynamically change the calendar option
timeZoneSelectorEl.addEventListener('change', function() {
calendar.setOption('timeZone', this.value);
});
});
</script>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
}
#top {
background: #eee;
border-bottom: 1px solid #ddd;
padding: 0 10px;
line-height: 40px;
font-size: 12px;
}
.left { float: left }
.right { float: right }
.clear { clear: both }
#script-warning, #loading { display: none }
#script-warning { font-weight: bold; color: red }
#calendar {
max-width: 1100px;
margin: 40px auto;
padding: 0 10px;
}
.tzo {
color: #000;
}
</style>
</head>
<body>
<div id='top'>
<div class='left'>
Timezone:
<select id='time-zone-selector'>
<option value='local' selected>local</option>
<option value='UTC'>UTC</option>
</select>
</div>
<div class='right'>
<span id='loading'>loading...</span>
<span id='script-warning'><code>php/get-events.php</code> must be running.</span>
</div>
<div class='clear'></div>
</div>
<div id='calendar'></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<link href='../lib/main.css' rel='stylesheet' />
<script src='../lib/main.js'></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar');
var calendar = new FullCalendar.Calendar(calendarEl, {
height: 'auto', // enough to active sticky headers
dayMinWidth: 200,
slotDuration: '00:05:00',
initialDate: '2020-09-12',
initialView: 'timeGridWeek',
nowIndicator: true,
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek'
},
navLinks: true, // can click day/week names to navigate views
editable: true,
selectable: true,
selectMirror: true,
dayMaxEvents: true, // allow "more" link when too many events
events: [
{
title: 'All Day Event',
start: '2020-09-01',
},
{
title: 'Long Event',
start: '2020-09-07',
end: '2020-09-10'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-09T16:00:00'
},
{
groupId: 999,
title: 'Repeating Event',
start: '2020-09-16T16:00:00'
},
{
title: 'Conference',
start: '2020-09-11',
end: '2020-09-13'
},
{
title: 'Meeting',
start: '2020-09-12T10:30:00',
end: '2020-09-12T12:30:00'
},
{
title: 'Lunch',
start: '2020-09-12T12:00:00'
},
{
title: 'Meeting',
start: '2020-09-12T14:30:00'
},
{
title: 'Happy Hour',
start: '2020-09-12T17:30:00'
},
{
title: 'Dinner',
start: '2020-09-12T20:00:00'
},
{
title: 'Birthday Party',
start: '2020-09-13T07:00:00'
},
{
title: 'Click for Google',
url: 'http://google.com/',
start: '2020-09-28'
}
]
});
calendar.render();
});
</script>
<style>
body {
margin: 40px 10px;
padding: 0;
font-family: Arial, Helvetica Neue, Helvetica, sans-serif;
font-size: 14px;
text-align: center;
}
#calendar {
max-width: 1100px;
margin: 0 auto;
}
</style>
</head>
<body>
<p style='margin-bottom: 5em'>
Demo for sticky header. Also, the bottom scrollbars stick.
</p>
<div id='calendar'></div>
<p style='margin-top: 5em'>
Cool, right?
</p>
</body>
</html>
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment