diff --git a/src/bar.js b/src/bar.js
index e27a85b2c..edc0feefe 100644
--- a/src/bar.js
+++ b/src/bar.js
@@ -591,6 +591,28 @@ export default class Bar {
}
compute_duration() {
+ // For hour-level precision, calculate duration directly in the configured unit
+ if (
+ ['Hour', 'Quarter Day', 'Half Day'].includes(
+ this.gantt.config.view_mode.name,
+ )
+ ) {
+ this.duration =
+ date_utils.diff(
+ this.task._end,
+ this.task._start,
+ this.gantt.config.unit,
+ ) / this.gantt.config.step;
+
+ // For hour-level views, we don't need to worry about ignored dates/weekends
+ this.actual_duration_raw = this.duration;
+ this.ignored_duration_raw = 0;
+ this.task.actual_duration = this.duration;
+ this.task.ignored_duration = 0;
+ return;
+ }
+
+ // Original day-based calculation for day-level and above views
let actual_duration_in_days = 0,
duration_in_days = 0;
for (
diff --git a/src/defaults.js b/src/defaults.js
index e63e8ef5c..48f93a2c9 100644
--- a/src/defaults.js
+++ b/src/defaults.js
@@ -130,19 +130,31 @@ const DEFAULT_OPTIONS = {
if (ctx.task.description) ctx.set_subtitle(ctx.task.description);
else ctx.set_subtitle('');
+ const is_hour_level = ['Hour', 'Quarter Day', 'Half Day'].includes(
+ ctx.chart.config.view_mode.name,
+ );
+ const date_format = is_hour_level ? 'MMM D HH:mm' : 'MMM D';
+
const start_date = date_utils.format(
ctx.task._start,
- 'MMM D',
+ date_format,
ctx.chart.options.language,
);
const end_date = date_utils.format(
date_utils.add(ctx.task._end, -1, 'second'),
- 'MMM D',
+ date_format,
ctx.chart.options.language,
);
+ const duration_unit = is_hour_level ? 'hours' : 'days';
+ const duration_value = is_hour_level
+ ? Math.round(
+ date_utils.diff(ctx.task._end, ctx.task._start, 'hour') * 100,
+ ) / 100
+ : ctx.task.actual_duration;
+
ctx.set_details(
- `${start_date} - ${end_date} (${ctx.task.actual_duration} days${ctx.task.ignored_duration ? ' + ' + ctx.task.ignored_duration + ' excluded' : ''})
Progress: ${Math.floor(ctx.task.progress * 100) / 100}%`,
+ `${start_date} - ${end_date} (${duration_value} ${duration_unit}${ctx.task.ignored_duration ? ' + ' + ctx.task.ignored_duration + ' excluded' : ''})
Progress: ${Math.floor(ctx.task.progress * 100) / 100}%`,
);
},
popup_on: 'click',
diff --git a/src/index.js b/src/index.js
index 054d3e2ce..c6fe930b8 100644
--- a/src/index.js
+++ b/src/index.js
@@ -165,10 +165,18 @@ export default class Gantt {
// cache index
task._index = i;
- // if hours is not set, assume the last day is full day
- // e.g: 2018-09-09 becomes 2018-09-09 23:59:59
+ // Only extend to full day if we're in day-level view modes and no time was specified
+ // For hour-level granularity, preserve the exact end time
const task_end_values = date_utils.get_date_values(task._end);
- if (task_end_values.slice(3).every((d) => d === 0)) {
+ const is_hour_level_view = [
+ 'Hour',
+ 'Quarter Day',
+ 'Half Day',
+ ].includes(this.options.view_mode);
+ if (
+ task_end_values.slice(3).every((d) => d === 0) &&
+ !is_hour_level_view
+ ) {
task._end = date_utils.add(task._end, 24, 'hour');
}
@@ -323,7 +331,15 @@ export default class Gantt {
}
this.config.date_format =
this.config.view_mode.date_format || this.options.date_format;
- this.gantt_start.setHours(0, 0, 0, 0);
+
+ // Only reset hours for day-level and above views
+ if (
+ !['Hour', 'Quarter Day', 'Half Day'].includes(
+ this.config.view_mode.name,
+ )
+ ) {
+ this.gantt_start.setHours(0, 0, 0, 0);
+ }
}
setup_date_values() {