mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
UI elements for scheduling
This commit is contained in:
parent
c87b5622ca
commit
2814d28f95
9 changed files with 181 additions and 3 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -112,3 +112,5 @@ playwright-report/
|
|||
|
||||
|
||||
electron-builder-appx.yaml
|
||||
SCHEDULING_FEATURE_IMPLEMENTATION.md
|
||||
SCHEDULING_ROADMAP.md
|
||||
|
|
|
|||
|
|
@ -21,6 +21,23 @@
|
|||
>
|
||||
{{ T.GCF.SCHEDULE.MONTH | translate }}
|
||||
</button>
|
||||
|
||||
<mat-form-field
|
||||
class="task-placement-select"
|
||||
appearance="outline"
|
||||
>
|
||||
<mat-label>{{ T.GCF.SCHEDULE.L_TASK_PLACEMENT_STRATEGY | translate }}</mat-label>
|
||||
<mat-select
|
||||
[value]="scheduleConfig()?.taskPlacementStrategy || 'DEFAULT'"
|
||||
(selectionChange)="updateTaskPlacementStrategy($event.value)"
|
||||
>
|
||||
@for (strategy of taskPlacementStrategies; track strategy.value) {
|
||||
<mat-option [value]="strategy.value">
|
||||
{{ strategy.label | translate }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -297,10 +297,11 @@ button.isActive2 {
|
|||
|
||||
.week-month-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--s);
|
||||
border-radius: var(--card-border-radius);
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
margin-right: calc(var(--s) * 0.25);
|
||||
box-shadow: var(--whiteframe-shadow-2dp);
|
||||
|
||||
@include mq(xs) {
|
||||
margin-right: var(--s);
|
||||
|
|
@ -308,6 +309,53 @@ button.isActive2 {
|
|||
|
||||
@include mq(xs, max) {
|
||||
margin-right: calc(var(--s) * 0.125);
|
||||
gap: calc(var(--s) * 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.task-placement-select {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
|
||||
::ng-deep .mat-mdc-form-field-subscript-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-text-field-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-form-field-infix {
|
||||
min-height: 36px;
|
||||
padding: 6px 12px 6px 8px;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-select {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-select-value {
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-select-arrow-wrapper {
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
@include mq(xs, max) {
|
||||
font-size: 11px;
|
||||
::ng-deep .mat-mdc-form-field-infix {
|
||||
min-height: 32px;
|
||||
padding: 4px 10px 4px 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Style for dropdown panel options
|
||||
::ng-deep .mat-mdc-select-panel {
|
||||
.mat-mdc-option {
|
||||
padding-left: 16px;
|
||||
padding-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ import { toSignal } from '@angular/core/rxjs-interop';
|
|||
import { MetricService } from '../../features/metric/metric.service';
|
||||
import { DateService } from '../../core/date/date.service';
|
||||
import { FocusButtonComponent } from './focus-button/focus-button.component';
|
||||
import { MatFormField, MatLabel } from '@angular/material/form-field';
|
||||
import { MatOption, MatSelect } from '@angular/material/select';
|
||||
import { TaskPlacementStrategy } from '../../features/config/global-config.model';
|
||||
|
||||
@Component({
|
||||
selector: 'main-header',
|
||||
|
|
@ -65,6 +68,10 @@ import { FocusButtonComponent } from './focus-button/focus-button.component';
|
|||
PlayButtonComponent,
|
||||
DesktopPanelButtonsComponent,
|
||||
FocusButtonComponent,
|
||||
MatFormField,
|
||||
MatSelect,
|
||||
MatOption,
|
||||
MatLabel,
|
||||
],
|
||||
})
|
||||
export class MainHeaderComponent implements OnDestroy {
|
||||
|
|
@ -152,6 +159,25 @@ export class MainHeaderComponent implements OnDestroy {
|
|||
this._metricService.getFocusSummaryForDay(this._dateService.todayStr()),
|
||||
);
|
||||
|
||||
scheduleConfig = toSignal(
|
||||
this.globalConfigService.cfg$.pipe(map((cfg) => cfg?.schedule)),
|
||||
);
|
||||
|
||||
taskPlacementStrategies: { value: TaskPlacementStrategy; label: string }[] = [
|
||||
{ value: 'DEFAULT', label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_DEFAULT },
|
||||
{
|
||||
value: 'SHORTEST_FIRST',
|
||||
label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_SHORTEST_FIRST,
|
||||
},
|
||||
{
|
||||
value: 'LONGEST_FIRST',
|
||||
label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_LONGEST_FIRST,
|
||||
},
|
||||
{ value: 'OLDEST_FIRST', label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_OLDEST_FIRST },
|
||||
{ value: 'NEWEST_FIRST', label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_NEWEST_FIRST },
|
||||
{ value: 'BEST_FIT', label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_BEST_FIT },
|
||||
];
|
||||
|
||||
private _subs: Subscription = new Subscription();
|
||||
|
||||
selectedTimeView = computed(() => this.layoutService.selectedTimeView());
|
||||
|
|
@ -160,6 +186,12 @@ export class MainHeaderComponent implements OnDestroy {
|
|||
this.layoutService.selectedTimeView.set(view);
|
||||
}
|
||||
|
||||
updateTaskPlacementStrategy(strategy: TaskPlacementStrategy): void {
|
||||
this.globalConfigService.updateSection('schedule', {
|
||||
taskPlacementStrategy: strategy,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this._subs.unsubscribe();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -173,6 +173,8 @@ export const DEFAULT_GLOBAL_CONFIG: GlobalConfigState = {
|
|||
isLunchBreakEnabled: false,
|
||||
lunchBreakStart: '13:00',
|
||||
lunchBreakEnd: '14:00',
|
||||
isAllowTaskSplitting: true,
|
||||
taskPlacementStrategy: 'DEFAULT',
|
||||
},
|
||||
|
||||
sync: {
|
||||
|
|
|
|||
|
|
@ -81,5 +81,46 @@ export const SCHEDULE_FORM_CFG: ConfigFormSection<ScheduleConfig> = {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'isAllowTaskSplitting',
|
||||
type: 'checkbox',
|
||||
templateOptions: {
|
||||
label: T.GCF.SCHEDULE.L_IS_ALLOW_TASK_SPLITTING,
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'taskPlacementStrategy',
|
||||
type: 'select',
|
||||
templateOptions: {
|
||||
label: T.GCF.SCHEDULE.L_TASK_PLACEMENT_STRATEGY,
|
||||
required: true,
|
||||
options: [
|
||||
{
|
||||
label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_DEFAULT,
|
||||
value: 'DEFAULT',
|
||||
},
|
||||
{
|
||||
label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_SHORTEST_FIRST,
|
||||
value: 'SHORTEST_FIRST',
|
||||
},
|
||||
{
|
||||
label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_LONGEST_FIRST,
|
||||
value: 'LONGEST_FIRST',
|
||||
},
|
||||
{
|
||||
label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_OLDEST_FIRST,
|
||||
value: 'OLDEST_FIRST',
|
||||
},
|
||||
{
|
||||
label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_NEWEST_FIRST,
|
||||
value: 'NEWEST_FIRST',
|
||||
},
|
||||
{
|
||||
label: T.GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_BEST_FIT,
|
||||
value: 'BEST_FIT',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -141,6 +141,14 @@ export type SyncConfig = Readonly<{
|
|||
localFileSync?: LocalFileSyncConfig;
|
||||
}>;
|
||||
|
||||
export type TaskPlacementStrategy =
|
||||
| 'DEFAULT'
|
||||
| 'BEST_FIT'
|
||||
| 'SHORTEST_FIRST'
|
||||
| 'LONGEST_FIRST'
|
||||
| 'OLDEST_FIRST'
|
||||
| 'NEWEST_FIRST';
|
||||
|
||||
export type ScheduleConfig = Readonly<{
|
||||
isWorkStartEndEnabled: boolean;
|
||||
workStart: string;
|
||||
|
|
@ -148,6 +156,8 @@ export type ScheduleConfig = Readonly<{
|
|||
isLunchBreakEnabled: boolean;
|
||||
lunchBreakStart: string;
|
||||
lunchBreakEnd: string;
|
||||
isAllowTaskSplitting: boolean;
|
||||
taskPlacementStrategy: TaskPlacementStrategy;
|
||||
}>;
|
||||
|
||||
export type ReminderConfig = Readonly<{
|
||||
|
|
|
|||
|
|
@ -1920,6 +1920,22 @@ const T = {
|
|||
WORK_START_END_DESCRIPTION: 'GCF.SCHEDULE.WORK_START_END_DESCRIPTION',
|
||||
WEEK: 'GCF.SCHEDULE.WEEK',
|
||||
MONTH: 'GCF.SCHEDULE.MONTH',
|
||||
L_IS_ALLOW_TASK_SPLITTING: 'GCF.SCHEDULE.L_IS_ALLOW_TASK_SPLITTING',
|
||||
L_TASK_PLACEMENT_STRATEGY: 'GCF.SCHEDULE.L_TASK_PLACEMENT_STRATEGY',
|
||||
TASK_PLACEMENT_STRATEGY_DEFAULT: 'GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_DEFAULT',
|
||||
TASK_PLACEMENT_STRATEGY_BEST_FIT: 'GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_BEST_FIT',
|
||||
TASK_PLACEMENT_STRATEGY_SHORTEST_FIRST:
|
||||
'GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_SHORTEST_FIRST',
|
||||
TASK_PLACEMENT_STRATEGY_LONGEST_FIRST:
|
||||
'GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_LONGEST_FIRST',
|
||||
TASK_PLACEMENT_STRATEGY_OLDEST_FIRST:
|
||||
'GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_OLDEST_FIRST',
|
||||
TASK_PLACEMENT_STRATEGY_NEWEST_FIRST:
|
||||
'GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_NEWEST_FIRST',
|
||||
TASK_PLACEMENT_STRATEGY_DEFAULT_HELP:
|
||||
'GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_DEFAULT_HELP',
|
||||
TASK_PLACEMENT_STRATEGY_BEST_FIT_HELP:
|
||||
'GCF.SCHEDULE.TASK_PLACEMENT_STRATEGY_BEST_FIT_HELP',
|
||||
},
|
||||
SHORT_SYNTAX: {
|
||||
HELP: 'GCF.SHORT_SYNTAX.HELP',
|
||||
|
|
|
|||
|
|
@ -1889,7 +1889,17 @@
|
|||
"TITLE": "Schedule",
|
||||
"WORK_START_END_DESCRIPTION": "e.g. 17:00",
|
||||
"WEEK": "Week",
|
||||
"MONTH": "Month"
|
||||
"MONTH": "Month",
|
||||
"L_IS_ALLOW_TASK_SPLITTING": "Allow task splitting across time boundaries",
|
||||
"L_TASK_PLACEMENT_STRATEGY": "Strategy",
|
||||
"TASK_PLACEMENT_STRATEGY_DEFAULT": "Default (Sequential)",
|
||||
"TASK_PLACEMENT_STRATEGY_BEST_FIT": "Best Fit (Experimental)",
|
||||
"TASK_PLACEMENT_STRATEGY_SHORTEST_FIRST": "Shortest Task First",
|
||||
"TASK_PLACEMENT_STRATEGY_LONGEST_FIRST": "Longest Task First",
|
||||
"TASK_PLACEMENT_STRATEGY_OLDEST_FIRST": "Oldest Task First",
|
||||
"TASK_PLACEMENT_STRATEGY_NEWEST_FIRST": "Newest Task First",
|
||||
"TASK_PLACEMENT_STRATEGY_DEFAULT_HELP": "Default: Uses simple sequential placement. Tasks are placed one after another in the order they appear in your task list, starting from the work start time or current time.",
|
||||
"TASK_PLACEMENT_STRATEGY_BEST_FIT_HELP": "Best Fit: Uses best-fit bin packing algorithm to minimize task splitting by placing tasks into gaps between scheduled items. Tasks are sorted by remaining time (shortest first) for optimal gap utilization."
|
||||
},
|
||||
"SHORT_SYNTAX": {
|
||||
"HELP": "<p>Here you can control short syntax options when creating a task</p>",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue