Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ Procest is a Nextcloud case management app (zaaksysteem) for Dutch municipalitie
| Register i18n | Full Dutch + English translation using Nextcloud gettext/l10n infrastructure | Forum Standaardisatie i18n | Partial | [register-i18n.md](register-i18n.md) |
| Multi-Tenant SaaS | Tenant isolation, per-tenant configuration, and NL Design System theming per tenant | Nextcloud Groups | Planned | [multi-tenant-saas.md](multi-tenant-saas.md) |
| Prometheus Metrics | `/metrics` endpoint for Prometheus scraping with SLA compliance and queue depth metrics | OpenMetrics | Planned | [prometheus-metrics.md](prometheus-metrics.md) |
| Start Case Widget | Dashboard widget for starting new cases directly from the Nextcloud dashboard | Nextcloud Dashboard API | Implemented | [start-case-widget.md](start-case-widget.md) |
| App Scaffold | PHP/Vue app foundation, OpenRegister wiring, Pinia stores, and build system | Nextcloud OCP | Implemented | [app-scaffold.md](app-scaffold.md) |

## TEC BPM RFP Template Coverage
Expand Down Expand Up @@ -179,4 +180,5 @@ create-procest-app → app-scaffold.md
procest-app-scaffold → app-scaffold.md
procest-object-store → app-scaffold.md
procest-case-management → case-management.md
start-case-widget → start-case-widget.md
```
35 changes: 35 additions & 0 deletions docs/features/start-case-widget.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Start Case Widget

## Summary

Dashboard widget for starting new cases directly from the Nextcloud dashboard or MyDash, without navigating into the Procest app first.

## Overview

Case workers frequently need to start new cases throughout their day. The Start Case widget eliminates friction by providing a one-click case creation flow directly from the Nextcloud home screen. It shows available case types (zaaktypen) as clickable cards. When a user clicks a case type, a new case is created and they are navigated to the case detail page.

In government case management (zaakgericht werken), fast intake is critical. Citizens call or walk in, and the case worker needs to register a new case immediately. This widget reduces the number of clicks from 3-4 to 1-2.

## Key Capabilities

- **Case type quick-start cards**: Shows configured case types as clickable cards with titles
- **Inline case creation**: Creates a case via OpenRegister API and navigates to the new case detail page
- **Empty state**: When no case types are configured, shows a helpful message directing admins to Procest settings
- **Loading state**: Shows loading indicator while fetching case types
- **i18n support**: All widget text available in Dutch and English
- **MyDash compatible**: Widget appears automatically when MyDash discovers registered Nextcloud widgets

## Technical Details

| Component | File |
|-----------|------|
| PHP Widget Class | `lib/Dashboard/StartCaseWidget.php` |
| Vue Component | `src/views/widgets/StartCaseWidget.vue` |
| Webpack Entry | `src/startCaseWidget.js` |
| Registration | `lib/AppInfo/Application.php` |

## Related

- Original issue: [#105](https://github.com/ConductionNL/procest/issues/105)
- Tracking issue: [#107](https://github.com/ConductionNL/procest/issues/107)
- OpenSpec change: `openspec/changes/archive/2026-03-24-start-case-widget/`
2 changes: 2 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use OCA\Procest\Dashboard\OverdueCasesWidget;
use OCA\Procest\Dashboard\StalledCasesWidget;
use OCA\Procest\Dashboard\TaskRemindersWidget;
use OCA\Procest\Dashboard\StartCaseWidget;
use OCA\Procest\Listener\DeepLinkRegistrationListener;
use OCA\Procest\Middleware\ZgwAuthMiddleware;
use OCP\AppFramework\App;
Expand Down Expand Up @@ -75,6 +76,7 @@ public function register(IRegistrationContext $context): void
$context->registerDashboardWidget(DeadlineAlertsWidget::class);
$context->registerDashboardWidget(TaskRemindersWidget::class);
$context->registerDashboardWidget(StalledCasesWidget::class);
$context->registerDashboardWidget(StartCaseWidget::class);
}//end register()

/**
Expand Down
115 changes: 115 additions & 0 deletions lib/Dashboard/StartCaseWidget.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

/**
* Start Case Dashboard Widget
*
* Displays available case types for quick case creation from the Nextcloud Dashboard.
*
* @category Dashboard
* @package OCA\Procest\Dashboard
*
* @author Conduction Development Team <dev@conductio.nl>
* @copyright 2024 Conduction B.V.
* @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
*
* @version GIT: <git-id>
*
* @link https://procest.nl
*/

declare(strict_types=1);

namespace OCA\Procest\Dashboard;

use OCA\Procest\AppInfo\Application;
use OCP\Dashboard\IWidget;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Util;

/**
* Dashboard widget showing available case types for quick case creation.
*/
class StartCaseWidget implements IWidget
{
/**
* Constructor.
*
* @param IL10N $l10n L10N service
* @param IURLGenerator $url URL generator
*/
public function __construct(
private IL10N $l10n,
private IURLGenerator $url
) {
}//end __construct()

/**
* Get the unique identifier for this widget.
*
* @inheritDoc
* @return string The widget identifier
*/
public function getId(): string
{
return 'procest_start_case_widget';
}//end getId()

/**
* Get the display title for this widget.
*
* @inheritDoc
* @return string The widget title
*/
public function getTitle(): string
{
return $this->l10n->t('Start case');
}//end getTitle()

/**
* Get the display order for this widget.
*
* @inheritDoc
* @return int The widget order
*/
public function getOrder(): int
{
return 15;
}//end getOrder()

/**
* Get the CSS icon class for this widget.
*
* @inheritDoc
* @return string The icon CSS class
*/
public function getIconClass(): string
{
return 'icon-procest-widget';
}//end getIconClass()

/**
* Get the URL for the widget's full view.
*
* @inheritDoc
* @return string|null The widget URL or null
*/
public function getUrl(): ?string
{
return $this->url->linkToRouteAbsolute(Application::APP_ID.'.dashboard.page');
}//end getUrl()

/**
* Load the widget scripts and styles.
*
* @inheritDoc
* @return void
*
* @SuppressWarnings(PHPMD.StaticAccess) — Nextcloud Util API is static by design
*/
public function load(): void
{
Util::addScript(Application::APP_ID, Application::APP_ID.'-startCaseWidget');
Util::addStyle(Application::APP_ID, 'dashboardWidgets');
}//end load()
}//end class
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Start Case Widget — Technical Design

## File List

| File | Action | Purpose |
|------|--------|---------|
| `lib/Dashboard/StartCaseWidget.php` | Create | PHP widget class implementing `IWidget` |
| `src/views/widgets/StartCaseWidget.vue` | Create | Vue component rendering case type cards |
| `src/startCaseWidget.js` | Create | Webpack entry point, registers with `OCA.Dashboard` |
| `lib/AppInfo/Application.php` | Modify | Register `StartCaseWidget::class` |
| `webpack.config.js` | Modify | Add `startCaseWidget` entry |

## Component Architecture

```
StartCaseWidget.php (IWidget)
├── getId() → 'procest_start_case_widget'
├── getTitle() → t('Start case')
├── getOrder() → 15
├── getIconClass() → 'icon-procest-widget'
├── getUrl() → link to Procest dashboard
└── load() → Util::addScript + Util::addStyle

