1 <& /Elements/Header, Title => 'Schedule', JavaScript => 0 &>
3 <SCRIPT TYPE="text/javascript">
9 for ( var c=0; c < <%$cells%>; c++) {
11 $this.css('background-color', '#ffffdd');
13 $this.css('border-top', '1px double black');
14 $this.css('border-left', '1px double black');
15 $this.css('border-right', '1px solid black');
16 } else if ( c == <%$cells-1%> ) {
17 $this.css('border-left', '1px double black');
18 $this.css('border-right', '1px solid black');
19 $this.css('border-bottom', '1px solid black');
21 $this.css('border-left', '1px double black');
22 $this.css('border-right', '1px solid black');
25 var rownum = $this.parent().prevAll('tr').length;
26 var colnum = $this.prevAll('td').length;
27 $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
31 function boxoff(what) {
33 for ( var c=0; c < <%$cells%>; c++) {
35 //$this.css('background-color', '');
36 //$this.css('border', ''); //IE8 woes, removes cell borders
37 $this.removeAttr('style'); //slightly "flashy" on cell changes under IE8
38 //but at least it doesn't remove cell borders
40 var rownum = $this.parent().prevAll('tr').length;
41 var colnum = $this.prevAll('td').length;
42 $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
49 % # it would be better if we had draggable-specific droppables, but this will prevent overlap for now...
50 function can_drop ($where, cells) {
51 for (var c=0; c < cells; c++) {
52 if (!$where.is('.ui-droppable')) {
55 var rownum = $where.parent().prevAll('tr').length;
56 var colnum = $where.prevAll('td').length;
57 $where = $where.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
65 // on drag stop (regardless of if it was dropped)
66 function clear_drag_hi () {
74 function boxon_drop(event, ui) {
75 //var $this = $(what);
78 drag_cells = ui.draggable.data('cells');
82 if (!can_drop($this, drag_cells)) return;
86 for ( var c=0; c < drag_cells; c++) {
88 /* well, its not exactly what i want, would prefer if it could properly
89 mouse in-out, but this sorta helps for now?
90 revisit when everthing else is working */
91 /* $this.effect("highlight", {}, 1500); */
93 $this.css('background-color', '#ffffdd');
95 $this.css('border-top', '1px double black');
96 $this.css('border-left', '1px double black');
97 $this.css('border-right', '1px solid black');
98 } else if ( c == (drag_cells-1) ) {
99 $this.css('border-left', '1px double black');
100 $this.css('border-right', '1px solid black');
101 $this.css('border-bottom', '1px solid black');
103 $this.css('border-left', '1px double black');
104 $this.css('border-right', '1px solid black');
107 var rownum = $this.parent().prevAll('tr').length;
108 var colnum = $this.prevAll('td').length;
109 $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
115 // clears highlighted box, used by clear_hi_drag (drag stop event)
116 function boxoff_do(what) {
120 for ( var c=0; c < drag_cells; c++) {
122 //$this.css('background-color', '');
123 //$this.css('border', ''); //IE8 woes, removes cell borders
124 $this.removeAttr('style'); //slightly "flashy" on cell changes under IE8
125 //but at least it doesn't remove cell borders
127 var rownum = $this.parent().prevAll('tr').length;
128 var colnum = $this.prevAll('td').length;
129 $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum);
134 function reschedule_appointment( event, ui ) {
138 if (!can_drop($this, ui.draggable.data('cells'))) return;
140 % #get the ticket number and appointment length (from the draggable object)
141 var ticketid = ui.draggable.data('ticketid');
142 var length = ui.draggable.data('length');
143 var bgcolor = ui.draggable.data('bgcolor');
145 % #and.. the new date and time, and username (from the droppable object)
146 var starts = $this.data('starts');
147 var username = $this.data('username');
149 var due = parseInt(starts) + parseInt(length);
151 var n_epoch = $this.data('epoch');
152 var n_st_tod_row = $this.data('tod_row');
154 var draggable = ui.draggable;
155 var droppable = $this;
156 draggable.effect( "transfer", { to: droppable }, 420 );
158 % #tell the backend to reschedule it
159 var url = "<% popurl(3) %>misc/xmlhttp-ticket-update.html?" +
160 "id=" + ticketid + ";starts=" + starts + ";due=" + due +
161 ";username=" + username;
163 $.getJSON( url, function( data ) {
164 if ( data.error && data.error.length ) {
165 % #error? "that shouldn't happen" but should display
167 % #XX and should revert the dragable...
170 //draggable.effect( "transfer", { to: droppable }, 1000 );
172 var label = data.sched_label;
174 % #remove the old appointment entirely
175 var epoch = ui.draggable.data('epoch');
176 var st_tod_row = ui.draggable.data('tod_row');
177 var old_username = ui.draggable.data('username');
178 var cells = ui.draggable.data('cells');
179 for ( var c=0; c < cells; c++) {
180 var tod_row = parseInt(st_tod_row) + (c * <%$timestep%>);
181 var td_id = 'td_' + epoch +
182 '_' + String( tod_row ) +
184 $('#'+td_id).css('background-color', '#FFFFFF');
185 $('#'+td_id).text('');
186 % #(and make those boxes droppable)
187 $('#'+td_id).droppable({
189 drop: reschedule_appointment,
194 % #maybe use that animation which shows the box from point A to B
197 for ( var d=0; d < cells; d++) {
198 var n_tod_row = parseInt(n_st_tod_row) + (d * <%$timestep%>);
199 var n_td_id = 'td_' + n_epoch +
200 '_' + String( n_tod_row ) +
202 $('#'+n_td_id).css('background-color', bgcolor);
203 % #remove their droppable
204 $('#'+n_td_id).droppable('destroy');
208 ' <A HREF="<%$RT::WebPath%>/Ticket/Display.html?id=' + ticketid + '" target="_blank">view</A> ' +
209 <% include('/elements/popup_link.html',
210 action=>$RT::WebPath.'/Ticket/ModifyCustomFieldsPopup.html?id=__MAGIC_TICKET_ID__',
212 actionlabel => 'Edit appointment',
213 height => 436, # better: A + B * (num_custom_fields)
216 title = title.replace( /__MAGIC_TICKET_ID__/, ticketid );
217 $('#'+n_td_id).html( title );
218 % #(and make the top draggable, so we could do it all over again)
219 $('#'+n_td_id).draggable({
220 containment: '.titlebox-content',
221 %# revert: 'invalid',
226 $('#'+n_td_id).data('ticketid', ticketid );
227 $('#'+n_td_id).data('length', length );
228 $('#'+n_td_id).data('cells', cells );
229 $('#'+n_td_id).data('bgcolor', bgcolor );
241 <& /Search/Calendar.html,
243 Query => "( Status = 'new' OR Status = 'open' OR Status = 'stalled')
244 AND ( Type = 'reminder' OR 'Type' = 'ticket' )
245 AND Queue = $queueid ",
246 slots => scalar(@usernames),
247 Embed => 'Schedule.html',
249 Display => 'Schedule',
250 DisplayArgs => [ username => \@usernames,
251 LengthMin => $LengthMin,
252 #oops, more freeside abstraction-leaking
253 custnum => $ARGS{custnum},
254 pkgnum => $ARGS{pkgnum},
255 RedirectToBasics => $ARGS{RedirectToBasics},
261 my $timestep = RT->Config->Get('CalendarWeeklySizeMin') || 30; #1/2h
267 my $conf = new FS::Conf;
268 my $queueid = $conf->config('ticket_system-appointment-queueid')
269 or die "ticket_system-appointment-queueid configuration not set";
272 #if ( ! $initialized ) {
273 push @files, map "overlibmws$_", ( '', qw( _iframe _draggable _crossframe ) );
274 push @files, map { "${_}contentmws" } qw( iframe ajax );
278 if ( ref($ARGS{username}) ) {
279 @usernames = @{ $ARGS{username} };
280 } elsif ( $ARGS{username} ) {
281 @usernames = ( $ARGS{username} );
283 #look them up ourslves... again, more FS abstraction-leaking, but
284 # we want to link to the schedule view, and better than doing this every
286 use FS::Record qw( qsearch );
288 my @sched_item = qsearch('sched_item', { 'disabled' => '', });
289 @usernames = map $_->access_user->username, @sched_item;
292 ( my $LengthMin = $ARGS{LengthMin} ) =~ /^\d+$/ or die 'non-numeric LengthMin';
294 my $cells = int($LengthMin / $timestep);
295 $cells++ if $LengthMin % $timestep;