RT# 77021 - fixed advanced report criteria so agent and other fileds with a . in...
[freeside.git] / rt / share / html / Search / Schedule.html
1 <& /Elements/Header, Title => 'Schedule', JavaScript => 0 &>
2
3 <SCRIPT TYPE="text/javascript">
4
5   // sets cell content and bgcolor in a div, for use as a draggable
6   //   (draggable tds have border problems on FF/IE)
7   function set_cell_div ($cell,content,bgcolor) {
8     var $div = $cell.data('div');
9     if (!$div) {
10       $div = $(document.createElement('div'));
11       $div.data('cell',$cell);
12       $cell.data('div',$div);
13       $cell.append($div);
14     }
15     $div.css('white-space','nowrap');
16     $div.css('width','100%');
17     $div.css('background-color', bgcolor);
18     $div.html(content || '&nbsp;<br>&nbsp;<br>&nbsp;');
19   }
20
21   // gives cell the appearance dictated by its data
22   function set_data_cell ($cell) {
23     $cell.css('border',  '1px solid #D7D7D7' );
24     $cell.css('background-color', $cell.data('bgcolor'));
25     set_cell_div($cell,$cell.data('content'),$cell.data('bgcolor'));
26   }
27
28   // sets cell data and appearance to schedulable
29   function set_schedulable_cell ($cell) {
30     $cell.data('bgcolor',  '#FFFFFF' );
31     $cell.data('ticketid', 0 );
32     $cell.data('length',   0 );
33     $cell.data('cells',    0 );
34     $cell.data('offset',   0 );
35     $cell.data('label',    '' );
36     $cell.data('content',  '' );
37     set_data_cell($cell);
38   }
39
40   // sets cell data and appearance as an appointment
41   function set_appointment_cell ($cell,ticketid,bgcolor,labeltime,labeltitle,length,cells,offset) {
42     $cell.data('bgcolor',  bgcolor );
43     $cell.data('ticketid', ticketid );
44     $cell.data('length',   length );
45     $cell.data('cells',    cells );
46     $cell.data('offset',   offset );
47     var label = labeltime + ' <br>' + labeltitle + ' <br>';
48     $cell.data('label',  label );
49     $cell.data('content', '');
50     if ( offset == 0 ) { // first row
51       var title = 
52         label +
53         ' <A HREF="<%$RT::WebPath%>/Ticket/Display.html?id=' + ticketid + '" target="_blank">view</A> ' +
54         <% include('/elements/popup_link.html',
55              action=>$RT::WebPath.'/Ticket/ModifyCustomFieldsPopup.html?id=__MAGIC_TICKET_ID__',
56              label =>'edit',
57              actionlabel => 'Edit appointment',
58              height      => 436, # better: A + B * (num_custom_fields)
59           ) |n,js_string
60         %>;
61       title = title.replace( /__MAGIC_TICKET_ID__/, ticketid );
62       $cell.data('content', title);
63     }
64     set_data_cell($cell);
65   }
66
67 % if ( $cells ) {
68
69   // hover effects for scheduling new appointment
70
71   function boxon(what) {
72     var $this = $(what);
73     for ( var c=0; c < <%$cells%>; c++) {
74
75       $this.css('background-color', '#ffffdd');
76       set_cell_div($this,'','#ffffdd');
77       if ( c == 0 ) {
78         $this.css('border-top', '1px double black');
79       }
80       if ( c == <%$cells-1%> ) {
81         $this.css('border-bottom', '1px solid black');
82       }
83       $this.css('border-left', '1px double black');
84       $this.css('border-right', '1px solid black');
85
86       var rownum = $this.parent().prevAll('tr').length;
87       var colnum = $this.prevAll('td').length;
88       $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
89     }
90   }
91
92   function boxoff(what) {
93     var $this = $(what);
94     for ( var c=0; c < <%$cells%>; c++) {
95       $this.css('background-color', '#ffffff');
96       set_cell_div($this,'','#ffffff');
97       $this.css('border', '1px solid #D7D7D7'); //watch out in IE8 woes, empty string removes cell borders
98       var rownum = $this.parent().prevAll('tr').length;
99       var colnum = $this.prevAll('td').length;
100       $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
101     }
102   }
103
104
105 % } else {
106
107   // functions for drag-and-drop rescheduling
108
109   // ticket-dependant test if we can drop here
110   // prevent overlap with other appointments, while allowing appointment to overlap itself
111   function can_drop ($where, ui) {
112     var cells = ui.draggable.data('cell').data('cells');
113     var ticketid = ui.draggable.data('cell').data('ticketid');
114     for (var c=0; c < cells; c++) {
115       if (!$where.is('.ui-droppable')) {
116         return false;
117       }
118       if ($where.data('ticketid')) {
119         if ($where.data('ticketid') != ticketid) {
120           return false;
121         }
122         if ($where.data('offset') == c) { // don't reschedule in the same slot
123           return false;
124         }
125       }
126       var rownum = $where.parent().prevAll('tr').length;
127       var colnum = $where.prevAll('td').length;
128       $where = $where.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
129     }
130     return true;
131   }
132
133   // makes cell droppable (can reschedule here, subject to can_drop)
134   function set_droppable_cell ($cell) {
135     $cell.droppable({
136       over: appointment_drag_over,
137       drop: reschedule_appointment,
138       tolerance: 'pointer'
139     });
140   }
141
142   // makes cell draggable (able to be rescheduled)
143   function set_draggable_cell ($cell) {
144     var $div = $cell.data('div');
145     $div.draggable({
146       containment: '.titlebox-content',
147       revert: true,
148       revertDuration: 0,
149       start: appointment_drag_start,
150       stop: appointment_drag_stop,
151       zIndex: 10,
152     });
153   }
154
155   // gives cell a white (schedulable) appearance, without changing cell data
156   function set_white_cell ($cell) {
157     $cell.css('border',  '1px solid #D7D7D7' );
158     $cell.css('background-color', '#FFFFFF');
159     set_cell_div($cell,'','#FFFFFF');
160   }
161
162   // track drag highlighting
163   var drag_hi;
164
165   // clear drag highlighting
166   function clear_drag_hi (cells) {
167     if ( drag_hi ) {
168       for ( var c=0; c < cells; c++) {
169         if (drag_hi.data('isdragging')) {
170           drag_hi.css('border',  '1px solid #D7D7D7' );
171           drag_hi.css('background-color',  '#FFFFFF' );
172         } else {
173           set_white_cell(drag_hi);
174         }
175         var rownum = drag_hi.parent().prevAll('tr').length;
176         var colnum = drag_hi.prevAll('td').length;
177         drag_hi = drag_hi.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
178       }
179       drag_hi = undefined;
180     }
181   }
182
183   // drag start event
184   function appointment_drag_start(event, ui) {
185     var $this = $(this);
186     // cell that's dragging
187     $this = $this.data('cell');
188     set_cell_div($this,$this.data('label'),$this.data('bgcolor'));
189     $this.data('isdragging',true);
190     var offset = $this.data('offset');
191     var cells  = $this.data('cells');
192     // jump to first cell in appointment
193     var rownum = $this.parent().prevAll('tr').length;
194     var colnum = $this.prevAll('td').length;
195     $this = $this.parent().parent().children('tr').eq(rownum-offset).children('td').eq(colnum);
196     // loop through all cells in appointment
197     for ( var c=0; c < cells; c++) {
198       if ($this.data('isdragging')) {
199         $this.css('background-color', '#FFFFFF');
200       } else {
201         set_white_cell($this);
202       }
203       var rownum = $this.parent().prevAll('tr').length;
204       var colnum = $this.prevAll('td').length;
205       $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
206     }
207   }
208
209   // drag stop event
210   function appointment_drag_stop(event, ui) {
211     var $this = $(this);
212     // cell that's dragging
213     $this = $this.data('cell');
214     var cells = $this.data('cells');
215     clear_drag_hi(cells);
216     $this.data('isdragging',false);
217     var offset = $this.data('offset');
218     // jump to first cell in appointment
219     var rownum = $this.parent().prevAll('tr').length;
220     var colnum = $this.prevAll('td').length;
221     $this = $this.parent().parent().children('tr').eq(rownum-offset).children('td').eq(colnum);
222     // loop through all cells in appointment
223     for ( var c=0; c < cells; c++) {
224       set_data_cell($this);
225       var rownum = $this.parent().prevAll('tr').length;
226       var colnum = $this.prevAll('td').length;
227       $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
228     }
229   }
230
231   // drag over event
232   function appointment_drag_over(event, ui) {
233     // the cell that's dragging
234     var cells = ui.draggable.data('cell').data('cells');
235     // the droppable cell that you're over
236     var $this = $(this);
237     clear_drag_hi(cells);
238     if (!can_drop($this, ui)) return;
239     drag_hi = $this;
240     // loop through potential appointment cells
241     for ( var c=0; c < cells; c++) {
242       $this.css('background-color', '#ffffdd');
243       if ( !$this.data('isdragging')) {
244         set_cell_div($this,'','#ffffdd');
245       } else {
246         $this.css('background-color','#ffffdd');
247       }
248       if ( c == 0 ) {
249         $this.css('border-top', '1px double black');
250       }
251       if ( c == (cells-1) ) {
252         $this.css('border-bottom', '1px solid black');
253       }
254       $this.css('border-left', '1px double black');
255       $this.css('border-right', '1px solid black');
256       var rownum = $this.parent().prevAll('tr').length;
257       var colnum = $this.prevAll('td').length;
258       $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
259     }
260   }
261
262   // drop event
263   function reschedule_appointment( event, ui ) {
264
265         // the droppable cell that you're over
266     var $this = $(this);
267
268     if (!can_drop($this, ui)) return;
269
270 %   #get the ticket number and appointment length (from the draggable object)
271     var dragcell = ui.draggable.data('cell');
272     var ticketid = dragcell.data('ticketid');
273     var length   = dragcell.data('length');
274     var bgcolor  = dragcell.data('bgcolor');
275     var offset   = dragcell.data('offset');
276
277 %   #and.. the new date and time, and username (from the droppable object)
278     var starts   = $this.data('starts');
279     var username = $this.data('username');
280     var due = parseInt(starts) + parseInt(length);
281     var n_epoch        = $this.data('epoch');
282     var n_st_tod_row   = $this.data('tod_row');
283
284     var droppable = $this;
285     ui.draggable.effect( "transfer", { to: droppable }, 420 );
286
287 %   #tell the backend to reschedule it
288     var url = "<% popurl(3) %>misc/xmlhttp-ticket-update.html?" +
289               "id=" + ticketid + ";starts=" + starts + ";due=" + due +
290               ";username=" + username;
291
292     $.getJSON( url, function( data ) {
293       if ( data.error && data.error.length ) {
294 %       #error?  "that shouldn't happen" but should display 
295         alert(data.error);
296
297       } else {
298
299         var label = data.sched_label;
300         var labeltime = data.sched_label_time;
301         var labeltitle = data.sched_label_title;
302
303         // jump to first cell in appointment
304         var rownum = dragcell.parent().prevAll('tr').length;
305         var colnum = dragcell.prevAll('td').length;
306         dragcell = dragcell.parent().parent().children('tr').eq(rownum-offset).children('td').eq(colnum);
307
308         // remove old appointment entirely
309         var epoch        = dragcell.data('epoch');
310         var st_tod_row   = dragcell.data('tod_row');
311         var old_username = dragcell.data('username');
312         var cells        = dragcell.data('cells');
313         for ( var c=0; c < cells; c++) {
314           var tod_row = parseInt(st_tod_row) + (c * <%$timestep%>);
315           var td_id = 'td_' + epoch +
316                       '_' + String( tod_row ) +
317                       '_' + old_username;
318           var $cell = $('#'+td_id);
319           $cell.data('div').draggable('destroy');
320           set_schedulable_cell($cell);
321           set_droppable_cell($cell);
322         }
323
324         // set appointment in new position
325         clear_drag_hi(cells);
326         for ( var d=0; d < cells; d++) {
327           var n_tod_row = parseInt(n_st_tod_row) + (d * <%$timestep%>);
328           var n_td_id = 'td_' + n_epoch +
329                         '_' + String( n_tod_row ) +
330                         '_' + username;
331           var $cell = $('#'+n_td_id);
332           set_appointment_cell($cell,ticketid,bgcolor,labeltime,labeltitle,length,cells,d);
333           set_draggable_cell($cell);
334           set_droppable_cell($cell);
335         }
336       }
337     });
338   }
339
340 % } # end of rescheduling functions
341
342 </SCRIPT>
343
344 <& /Search/Calendar.html,
345      @_,
346      Query       => "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
347                      AND ( Type = 'reminder' OR 'Type' = 'ticket' )
348                      AND Queue = $queueid ",
349      slots       => scalar(@usernames),
350      Embed       => 'Schedule.html',
351      DimPast     => 1,
352      Display     => 'Schedule',
353      DisplayArgs => [ username  => \@usernames,
354                       LengthMin => $LengthMin,
355                       #oops, more freeside abstraction-leaking
356                       custnum   => $ARGS{custnum},
357                       pkgnum    => $ARGS{pkgnum},
358                       RedirectToBasics => $ARGS{RedirectToBasics},
359                     ],
360 &>
361
362 <%ONCE>
363
364 my $timestep =  RT->Config->Get('CalendarWeeklySizeMin') || 30; #1/2h
365
366 </%ONCE>
367 <%init>
368
369 #abstraction-leaking
370 my $conf = new FS::Conf;
371 my $queueid = $conf->config('ticket_system-appointment-queueid')
372   or die "ticket_system-appointment-queueid configuration not set";
373
374 my @files = ();
375 #if ( ! $initialized ) {
376   push @files, map "overlibmws$_", ( '', qw( _iframe _draggable _crossframe ) );
377   push @files, map { "${_}contentmws" } qw( iframe ajax );
378 #%}
379
380 my @usernames = ();
381 if ( ref($ARGS{username}) ) {
382   @usernames = @{ $ARGS{username} };
383 } elsif ( $ARGS{username} ) {
384   @usernames = ( $ARGS{username} );
385 } else {
386   #look them up ourslves... again, more FS abstraction-leaking, but 
387   # we want to link to the schedule view, and better than doing this every
388   # menu render
389   use FS::Record qw( qsearch );
390   use FS::sched_item;
391   my @sched_item = qsearch('sched_item', { 'disabled' => '', });
392   @usernames = map $_->access_user->username, @sched_item;
393 }
394
395 ( my $LengthMin = $ARGS{LengthMin} ) =~ /^\d+$/ or die 'non-numeric LengthMin';
396
397 my $cells = int($LengthMin / $timestep);
398 $cells++ if $LengthMin % $timestep;
399
400 </%init>