diff --git a/javascript/apps/taiga/src/app/modules/project/data-access/+state/effects/project.effects.ts b/javascript/apps/taiga/src/app/modules/project/data-access/+state/effects/project.effects.ts
index 59b41e1b1..cd4a8aacb 100644
--- a/javascript/apps/taiga/src/app/modules/project/data-access/+state/effects/project.effects.ts
+++ b/javascript/apps/taiga/src/app/modules/project/data-access/+state/effects/project.effects.ts
@@ -285,8 +285,8 @@ export class ProjectEffects {
}, 500);
void this.router.navigate([
- `project/${project.id}/${project.slug}${
- action.moveTo ? `/kanban/${action.moveTo}` : '/overview'
+ `project/${project.id}/${project.slug}/kanban/${
+ action.moveTo ? action.moveTo : project.workflows[0].slug
}`,
]);
diff --git a/javascript/apps/taiga/src/app/modules/project/feature-kanban/components/kanban-header/kanban-header.component.css b/javascript/apps/taiga/src/app/modules/project/feature-kanban/components/kanban-header/kanban-header.component.css
index eb3473bf3..d88a69a5e 100644
--- a/javascript/apps/taiga/src/app/modules/project/feature-kanban/components/kanban-header/kanban-header.component.css
+++ b/javascript/apps/taiga/src/app/modules/project/feature-kanban/components/kanban-header/kanban-header.component.css
@@ -20,6 +20,7 @@ Copyright (c) 2023-present Kaleidos INC
color: var(--color-gray100);
font-weight: 500;
margin: 0;
+ padding-inline-start: var(--spacing-16);
}
.view-options-list {
@@ -32,6 +33,7 @@ Copyright (c) 2023-present Kaleidos INC
.kanban-header-breadcrumbs {
padding-block-start: var(--spacing-4);
+ padding-inline-start: var(--spacing-16);
}
.workflow-form {
diff --git a/javascript/apps/taiga/src/app/modules/project/feature-kanban/components/kanban-header/kanban-header.component.ts b/javascript/apps/taiga/src/app/modules/project/feature-kanban/components/kanban-header/kanban-header.component.ts
index e668ce565..6cbb8d943 100644
--- a/javascript/apps/taiga/src/app/modules/project/feature-kanban/components/kanban-header/kanban-header.component.ts
+++ b/javascript/apps/taiga/src/app/modules/project/feature-kanban/components/kanban-header/kanban-header.component.ts
@@ -25,6 +25,7 @@ import {
updateWorkflow,
} from '~/app/modules/project/data-access/+state/actions/project.actions';
import { Store } from '@ngrx/store';
+import { KanbanStory } from '~/app/modules/project/feature-kanban/kanban.model';
@Component({
selector: 'tg-kanban-header',
@@ -49,6 +50,7 @@ export class KanbanHeaderComponent {
@Input({ required: true }) public project!: Project;
@Input({ required: true }) public workflows: Workflow[] = [];
@Input({ required: true }) public workflow!: Workflow;
+ @Input({ required: true }) public stories: KanbanStory[] = [];
public openWorkflowOptions = false;
public deleteWorkflowModal = false;
@@ -57,6 +59,11 @@ export class KanbanHeaderComponent {
constructor(private store: Store) {}
public openDeleteWorkflowModal() {
+ if (this.workflow.statuses.length === 0 || this.stories.length === 0) {
+ this.submitDeleteWorkflow(undefined);
+ return;
+ }
+
this.deleteWorkflowModal = true;
}
diff --git a/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/reducers/kanban.reducer.ts b/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/reducers/kanban.reducer.ts
index 252cea9c7..03ded6867 100644
--- a/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/reducers/kanban.reducer.ts
+++ b/javascript/apps/taiga/src/app/modules/project/feature-kanban/data-access/+state/reducers/kanban.reducer.ts
@@ -368,9 +368,10 @@ export const reducer = createImmerReducer(
return state;
}),
on(KanbanEventsActions.newStory, (state, { story }): KanbanState => {
- state.stories[story.status.id].push(story);
-
- state.newEventStories.push(story.ref);
+ if (state.stories[story.status.id]) {
+ state.stories[story.status.id].push(story);
+ state.newEventStories.push(story.ref);
+ }
return state;
}),
diff --git a/javascript/apps/taiga/src/app/modules/project/feature-kanban/project-feature-kanban.component.html b/javascript/apps/taiga/src/app/modules/project/feature-kanban/project-feature-kanban.component.html
index 36a614567..910849af4 100644
--- a/javascript/apps/taiga/src/app/modules/project/feature-kanban/project-feature-kanban.component.html
+++ b/javascript/apps/taiga/src/app/modules/project/feature-kanban/project-feature-kanban.component.html
@@ -24,7 +24,8 @@
*ngIf="vm.workflow"
[project]="vm.project"
[workflows]="vm.workflows"
- [workflow]="vm.workflow">
+ [workflow]="vm.workflow"
+ [stories]="vm.stories">
;
+ stories: KanbanStory[];
}
@UntilDestroy()
@@ -241,6 +242,13 @@ export class ProjectFeatureKanbanComponent {
.subscribe((postponed) => {
this.lockKanban = !!postponed;
});
+
+ this.state.connect(
+ 'stories',
+ this.store
+ .select(selectStories)
+ .pipe(map((stories) => Object.values(stories).flat()))
+ );
}
public setCloseShortcut() {
@@ -390,50 +398,6 @@ export class ProjectFeatureKanbanComponent {
this.store.dispatch(ProjectActions.fetchProjectMembers());
this.unassignRoleMembersWithoutPermissions(permissions as Role);
});
-
- this.wsService
- .projectEvents<{
- workflow: Workflow & { stories: Story[] };
- targetWorkflow: Workflow;
- }>('workflows.delete')
- .pipe(
- untilDestroyed(this),
- concatLatestFrom(() => [
- this.store.select(selectCurrentWorkflowSlug).pipe(filterNil()),
- ])
- )
- .subscribe(([eventResponse, workflow]) => {
- const action = eventResponse.event.content;
- const { stories, ...wf } = action.workflow;
- this.store.dispatch(
- ProjectActions.projectEventActions.deleteWorkflow({
- workflow: wf,
- targetWorkflow: action.targetWorkflow,
- hasOpenStory: this.state.get('showStoryDetail'),
- })
- );
-
- if (workflow === action.targetWorkflow?.slug) {
- this.movedWorkflow.statuses = action.workflow.statuses;
-
- const statuses = action.workflow.statuses;
- statuses.forEach((status) => {
- this.store.dispatch(
- KanbanEventsActions.moveStatus({
- status,
- workflow: action.targetWorkflow.slug,
- stories: stories.filter(
- (story) => story.status.id === status.id
- ),
- })
- );
- });
-
- setTimeout(() => {
- this.movedWorkflow.statuses = null;
- }, 500);
- }
- });
}
private unassignRoleMembersWithoutPermissions(role: Role) {
diff --git a/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell.component.ts b/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell.component.ts
index 183e12ef8..1db3f6466 100644
--- a/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell.component.ts
+++ b/javascript/apps/taiga/src/app/modules/project/feature-shell/project-feature-shell.component.ts
@@ -58,6 +58,11 @@ import { TranslocoDirective } from '@ngneat/transloco';
import { ContextNotificationComponent } from '@taiga/ui/context-notification/context-notification.component';
import { ProjectNavigationComponent } from '../feature-navigation/project-feature-navigation.component';
import * as ProjectActions from '~/app/modules/project/data-access/+state/actions/project.actions';
+import { concatLatestFrom } from '@ngrx/effects';
+import { selectCurrentWorkflowSlug } from '../feature-kanban/data-access/+state/selectors/kanban.selectors';
+import { KanbanEventsActions } from '../feature-kanban/data-access/+state/actions/kanban.actions';
+import { MovedWorkflowService } from '../feature-kanban/services/moved-workflow.service';
+import { selectStory } from '../story-detail/data-access/+state/selectors/story-detail.selectors';
@UntilDestroy()
@Component({
@@ -117,11 +122,13 @@ export class ProjectFeatureShellComponent implements OnDestroy, AfterViewInit {
private state: RxState<{
project: Project;
showBannerOnRevoke: boolean;
+ selectedStory: StoryDetail | null;
}>,
private userStorageService: UserStorageService,
private router: Router,
private route: ActivatedRoute,
- private appService: AppService
+ private appService: AppService,
+ private movedWorkflow: MovedWorkflowService
) {
this.route.data.subscribe((data) => {
this.store.dispatch(
@@ -142,6 +149,8 @@ export class ProjectFeatureShellComponent implements OnDestroy, AfterViewInit {
this.store.select(selectShowBannerOnRevoke).pipe(filterNil())
);
+ this.state.connect('selectedStory', this.store.select(selectStory));
+
const project$ = this.state
.select('project')
.pipe(distinctUntilChanged((prev, curr) => prev.id === curr.id));
@@ -499,6 +508,56 @@ export class ProjectFeatureShellComponent implements OnDestroy, AfterViewInit {
.subscribe(() => {
this.userLoseMembership();
});
+
+ this.wsService
+ .projectEvents<{
+ workflow: Workflow & { stories: Story[] };
+ targetWorkflow: Workflow;
+ }>('workflows.delete')
+ .pipe(
+ untilDestroyed(this),
+ concatLatestFrom(() => [
+ this.store.select(selectCurrentWorkflowSlug).pipe(filterNil()),
+ ])
+ )
+ .subscribe(([eventResponse, workflow]) => {
+ const action = eventResponse.event.content;
+ const { stories, ...wf } = action.workflow;
+
+ this.store.dispatch(
+ ProjectActions.projectEventActions.deleteWorkflow({
+ workflow: wf,
+ targetWorkflow: action.targetWorkflow,
+ hasOpenStory: !!this.state.get('selectedStory'),
+ })
+ );
+
+ if (workflow === action.targetWorkflow?.slug) {
+ this.movedWorkflow.statuses = action.workflow.statuses;
+
+ const statuses = action.workflow.statuses;
+
+ statuses.forEach((status) => {
+ const statusStories = stories.filter(
+ (story) => story.status.id === status.id
+ );
+
+ if (statusStories && statusStories.length > 0) {
+ this.store.dispatch(
+ KanbanEventsActions.moveStatus({
+ status,
+ workflow: action.targetWorkflow.slug,
+ stories: statusStories,
+ })
+ );
+ }
+ });
+
+ setTimeout(() => {
+ this.movedWorkflow.statuses = null;
+ }, 500);
+ }
+ });
}
public userLoseMembership() {