Made a fair bit of progress with the calendar editor.

This commit is contained in:
2025-07-02 17:40:47 +10:00
parent f8616ff819
commit b6d51c7cfa
3 changed files with 163 additions and 54 deletions

View File

@@ -1,7 +1,47 @@
.tabbody { body { display: none; }
border-bottom: solid 1px rgb(222, 226, 230); .bounding-box {
border-left: solid 1px rgb(222, 226, 230); border: solid 1px rgb(222, 226, 230);
border-right: solid 1px rgb(222, 226, 230); border-radius: 10px;
border-bottom-left-radius: 10px; padding: 10px;
border-bottom-right-radius: 10px; margin-top: 10px;
height: 96vh;
overflow-y: scroll;
overflow-x: clip;
}
.selection td {
background-color: darkgrey;
color: white;
}
.tt {
font-family: monospace;
}
.modal-form-background {
background-color: white;
position: absolute;
left: 0px;
top: 0px;
width: 100vw;
height: 100vh;
opacity: 0.8;
display: flex;
}
.modal-form {
background-color: transparent;
position: absolute;
left: 0px;
top: 0px;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
z-index: 3;
}
.modal-form-body {
background-color: #fff; /* optional: distinguish it */
padding: 2rem; /* optional: add spacing */
border-radius: 8px; /* optional: rounded corners */
box-shadow: 0 0 10px rgba(0,0,0,0.2); /* optional: subtle shadow */
opacity: 1;
z-index: 4;
} }

View File

