X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=rt%2Fshare%2Fhtml%2FSearch%2FSchedule.html;h=cfa4558be8d51c9f2c3d606662a0abc0698d0311;hb=b3ecf9c3a19f00676ff9091602be6d793748b756;hp=34ba142bdf0e24345c56cb210dfe97d7027fda05;hpb=2cb16bcb5d43d9194ca1eaf95fe5e59ec9076b76;p=freeside.git diff --git a/rt/share/html/Search/Schedule.html b/rt/share/html/Search/Schedule.html index 34ba142bd..cfa4558be 100644 --- a/rt/share/html/Search/Schedule.html +++ b/rt/share/html/Search/Schedule.html @@ -1,13 +1,55 @@ -<& /Elements/Header, Title => 'Schedule' &> +<& /Elements/Header, Title => 'Schedule', JavaScript => 0 &> -%#init_overlib.html -%foreach my $file (@files) { -<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/<%$file%>.js"></SCRIPT> -%} +<SCRIPT TYPE="text/javascript"> -<SCRIPT TYPE="text/javascript" SRC="<%$fsurl%>elements/jquery.js"></SCRIPT> + // gives cell the appearance dictated by its data + function set_data_cell ($cell) { + $cell.css('border', '1px solid #D7D7D7' ); + $cell.css('background-color', $cell.data('bgcolor')); + $cell.html($cell.data('content')); + } -<SCRIPT TYPE="text/javascript"> + // sets cell data and appearance to schedulable + function set_schedulable_cell ($cell) { + $cell.data('bgcolor', '#FFFFFF' ); + $cell.data('ticketid', 0 ); + $cell.data('length', 0 ); + $cell.data('cells', 0 ); + $cell.data('offset', 0 ); + $cell.data('label', '' ); + $cell.data('content', '' ); + set_data_cell($cell); + } + + // sets cell data and appearance as an appointment + function set_appointment_cell ($cell,ticketid,bgcolor,label,length,cells,offset) { + $cell.data('bgcolor', bgcolor ); + $cell.data('ticketid', ticketid ); + $cell.data('length', length ); + $cell.data('cells', cells ); + $cell.data('offset', offset ); + $cell.data('label', label ); + $cell.data('content', ''); + if ( offset == 0 ) { // first row + var title = + label + + ' <A HREF="<%$RT::WebPath%>/Ticket/Display.html?id=' + ticketid + '" target="_blank">view</A> ' + + <% include('/elements/popup_link.html', + action=>$RT::WebPath.'/Ticket/ModifyCustomFieldsPopup.html?id=__MAGIC_TICKET_ID__', + label =>'edit', + actionlabel => 'Edit appointment', + height => 436, # better: A + B * (num_custom_fields) + ) |n,js_string + %>; + title = title.replace( /__MAGIC_TICKET_ID__/, ticketid ); + $cell.data('content', title); + } + set_data_cell($cell); + } + +% if ( $cells ) { + + // hover effects for scheduling new appointment function boxon(what) { var $this = $(what); @@ -16,16 +58,12 @@ $this.css('background-color', '#ffffdd'); if ( c == 0 ) { $this.css('border-top', '1px double black'); - $this.css('border-left', '1px double black'); - $this.css('border-right', '1px solid black'); - } else if ( c == <%$cells-1%> ) { - $this.css('border-left', '1px double black'); - $this.css('border-right', '1px solid black'); + } + if ( c == <%$cells-1%> ) { $this.css('border-bottom', '1px solid black'); - } else { - $this.css('border-left', '1px double black'); - $this.css('border-right', '1px solid black'); } + $this.css('border-left', '1px double black'); + $this.css('border-right', '1px solid black'); var rownum = $this.parent().prevAll('tr').length; var colnum = $this.prevAll('td').length; @@ -36,25 +74,241 @@ function boxoff(what) { var $this = $(what); for ( var c=0; c < <%$cells%>; c++) { + $this.css('background-color', '#ffffff'); + $this.css('border', '1px solid #D7D7D7'); //watch out in IE8 woes, empty string removes cell borders + var rownum = $this.parent().prevAll('tr').length; + var colnum = $this.prevAll('td').length; + $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum); + } + } + + +% } else { + + // functions for drag-and-drop rescheduling + + // ticket-dependant test if we can drop here + // prevent overlap with other appointments, while allowing appointment to overlap itself + function can_drop ($where, ui) { + var cells = ui.draggable.data('cells'); + var ticketid = ui.draggable.data('ticketid'); + for (var c=0; c < cells; c++) { + if (!$where.is('.ui-droppable')) { + return false; + } + if ($where.data('ticketid') && ($where.data('ticketid') != ticketid)) { + return false; + } + var rownum = $where.parent().prevAll('tr').length; + var colnum = $where.prevAll('td').length; + $where = $where.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum); + } + return true; + } + + // makes cell droppable (can reschedule here, subject to can_drop) + function set_droppable_cell ($cell) { + $cell.droppable({ + over: appointment_drag_over, + drop: reschedule_appointment, + tolerance: 'pointer' + }); + } + + // makes cell draggable (able to be rescheduled) + function set_draggable_cell ($cell) { + $cell.draggable({ + containment: '.titlebox-content', + revert: true, + revertDuration: 0, + start: appointment_drag_start, + stop: appointment_drag_stop, + }); + } + + // gives cell a white (schedulable) appearance, without changing cell data + function set_white_cell ($cell) { + $cell.css('border', '1px solid #D7D7D7' ); + $cell.css('background-color', '#FFFFFF'); + $cell.html(''); + } + + // track drag highlighting + var drag_hi; + + // clear drag highlighting + function clear_drag_hi (cells) { + if ( drag_hi ) { + for ( var c=0; c < cells; c++) { + if (drag_hi.data('isdragging')) { + drag_hi.css('border', '1px solid #D7D7D7' ); + } else { + set_white_cell(drag_hi); + } + var rownum = drag_hi.parent().prevAll('tr').length; + var colnum = drag_hi.prevAll('td').length; + drag_hi = drag_hi.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum); + } + drag_hi = undefined; + } + } - //$this.css('background-color', ''); - //$this.css('border', ''); //IE8 woes, removes cell borders - $this.removeAttr('style'); //slightly "flashy" on cell changes under IE8 - //but at least it doesn't remove cell borders + // drag start event + function appointment_drag_start(event, ui) { + var $this = $(this); + // cell that's actually dragging + $this.html($this.data('label')); + $this.css('z-index',10); + $this.data('isdragging',true); + var offset = $this.data('offset'); + var cells = $this.data('cells'); + // jump to first cell in appointment + var rownum = $this.parent().prevAll('tr').length; + var colnum = $this.prevAll('td').length; + $this = $this.parent().parent().children('tr').eq(rownum-offset).children('td').eq(colnum); + // loop through all cells in appointment + for ( var c=0; c < cells; c++) { + if (c != offset) set_white_cell($this); + var rownum = $this.parent().prevAll('tr').length; + var colnum = $this.prevAll('td').length; + $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum); + } + } + + // drag stop event + function appointment_drag_stop(event, ui) { + var $this = $(this); + // the cell that was dragging + var cells = $this.data('cells'); + clear_drag_hi(cells); + $this.css('z-index','initial'); + $this.data('isdragging',false); + var offset = $this.data('offset'); + // jump to first cell in appointment + var rownum = $this.parent().prevAll('tr').length; + var colnum = $this.prevAll('td').length; + $this = $this.parent().parent().children('tr').eq(rownum-offset).children('td').eq(colnum); + // loop through all cells in appointment + for ( var c=0; c < cells; c++) { + set_data_cell($this); + var rownum = $this.parent().prevAll('tr').length; + var colnum = $this.prevAll('td').length; + $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum); + } + } + // drag over event + function appointment_drag_over(event, ui) { + // the cell that is dragging + var cells = ui.draggable.data('cells'); + // the droppable cell that you're over + var $this = $(this); + clear_drag_hi(cells); + if (!can_drop($this, ui)) return; + drag_hi = $this; + // loop through potential appointment cells + for ( var c=0; c < cells; c++) { + if ( !$this.data('isdragging')) { + $this.css('background-color', '#ffffdd'); + } + if ( c == 0 ) { + $this.css('border-top', '1px double black'); + } + if ( c == (cells-1) ) { + $this.css('border-bottom', '1px solid black'); + } + $this.css('border-left', '1px double black'); + $this.css('border-right', '1px solid black'); var rownum = $this.parent().prevAll('tr').length; var colnum = $this.prevAll('td').length; $this = $this.parent().parent().children('tr').eq(rownum+1).children('td').eq(colnum); } } + // drop event + function reschedule_appointment( event, ui ) { + + var $this = $(this); + + if (!can_drop($this, ui)) return; + +% #get the ticket number and appointment length (from the draggable object) + var draggable = ui.draggable; + var ticketid = draggable.data('ticketid'); + var length = draggable.data('length'); + var bgcolor = draggable.data('bgcolor'); + var offset = draggable.data('offset'); + +% #and.. the new date and time, and username (from the droppable object) + var starts = $this.data('starts'); + var username = $this.data('username'); + var due = parseInt(starts) + parseInt(length); + var n_epoch = $this.data('epoch'); + var n_st_tod_row = $this.data('tod_row'); + + var droppable = $this; + draggable.effect( "transfer", { to: droppable }, 420 ); + +% #tell the backend to reschedule it + var url = "<% popurl(3) %>misc/xmlhttp-ticket-update.html?" + + "id=" + ticketid + ";starts=" + starts + ";due=" + due + + ";username=" + username; + + $.getJSON( url, function( data ) { + if ( data.error && data.error.length ) { +% #error? "that shouldn't happen" but should display + alert(data.error); + + } else { + + var label = data.sched_label; + + // jump to first cell in appointment + var rownum = draggable.parent().prevAll('tr').length; + var colnum = draggable.prevAll('td').length; + draggable = draggable.parent().parent().children('tr').eq(rownum-offset).children('td').eq(colnum); + + // remove old appointment entirely + var epoch = draggable.data('epoch'); + var st_tod_row = draggable.data('tod_row'); + var old_username = draggable.data('username'); + var cells = draggable.data('cells'); + for ( var c=0; c < cells; c++) { + var tod_row = parseInt(st_tod_row) + (c * <%$timestep%>); + var td_id = 'td_' + epoch + + '_' + String( tod_row ) + + '_' + old_username; + var $cell = $('#'+td_id); + set_schedulable_cell($cell); + $cell.draggable('destroy'); + set_droppable_cell($cell); + } + + // set appointment in new position + clear_drag_hi(cells); + for ( var d=0; d < cells; d++) { + var n_tod_row = parseInt(n_st_tod_row) + (d * <%$timestep%>); + var n_td_id = 'td_' + n_epoch + + '_' + String( n_tod_row ) + + '_' + username; + var $cell = $('#'+n_td_id); + set_appointment_cell($cell,ticketid,bgcolor,label,length,cells,d); + set_draggable_cell($cell); + set_droppable_cell($cell); + } + } + }); + } + +% } # end of rescheduling functions + </SCRIPT> <& /Search/Calendar.html, @_, Query => "( Status = 'new' OR Status = 'open' OR Status = 'stalled') - AND ( Type = 'reminder' OR 'Type' = 'ticket' )", - #XXX and we have the magic custom field + AND ( Type = 'reminder' OR 'Type' = 'ticket' ) + AND Queue = $queueid ", slots => scalar(@usernames), Embed => 'Schedule.html', DimPast => 1, @@ -64,6 +318,7 @@ #oops, more freeside abstraction-leaking custnum => $ARGS{custnum}, pkgnum => $ARGS{pkgnum}, + RedirectToBasics => $ARGS{RedirectToBasics}, ], &> @@ -74,6 +329,11 @@ my $timestep = RT->Config->Get('CalendarWeeklySizeMin') || 30; #1/2h </%ONCE> <%init> +#abstraction-leaking +my $conf = new FS::Conf; +my $queueid = $conf->config('ticket_system-appointment-queueid') + or die "ticket_system-appointment-queueid configuration not set"; + my @files = (); #if ( ! $initialized ) { push @files, map "overlibmws$_", ( '', qw( _iframe _draggable _crossframe ) ); @@ -86,9 +346,13 @@ if ( ref($ARGS{username}) ) { } elsif ( $ARGS{username} ) { @usernames = ( $ARGS{username} ); } else { - #XXX shouldn't even get offered the link in the first place rather than perl - # barf, but this is better than erroring out later or empty @username - die "Can't schedule an appointment - no employees are configured as installers"; + #look them up ourslves... again, more FS abstraction-leaking, but + # we want to link to the schedule view, and better than doing this every + # menu render + use FS::Record qw( qsearch ); + use FS::sched_item; + my @sched_item = qsearch('sched_item', { 'disabled' => '', }); + @usernames = map $_->access_user->username, @sched_item; } ( my $LengthMin = $ARGS{LengthMin} ) =~ /^\d+$/ or die 'non-numeric LengthMin';