startCaseWidget.js (entry point)
└── OCA.Dashboard.register('procest_start_case_widget', callback)
└── Mounts StartCaseWidget.vue

StartCaseWidget.vue
├── data: loading, creating, caseTypes[]
├── computed: objectStore (useObjectStore)
├── mounted: fetchCaseTypes()
├── methods:
│ ├── fetchCaseTypes() → objectStore.fetchCollection('caseType')
│ └── startCase(caseType) → objectStore.saveObject('case', ...) → navigate
└── template:
├── loading → NcLoadingIcon
├── empty → NcEmptyContent
└── cards → grid of case type buttons
```

## Data Flow

1. **Widget mount**: `StartCaseWidget.vue` calls `fetchCaseTypes()` on mount
2. **Fetch**: Object store queries OpenRegister for `caseType` objects (non-draft)
3. **Render**: Case types displayed as clickable cards in a CSS grid
4. **Click**: User clicks a card → `startCase(caseType)` is called
5. **Create**: Object store saves a new `case` object with the selected case type
6. **Navigate**: `window.location.href` redirects to the new case in Procest

## Seed Data

No new seed data required. The widget reads existing case types already seeded by the Procest register configuration (`procest_register.json`). Case types are created through the Procest admin interface.

## CSS Strategy

Widget uses scoped styles with CSS variables for NL Design compatibility:
- `var(--color-primary)` for card hover/active states
- `var(--color-background-hover)` for card background on hover
- `var(--color-main-text)` for text
- `var(--border-radius-large)` for card corners
91 changes: 91 additions & 0 deletions openspec/changes/archive/2026-03-24-start-case-widget/plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
{
"change": "start-case-widget",
"project": "procest",
"repo": "ConductionNL/procest",
"created": "2026-03-24",
"tracking_issue": 107,
"tasks": [
{
"id": "T1",
"title": "Create PHP widget class",
"priority": "MVP",
"status": "done",
"spec_ref": "specs/spec.md#start-case-widget-php",
"files_likely_affected": ["lib/Dashboard/StartCaseWidget.php"],
"acceptance_criteria": [
"Class passes php -l syntax check and follows PSR-12",
"Implements OCP\\Dashboard\\IWidget following CasesOverviewWidget pattern"
],
"github_issue": 108
},
{
"id": "T2",
"title": "Register widget in Application.php",
"priority": "MVP",
"status": "done",
"spec_ref": "specs/spec.md#widget-registration",
"files_likely_affected": ["lib/AppInfo/Application.php"],
"acceptance_criteria": [
"Widget appears in register() method alongside other widgets",
"Use statement and registerDashboardWidget call added"
],
"github_issue": 109
},
{
"id": "T3",
"title": "Create Vue component",
"priority": "MVP",
"status": "done",
"spec_ref": "specs/spec.md#start-case-widget-vue",
"files_likely_affected": ["src/views/widgets/StartCaseWidget.vue"],
"acceptance_criteria": [
"GIVEN a user viewing the dashboard WHEN they add the widget THEN they see available case types",
"GIVEN a user clicks a case type THEN a new case is created and they navigate to detail page",
"GIVEN no case types configured THEN empty state with admin settings message shown",
"GIVEN case types loading THEN loading state shown",
"All strings wrapped in t('procest', '...')"
],
"github_issue": 110
},
{
"id": "T4",
"title": "Create webpack entry point",
"priority": "MVP",
"status": "done",
"spec_ref": "specs/spec.md#webpack-entry",
"files_likely_affected": ["src/startCaseWidget.js"],
"acceptance_criteria": [
"Entry file matches existing widget entry pattern",
"Registers via OCA.Dashboard.register('procest_start_case_widget', ...)"
],
"github_issue": 111
},
{
"id": "T5",
"title": "Add webpack config entry",
"priority": "MVP",
"status": "done",
"spec_ref": "specs/spec.md#webpack-config",
"files_likely_affected": ["webpack.config.js"],
"acceptance_criteria": [
"startCaseWidget entry added alongside other widget entries",
"Output filename: procest-startCaseWidget.js"
],
"github_issue": 112
},
{
"id": "T6",
"title": "Quality checks",
"priority": "MVP",
"status": "done",
"spec_ref": "tasks/tasks.md#T6",
"files_likely_affected": [],
"acceptance_criteria": [
"No syntax errors from php -l",
"Pattern consistency verified",
"All user-visible strings use t() for i18n"
],
"github_issue": 113
}
]
}
Loading
Loading