/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package net.rrm.ehour.ui.timesheet.panel;
import com.google.common.collect.Lists;
import net.rrm.ehour.data.DateRange;
import net.rrm.ehour.domain.ProjectAssignment;
import net.rrm.ehour.ui.EhourWebApplication;
import net.rrm.ehour.ui.common.form.FormHighlighter;
import net.rrm.ehour.ui.timesheet.dto.GrandTotal;
import net.rrm.ehour.ui.timesheet.dto.ProjectTotalModel;
import net.rrm.ehour.ui.timesheet.dto.TimesheetCell;
import net.rrm.ehour.ui.timesheet.dto.TimesheetRow;
import net.rrm.ehour.ui.timesheet.model.TimesheetContainer;
import net.rrm.ehour.ui.timesheet.panel.renderer.TimesheetIconRenderFactory;
import net.rrm.ehour.ui.timesheet.panel.renderer.TimesheetIconRenderFactoryCollection;
import net.rrm.ehour.util.DateUtil;
import org.apache.commons.lang.StringUtils;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.event.Broadcast;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import java.util.Calendar;
import java.util.List;
/**
* Representation of a timesheet row
*/
public class TimesheetRowList extends ListView<TimesheetRow> {
private static final long serialVersionUID = -6905022018110510887L;
private static final String DAY_ID = "day";
private static final String DAY_OPTIONS_ID = "options";
private final GrandTotal grandTotals;
private Form<?> form;
private final IModel<TimesheetContainer> timesheetContainerModel;
private MarkupContainer provider;
@SpringBean
private TimesheetIconRenderFactoryCollection iconRenderer;
public TimesheetRowList(String id, List<TimesheetRow> model, GrandTotal grandTotals, IModel<TimesheetContainer> timesheetContainerModel, Form<?> form, MarkupContainer provider) {
super(id, model);
this.timesheetContainerModel = timesheetContainerModel;
this.provider = provider;
setReuseItems(true);
this.grandTotals = grandTotals;
this.form = form;
}
@Override
protected void populateItem(ListItem<TimesheetRow> item) {
final TimesheetRow row = item.getModelObject();
ProjectAssignment assignment = row.getProjectAssignment();
item.add(createBookWholeWeekLink(row, "bookWholeWeek"));
item.add(new Label("project", assignment.getProject().getName()));
Label role = new Label("role", String.format("(%s)", assignment.getRole()));
role.setVisible(StringUtils.isNotBlank(assignment.getRole()));
item.add(role);
item.add(new Label("projectCode", assignment.getProject().getProjectCode()));
item.add(createStatusLabel(item));
addInputCells(item, row);
item.add(createTotalHoursLabel(row));
}
private Component createBookWholeWeekLink(final TimesheetRow row, final String bookWholeWeek) {
final AjaxLink<Void> link = new AjaxLink<Void>(bookWholeWeek) {
private static final long serialVersionUID = -663239917205218384L;
@Override
public void onClick(AjaxRequestTarget target) {
row.bookRemainingHoursOnRow();
target.add(form);
}
};
link.setVisible(EhourWebApplication.get().isBookWholeWeekEnabled() && !row.getTimesheet().isAnyLocked());
return link;
}
private Label createStatusLabel(ListItem<TimesheetRow> item) {
Label label = new Label("status", new PropertyModel<String>(item.getModel(), "status")) {
@Override
public boolean isVisible() {
return StringUtils.isNotBlank(getDefaultModelObjectAsString());
}
};
label.setEscapeModelStrings(false);
label.setOutputMarkupId(true);
label.setOutputMarkupPlaceholderTag(true);
return label;
}
private Label createTotalHoursLabel(final TimesheetRow row) {
final Label totalHours = new Label("total", new ProjectTotalModel(row)) {
@Override
public void onEvent(IEvent<?> event) {
if (event.getPayload() instanceof TimesheetInputModifiedEvent) {
TimesheetInputModifiedEvent payload = (TimesheetInputModifiedEvent) event.getPayload();
if (payload.getForAssignment().equals(row.getProjectAssignment())) {
payload.getTarget().add(this);
}
}
}
};
totalHours.setOutputMarkupId(true);
return totalHours;
}
private void addInputCells(ListItem<TimesheetRow> item, final TimesheetRow row) {
Calendar currentDate = (Calendar) row.getFirstDayOfWeekDate().clone();
ProjectAssignment assignment = row.getProjectAssignment();
final DateRange range = new DateRange(assignment.getDateStart(), assignment.getDateEnd());
final List<Calendar> dates = Lists.newArrayList();
for (int i = 1;
i <= 7;
i++, currentDate.add(Calendar.DAY_OF_YEAR, 1)) {
dates.add((Calendar) currentDate.clone());
}
item.add(new ListView<Calendar>("days", dates) {
@Override
protected void populateItem(ListItem<Calendar> item) {
Calendar currentDate = item.getModelObject();
int index = currentDate.get(Calendar.DAY_OF_WEEK) - 1;
TimesheetCell timesheetCell = row.getTimesheetCells()[index];
DayStatus dayStatus = determineDayStatus(timesheetCell, currentDate, range);
Component day;
switch (dayStatus) {
case HIDDEN:
default:
day = createEmptyTimesheetEntry(DAY_ID);
item.add(new WebMarkupContainer(DAY_OPTIONS_ID));
break;
case OPEN:
day = createInputTimesheetEntry(DAY_ID, row, index);
item.add(renderOptions(DAY_OPTIONS_ID, timesheetCell, dayStatus));
break;
case LOCKED:
day = createLockedTimesheetEntry(DAY_ID, row, index);
item.add(renderOptions(DAY_OPTIONS_ID, timesheetCell, dayStatus));
item.add(AttributeModifier.append("class", "locked"));
break;
}
String cssClass = "weekday";
if (dayStatus == DayStatus.LOCKED) {
cssClass += " lockedCell";
} else if (item.getIndex() == 0) {
cssClass = "sunday";
} else if (item.getIndex() == 6) {
cssClass = "saturday";
}
day.add(AttributeModifier.replace("class", cssClass));
item.add(day);
}
});
}
private DayStatus determineDayStatus(TimesheetCell timesheetCell, Calendar currentDate, DateRange range) {
if (DateUtil.isDateWithinRange(currentDate, range)) {
return timesheetCell.isLocked() ? DayStatus.LOCKED : DayStatus.OPEN;
} else {
return timesheetCell.isWithHours() ? DayStatus.LOCKED : DayStatus.HIDDEN;
}
}
private Fragment createLockedTimesheetEntry(String id, TimesheetRow row, int index) {
Fragment fragment = new Fragment(id, "dayLocked", provider);
TimesheetCell timesheetCell = row.getTimesheetCells()[index];
PropertyModel<Float> cellModel = new PropertyModel<>(timesheetCell, "timesheetEntry.hours");
String css = timesheetCell.getTimesheetEntry() != null && StringUtils.isNotBlank(timesheetCell.getTimesheetEntry().getComment()) ? "lockedday" : "lockeddaynocomment";
fragment.add(AttributeModifier.append("class", css));
// make sure it's added to the grandtotal
grandTotals.addValue(index, timesheetCell, cellModel);
fragment.add(new Label(DAY_ID, cellModel));
return fragment;
}
private RepeatingView renderOptions(String id, TimesheetCell timesheetCell, DayStatus status) {
RepeatingView options = new RepeatingView(id);
for (TimesheetIconRenderFactory renderFactory : iconRenderer.getRenderFactories()) {
options.add(renderFactory.renderForId(options.newChildId(), timesheetCell, status, timesheetContainerModel));
}
return options;
}
private Fragment createEmptyTimesheetEntry(String id) {
return new Fragment(id, "dayInputHidden", provider);
}
private Fragment createInputTimesheetEntry(String id, TimesheetRow row, final int index) {
TimesheetCell timesheetCell = row.getTimesheetCells()[index];
Fragment fragment = new Fragment(id, "dayInput", provider);
fragment.add(createTextFieldWithValidation(timesheetCell, index));
return fragment;
}
private TimesheetTextField createTextFieldWithValidation(final TimesheetCell timesheetCell, final int index) {
PropertyModel<Float> cellModel = new PropertyModel<>(timesheetCell, "timesheetEntry.hours");
// make sure it's added to the grandtotal
grandTotals.addValue(index, timesheetCell, cellModel);
// add inputfield with validation to the parent
final TimesheetTextField dayInput = new TimesheetTextField(DAY_ID, cellModel, 1);
dayInput.setOutputMarkupId(true);
// add validation
AjaxFormComponentUpdatingBehavior behavior = new AjaxFormComponentUpdatingBehavior("onblur") {
private static final long serialVersionUID = 1L;
@Override
protected void onUpdate(AjaxRequestTarget target) {
Form sink = dayInput.findParent(Form.class);
send(sink, Broadcast.DEPTH, new TimesheetInputModifiedEvent(target, index, timesheetCell.getProjectAssignment()));
form.visitFormComponents(new FormHighlighter(target));
}
@Override
protected void onError(final AjaxRequestTarget target, RuntimeException e) {
form.visitFormComponents(new FormHighlighter(target));
}
};
dayInput.add(behavior);
return dayInput;
}
public enum DayStatus {
OPEN,
LOCKED,
HIDDEN
}
}