5 $multi_seedlot => !(defined $seedlot_id && defined $seedlot_name) ? 1 : 0
12 $user_role = $c->user->get_object()->get_user_type();
13 $user_name = $c->user->get_object()->get_username();
17 <& /util/import_javascript.mas, classes => [ 'CXGN.List', 'jquery', 'jquery.dataTables', 'jquery.dataTables-buttons-min', 'jszip-min', 'buttons.bootstrap-min', 'buttons.html5-min' ], &>
21 <&| /page/info_section.mas, title=>"Filter Events", collapsible=>1, collapsed=>!$show_filters, subtitle=>"Filter maintenance events based on date, type, and/or value" &>
25 <p>Add one or more filters to apply to the table of displayed maintenance events. To add a filter, enter the properties for a filter type
26 and click the add button to add the filter to the list. Once you're done adding filters, click the Filter button to display the results.</p>
30 <form class="form-horizontal" id="seedlot_maintenance_event_form" name="seedlot_maintenance_event_form">
32 % if ( $multi_seedlot ) {
34 <!-- Seedlot (by user-input) -->
35 <div class="form-group">
36 <label class="col-sm-3 control-label">Seedlot(s): </label>
37 <div class="col-sm-8">
38 <div class="panel panel-info">
39 <div class="panel-heading">Seedlot Name Contains</div>
40 <div class="panel-body">
41 <input id="event_filter_seedlot_name_contains" class="form-control" placeholder="part of Seedlot name" />
45 <div style="text-align: center"><strong>OR</strong></div>
47 <div class="col-sm-1">
48 <button id="event_filter_seedlot_name_contains_add" class='btn btn-info btn-sm'>Add</button>
52 <!-- Seedlot (by user-input) -->
53 <div class="form-group">
54 <label class="col-sm-3 control-label"></label>
55 <div class="col-sm-8">
56 <div class="panel panel-info">
57 <div class="panel-heading">Enter the name(s) of the Seedlot(s) - one per line</div>
58 <div class="panel-body">
59 <textarea id="event_filter_seedlot_names" class="form-control" rows=5></textarea>
63 <div style="text-align: center"><strong>OR</strong></div>
65 <div class="col-sm-1">
66 <button id="event_filter_seedlot_names_add" class='btn btn-info btn-sm'>Add</button>
70 <!-- Seedlot (by list) -->
71 <div class="form-group">
72 <label class="col-sm-3 control-label"></label>
73 <div class="col-sm-8">
74 <div class="panel panel-info">
75 <div class="panel-heading">Select a List of Seedlots</div>
76 <div class="panel-body"><div id="event_filter_seedlot_list"></div></div>
79 <div class="col-sm-1">
80 <button id="event_filter_seedlot_list_add" class='btn btn-info btn-sm'>Add</button>
84 <!-- Single Seedlot Name -->
85 <div class="form-group">
86 <label class="col-sm-3 control-label">Seedlot: </label>
87 <div class="col-sm-8">
88 <input class="form-control" type="text" value="<% $seedlot_name %>" disabled>
90 <div class="col-sm-1"></div>
95 <div class="form-group">
96 <label class="col-sm-3 control-label">Date: </label>
97 <div class="col-sm-4">
98 <input class="form-control" id="event_filter_date" type="date">
100 <div class="col-sm-4">
101 <select class="form-control" id="event_filter_date_type">
102 <option value="=">on</option>
103 <option value="<=">on or before</option>
104 <option value="<">before</option>
105 <option value=">=">on or after</option>
106 <option value=">">after</option>
109 <div class="col-sm-1">
110 <button id="event_filter_date_add" class='btn btn-info btn-sm'>Add</button>
115 <div class="form-group">
116 <label class="col-sm-3 control-label">Type: </label>
117 <div class="col-sm-4">
118 <select class="form-control" id="event_filter_type" disabled><option>Loading...</option></select>
120 <div class="col-sm-4" id="event_filter_type_value">
123 <div class="col-sm-1">
124 <button id="event_filter_type_add" class='btn btn-info btn-sm'>Add</button>
129 <div class="form-group">
130 <label class="col-sm-3 control-label">Operator: </label>
131 <div class="col-sm-8">
132 <input class="form-control" id="event_filter_operator" />
134 <div class="col-sm-1">
135 <button id="event_filter_operator_add" class='btn btn-info btn-sm'>Add</button>
143 <!-- Filter Table -->
144 <p><strong>Applied Filters:</strong></p>
146 <table class="table table-striped">
155 <tbody id="event_filter_table"></tbody>
160 <!-- Submit Filters -->
161 <button id="event_filter_submit" class="btn btn-primary btn-block" style="max-width: 400px; margin: 0 auto">Filter</button>
169 <!-- Events Table -->
170 <table id="seedlot_maintenance_events" class="table table-hover table-bordered"></table>
172 <div id="seedlot_maintenance_events_status">
173 <span style="float: left">
174 <span id="seedlot_maintenance_events_status_displayed"></span><br />
175 <span id="seedlot_maintenance_events_status_page"></span>
177 <div class="btn-group" style="float: right">
178 <button id="seedlot_maintenance_events_prev" type="button" class="btn btn-default" disabled><span class="glyphicon glyphicon-chevron-left"></span> Prev</button>
179 <button id="seedlot_maintenance_events_next" type="button" class="btn btn-default" disabled>Next <span class="glyphicon glyphicon-chevron-right"></span></button>
183 <!-- Remove Modal -->
184 <div id="seedlot_maintenance_remove_modal" class="modal fade" tabindex="-1" role="dialog">
185 <div class="modal-dialog" role="document">
186 <div class="modal-content">
187 <div class="modal-header">
188 <p class="modal-title">Remove Event?</p>
190 <div id="seedlot_modal_body" class="modal-body">
191 <p class="modal-body">Are you sure you want to remove this Seedlot Maintenance Event?</p>
193 <div class="modal-footer">
194 <button id="seedlot_maintenance_remove_modal_cancel" type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
195 <button id="seedlot_maintenance_remove_modal_ok" type="button" class="btn btn-danger">Remove Event</button>
202 <script type="text/javascript">
204 let MULTI_SEEDLOT = <% $multi_seedlot %> === 1;
205 let LO = new CXGN.List();
208 let EVENTS = []; // List of maintenance events for the Seedlot
209 let PAGE_SIZE = MULTI_SEEDLOT ? 50 : 10; // Max number of results per page
210 let PAGE = 1; // The current page of results
211 let MAX_PAGE; // The max page of results
212 let TOTAL; // The total number of results
213 let ONTOLOGY = []; // Event type ontology
214 let FILTERS = []; // List of filter properties
216 jQuery(document).ready(function() {
221 getOntology(function() {
225 // Add list selection, if enabled
226 if ( MULTI_SEEDLOT ) {
227 jQuery('#event_filter_seedlot_list').html(LO.listSelect('event_filter_seedlot', [ 'seedlots' ], 'select', undefined, undefined));
230 // Click and Change Listeners
231 jQuery("#event_filter_seedlot_name_contains_add").click(addSeedlotNameContainsFilter);
232 jQuery("#event_filter_seedlot_names_add").click(addSeedlotNamesFilter);
233 jQuery("#event_filter_seedlot_list_add").click(addSeedlotListFilter);
234 jQuery("#event_filter_date_add").click(addDateFilter);
235 jQuery("#event_filter_type_add").click(addTypeFilter);
236 jQuery("#event_filter_operator_add").click(addOperatorFilter);
237 jQuery("#event_filter_type").change(setTypeFilterValues);
238 jQuery("#event_filter_submit").click(submitFilter);
239 jQuery("#seedlot_maintenance_events_prev").click(getPrevPage);
240 jQuery("#seedlot_maintenance_events_next").click(getNextPage);
245 * Setup and Initialize the DataTable for the Events
247 function setupDataTable() {
249 // Set DataTables Buttons
252 extend: 'excelHtml5',
253 title: 'seedlot_events',
260 title: 'seedlot_events',
269 if ( MULTI_SEEDLOT ) {
270 columns = columns.concat([
274 render: function(data, type, row) {
275 if ( type === 'display' ) {
276 let url = "/breeders/seedlot/" + row.stock_id;
277 return "<a href='" + url + "'>" + data + "</a>";
284 columns = columns.concat([
285 { title: "Event ID", data: "stockprop_id" },
289 render: function(data, type, row) {
290 if ( type === "display" ) {
291 let dow = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
292 let month = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
293 let d = new Date(data);
294 let h = d.getHours();
295 let m = d.getMinutes();
296 let s = d.getSeconds();
297 if ( h <= 9 ) h = '0' + h;
298 if ( m <= 9 ) m = '0' + m;
299 if ( s <= 9 ) s = '0' + s;
300 let ts = dow[d.getDay()] + " " + month[d.getMonth()] + " " + d.getDate() + " " + h + ":" + m + ":" + s + " " + d.getFullYear();
306 { title: "Event Type", data: "cvterm_name" },
307 { title: "Value", data: "value" },
311 render: function(data, type, row) {
312 return data ? data : '';
315 { title: "Operator", data: "operator" },
318 data: "stockprop_id",
319 render: function(data, type, row) {
320 if ( type === "display" ) {
322 if ( '<% $user_role %>' === 'curator' || ( '<% $user_role %>' === 'submitter' && '<% $user_name %>' === row.operator ) ) {
323 html += "<a class='event_table_remove' onclick='removeEvent(" + row.stock_id + ", " + row.stockprop_id + ")' style='cursor: pointer;'>[Remove]</a>";
335 DT = jQuery('#seedlot_maintenance_events').DataTable({
338 pageLength: PAGE_SIZE,
341 order: [[ MULTI_SEEDLOT ? 2 : 1, "desc" ]],
349 * Get the Maintenance Events
350 * - Query the database
351 * - call displayEvents() to populate the table
353 function getEvents() {
357 jQuery("#event_filter_submit").html("Searching...");
358 jQuery("#event_filter_submit").attr("disabled", true);
359 jQuery("#seedlot_maintenance_events_prev").attr('disabled', true);
360 jQuery("#seedlot_maintenance_events_next").attr('disabled', true);
361 // DT.clear().draw();
362 // jQuery("#seedlot_maintenance_events .dataTables_empty").html("Searching...");
364 // Build filter arguments
369 for ( let i = 0; i < FILTERS.length; i++ ) {
371 if ( f.type_id === 'name' ) {
372 if ( f.comp === 'includes' ) {
378 else if ( f.comp === 'contains' ) {
380 value: "%" + f.value + "%",
385 else if ( f.type_id === 'date' ) {
386 if ( f.comp === '=' ) {
392 else if ( f.comp === '<=' ) {
394 date: f.value + " 24:00:00",
398 else if ( f.comp === '<' ) {
400 date: f.value + " 00:00:00",
404 else if ( f.comp === '>=' ) {
406 date: f.value + " 00:00:00",
410 else if ( f.comp === '>' ) {
412 date: f.value + " 24:00:00",
417 else if ( f.type_id === 'operator' ) {
418 operators.push(f.value);
422 cvterm_id: f.type_id,
427 if ( !MULTI_SEEDLOT ) {
429 value: "<% $seedlot_name %>",
447 url: '/ajax/breeders/seedlot/maintenance/search',
448 data: JSON.stringify(body),
449 contentType: "application/json; charset=utf-8",
451 success: function(data) {
452 if ( data && data.results ) {
453 EVENTS = data.results;
455 MAX_PAGE = data.maxPage;
459 alert("ERROR: Could not load maintenance events!");
463 alert("ERROR: Could not load maintenance events!");
465 complete: function() {
468 jQuery("#event_filter_submit").html("Filter");
469 jQuery("#event_filter_submit").attr("disabled", false);
475 * Redraw the display of the Maintenance Events in the table
477 function displayEvents() {
484 // Update Status Info
485 let end = PAGE*PAGE_SIZE;
486 let start = (end-PAGE_SIZE)+1;
487 end = end > TOTAL ? TOTAL : end;
488 let displayed = start + " - " + end + " / " + TOTAL;
489 let page_info = PAGE + " / " + MAX_PAGE;
491 jQuery("#seedlot_maintenance_events_status_displayed").html("<strong>EVENTS:</strong> " + displayed);
492 jQuery("#seedlot_maintenance_events_status_page").html("<strong>PAGE:</strong> " + page_info);
493 jQuery("#seedlot_maintenance_events_prev").attr('disabled', PAGE === 1);
494 jQuery("#seedlot_maintenance_events_next").attr('disabled', PAGE === MAX_PAGE || MAX_PAGE === 1);
499 * Start a new filtered search
501 function submitFilter() {
507 * Get the previous page of events
509 function getPrevPage() {
517 * Get the next page of events
519 function getNextPage() {
531 * Get the maintenance event ontology
532 * @param {Function} [callback] Callback function()
534 function getOntology(callback) {
538 url: '/ajax/breeders/seedlot/maintenance/ontology',
539 success: function(data) {
540 if ( data && data.ontology ) {
541 ONTOLOGY = data.ontology;
542 if ( callback ) return callback();
545 alert("ERROR: Could not fetch ontology terms");
549 alert("ERROR: Could not fetch ontology terms");
555 * Parse the event ontology for event types
556 * - Populate event type select
557 * - Call setTypeFilterValues to set initial values
559 function setTypeFilter() {
561 // Populate type select
562 let html = "<option value=''>...Select Maintenance Event Type...</option>";
564 for ( let i = 0; i < ONTOLOGY.length; i++ ) {
565 html += "<optgroup label='" + ONTOLOGY[i].name + "'>";
566 if ( ONTOLOGY[i].children ) {
567 for ( let j = 0; j < ONTOLOGY[i].children.length; j++ ) {
568 html += "<option value='" + ONTOLOGY[i].children[j].cvterm_id + "'>" + ONTOLOGY[i].children[j].name + "</option>";
571 html += "</optgroup>";
574 jQuery("#event_filter_type").html(html);
575 jQuery("#event_filter_type").attr("disabled", false);
578 setTypeFilterValues();
583 * Populate the event type input values based on the currently selected event type with:
584 * - a multi-select with the values for the currently selected event type, if set in the ontology
585 * - a text input, if not set in the ontology
587 function setTypeFilterValues() {
588 let id = jQuery("#event_filter_type").val();
590 // Build input based on possible values of selected event type
593 if ( id && id !== '' && ONTOLOGY ) {
595 for ( let i = 0; i < ONTOLOGY.length; i++ ) {
596 for ( let j = 0; j < ONTOLOGY[i].children.length; j++ ) {
597 if ( ONTOLOGY[i].children[j].cvterm_id === parseInt(id) ) {
598 values = ONTOLOGY[i].children[j].children;
602 if ( values && values.length > 0 ) {
603 input_type = "select";
604 html += "<select class='form-control' id='event_filter_type_value_select' multiple>";
605 for ( let i = 0; i < values.length; i++ ) {
606 html += "<option value='" + values[i].name + "'>" + values[i].name + "</option>";
612 html += "<input class='form-control' id='event_filter_type_value_user' type='text'>";
619 jQuery("#event_filter_type_value").data("input-type", input_type);
620 jQuery("#event_filter_type_value").html(html);
625 * Add a Name Filter - Seedlot name contains user-provided substring
627 function addSeedlotNameContainsFilter() {
628 let substring = jQuery("#event_filter_seedlot_name_contains").val();
629 addFilter('name', 'name', substring, 'contains');
634 * Add a Name Filter - from the user-entered list of names
636 function addSeedlotNamesFilter() {
637 let names = jQuery("#event_filter_seedlot_names").val();
638 names = names ? names.split('\n') : [];
639 addFilter('name', 'name', names && names.length > 0 ? names : undefined, 'includes');
644 * Add a Name Filter - from the user-selected list of Seedlots
646 function addSeedlotListFilter() {
647 let list_id = jQuery("#event_filter_seedlot_list_select").val();
648 let names = list_id ? LO.getList(list_id) : [];
649 addFilter('name', 'name', names && names.length > 0 ? names : undefined, 'includes');
657 function addDateFilter() {
658 let d = jQuery('#event_filter_date').val();
659 let t = jQuery('#event_filter_date_type').val();
662 let regex = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/;
663 if ( !d.match(regex) ) {
664 alert("Incorrect date format - the date must be in the format of YYYY-MM-DD");
669 addFilter('date', 'date', d, t);
674 * Add a Type/Value Filter
676 function addTypeFilter() {
679 let type_id = jQuery('#event_filter_type').val();
680 let type_name = jQuery('#event_filter_type option:selected').text();
682 // Get event value(s)
684 let input_type = jQuery('#event_filter_type_value').data('input-type');
685 if ( input_type === 'select' ) {
686 values = jQuery('#event_filter_type_value_select').val();
688 else if ( input_type === 'user' ) {
689 let v = jQuery('#event_filter_type_value_user').val();
690 values = v && v !== '' ? [v] : undefined;
693 addFilter(type_id, type_name, values, 'includes');
698 * Add an Operator Filter
700 function addOperatorFilter() {
701 let operators = jQuery('#event_filter_operator').val();
702 addFilter('operator', 'operator', operators, '=');
707 * Add a Filter to the list of added filters
708 * - call displayFilters() to update table
709 * @param {string|int} type_id ID of the filter type (cvterm_id, date, operator)
710 * @param {string} type_name Name of the filter type (cvterm name, date, operator)
711 * @param {string|int} value Value of the filter for comparison (type value, date, operator names)
712 * @param {string} comp Comparison used for the filter
714 function addFilter(type_id, type_name, value, comp) {
717 type_name: type_name,
725 * Remove the specified filter from the list of added filters
726 * - call displayFilters() to update table
727 * @param {int} i Index of filter to remove
729 function removeFilter(i) {
730 FILTERS.splice(i, 1);
735 * Update the table of added filters
737 function displayFilters() {
739 for ( let i = 0; i < FILTERS.length; i++ ) {
740 let v = FILTERS[i].value;
741 if ( !v ) v = "<em>Any Value</em>";
742 if ( Array.isArray(v) && v.length > 20 ) v = v.slice(0, 20).join(',') + "...";
744 html += "<td>" + FILTERS[i].type_name + "</td>";
745 html += "<td>" + FILTERS[i].comp + "</td>";
746 html += "<td>" + v + "</td>";
747 html += "<td><button class='btn btn-danger btn-xs' onclick='removeFilter(" + i + ")'><span class='glyphicon glyphicon-remove'></span></button></td>";
750 jQuery("#event_filter_table").html(html);
759 * Remove the specified Seedlot Event
760 * - Display the confirmation dialog
761 * - Remove the event from the database
762 * - Reload the displayed events
763 * @param {int} stock_id Stock ID of the Seedlot
764 * @param {int} stockprop_id Stockprop ID of the Event
766 function removeEvent(stock_id, stockprop_id) {
767 jQuery('#seedlot_maintenance_remove_modal').modal({backdrop: 'static', keyboard: false});
768 jQuery("#seedlot_maintenance_remove_modal_ok").off().click(function() {
769 jQuery("#seedlot_maintenance_remove_modal_cancel").attr("disabled", true);
770 jQuery("#seedlot_maintenance_remove_modal_ok").attr("disabled", true);
771 jQuery("#seedlot_maintenance_remove_modal_ok").html("Removing...");
776 url: '/ajax/breeders/seedlot/' + stock_id + '/maintenance/' + stockprop_id,
778 success: function(data) {
779 if ( !data || !data.success || data.success === 0 ) {
780 console.log(data.error);
781 alert("ERROR: Could not remove seedlot event!");
785 alert("ERROR: Could not remove seedlot event!");
787 complete: function() {
788 jQuery('#seedlot_maintenance_remove_modal').modal('hide')
789 jQuery("#seedlot_maintenance_remove_modal_cancel").attr("disabled", false);
790 jQuery("#seedlot_maintenance_remove_modal_ok").attr("disabled", false);
791 jQuery("#seedlot_maintenance_remove_modal_ok").html("Remove Event");
805 #seedlot_maintenance_remove_modal .modal-title {
809 #seedlot_maintenance_remove_modal .modal-body {