@@ -1,8 +1,61 @@
Vue.createApp({ Vue.createApp({
data: function() { data: function() {
return { 'events': null return { 'events': null
, 'tabs': [ 'Events', 'Create' ] , 'frm': { 'e': null, 'modal': null }
, 'frm': { 'tab': 0, 'e': null } , 'faults': null
, 'patterns': { 'coordinates': [ '^$', '^-?\\d{1,3}(?:\\.\\d+)?,-?\\d{1,3}(?:\\.\\d+)?$' ]
, 'start': [ '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$' ]
, 'end': [ '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$' ]
, 'title': [ '^.{20,50}$' ]
, 'description': [ '^.{20,}$' ]
, 'location': [ '^.{5,100}$' ]
}
}
},
methods: { formatDate: function(dt) {
var fmt = 'DD MMM YYYY';
var m = moment(dt);
var t = m.hour() * 3600 + m.minute() * 60 + m.second();
if ( t > 0 ) {
fmt = 'DD MMM YYYY, HH:mm:ss';
}
var ret = moment(dt).format(fmt);
return ret;
},
deleteEvent: function() {
this.events.splice(this.frm.e, 1);
this.frm.e = null;
this.validateEvents();
},
validateEvents: function() {
var faults = [];
for ( var e in this.events ) {
for ( var k in this.patterns ) {
var value = this.events[e][k];
value = ( value == undefined ? '' : value );
var patterns = this.patterns[k];
var match = false;
for ( var p in patterns ) {
var pattern = new RegExp(this.patterns[k][p]);
match = match || pattern.test(value);
}
console.log(e, k, value, match);
if ( ! match ) {
var fault = {};
fault.e = e;
fault.k = k;
fault.v = value;
faults.push(fault);
}
}
}
this.faults = ( faults.length == 0 ? null : faults );
},
modal: function(property) {
var d = {};
d.property = property;
d.value = this.events[this.frm.e][property];
this.frm.modal = d;
} }
}, },
mounted: function() { mounted: function() {
@@ -11,6 +64,8 @@ Vue.createApp({
, { 'cmd': 'get' } , { 'cmd': 'get' }
, function(data) { , function(data) {
app.events = data; app.events = data;
app.validateEvents();
$('body').fadeIn();
} }
); );
} }

View File

@@ -13,58 +13,72 @@
</head> </head>
<body> <body>
<div class="container" id="app"> <div class="container-fluid" id="app">
<ul class="nav nav-tabs"> <div class="modal-form-background" v-if="frm.modal != null">
<li class="nav-item" v-for="lbl, t in tabs" v-on:click="frm.tab = t; frm.e = null;"> </div>
<a class="nav-link" :class="{ 'active': frm.tab == t }" aria-current="page" href="#">{{lbl}}</a> <div class="modal-form" v-if="frm.modal != null">
</li> <div class="modal-form-body" v-if="frm.modal != null">
</ul> <input class="form-control" v-if="[ 'start', 'end' ].indexOf(frm.modal.property) != -1" type="datetime-local" v-model="frm.modal.value">
<div v-if="frm.tab == 0" class="tabbody"> <hr />
<div class="btn-group" role="group" style="width: 100%;">
<button type="button" class="btn btn-warning" v-on:click="frm.modal = null;">Cancel</button>
<button type="button" class="btn btn-success" v-on:click="events[frm.e][frm.modal.property] = frm.modal.value; frm.modal = null;">Update</button>
</div>
</div>
</div>
<div class="bounding-box">
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-4">
<table class="table"> <table class="table">
<tbody> <tbody>
<tr v-for="event, e in events" v-on:click="frm.e = e"> <tr v-for="event, e in events" v-on:click="frm.e = e" :class="{ 'selection': frm.e == e }">
<td>{{event.start}}</td> <td><span class="badge text-bg-info">{{ e + 1 }}</span></td>
<td>{{event.end}}</td> <td>{{ formatDate(event.start) }}</td>
<td>{{ formatDate(event.end) }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<button v-if="faults == null" class="btn btn-primary form-control">Save</button>
<ul v-else>
<li v-for="fault, f in faults">
<span class="badge text-bg-info">{{ parseInt(fault.e) + 1 }}</span> <span class="tt">{{fault.k}}</span></span>
</li>
</ul>
</div> </div>
<div class="col-6" v-if="frm.e != null"> <div class="col-8" v-if="frm.e != null">
<div class="input-group mb-1"> <div class="input-group mb-1">
<span class="input-group-text">Start</span> <span class="input-group-text">Start</span>
<input type="text" class="form-control" v-model="events[frm.e].start"> <input type="text" disabled class="form-control" v-model="events[frm.e].start" v-on:keyup="validateEvents">
<button class="btn btn-outline-secondary" type="button" v-on:click="modal('start')">...</button>
</div> </div>
<div class="input-group mb-1"> <div class="input-group mb-1">
<span class="input-group-text">End</span> <span class="input-group-text">End</span>
<input type="text" class="form-control" v-model="events[frm.e].end"> <input type="text" disabled class="form-control" v-model="events[frm.e].end" v-on:keyup="validateEvents">
<button class="btn btn-outline-secondary" type="button" v-on:click="modal('end')">...</button>
</div> </div>
<div class="input-group mb-1"> <div class="input-group mb-1">
<span class="input-group-text">Title</span> <span class="input-group-text">Title</span>
<input type="text" class="form-control" v-model="events[frm.e].title"> <input type="text" class="form-control" v-model="events[frm.e].title" v-on:keyup="validateEvents">
</div> </div>
<div class="input-group mb-1"> <div class="input-group mb-1">
<span class="input-group-text">Description</span> <span class="input-group-text">Description</span>
<input type="text" class="form-control" v-model="events[frm.e].description"> <input type="text" class="form-control" v-model="events[frm.e].description" v-on:keyup="validateEvents">
</div> </div>
<div class="input-group mb-1"> <div class="input-group mb-1">
<span class="input-group-text">Location</span> <span class="input-group-text">Location</span>
<input type="text" class="form-control" v-model="events[frm.e].location"> <input type="text" class="form-control" v-model="events[frm.e].location" v-on:keyup="validateEvents">
</div> </div>
<div class="input-group mb-1"> <div class="input-group mb-1">
<span class="input-group-text">Coordinates</span> <span class="input-group-text">Coordinates</span>
<input type="text" class="form-control" v-model="events[frm.e].coordinates"> <input type="text" class="form-control" v-model="events[frm.e].coordinates" v-on:keyup="validateEvents">
</div>
<button class="btn btn-danger btn-lg form-control mt-2" v-on:click="deleteEvent">Delete</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div v-else-if="frm.tab == 1" class="tabbody">
<h1>Create</h1>
</div>
<pre>{{events}}</pre>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment-with-locales.min.js" integrity="sha512-4F1cxYdMiAW98oomSLaygEwmCnIP38pb4Kx70yQYqRwLVCs3DbRumfBq82T08g/4LJ/smbFGFpmeFlQgoDccgg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.7.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
<script src="index.js"></script> <script src="index.js"></script>