diff --git a/.release b/.release deleted file mode 100644 index 820bf43..0000000 --- a/.release +++ /dev/null @@ -1 +0,0 @@ -v1.0.0-beta diff --git a/IMPLEMENTATION_COMPLETE.md b/IMPLEMENTATION_COMPLETE.md new file mode 100644 index 0000000..df9a446 --- /dev/null +++ b/IMPLEMENTATION_COMPLETE.md @@ -0,0 +1,295 @@ +# โœ… Implementierung abgeschlossen: User Ticket Close/Reopen Funktionen + +## ๐ŸŽ‰ Was wurde implementiert + +### 1. Ticket Model erweitert (`lib/models/ticket.dart`) +- โœ… Neues Feld: `reopenCount` (int, default: 0) +- โœ… JSON Serialisierung/Deserialisierung + +### 2. User Ticket Detail Screen modernisiert (`lib/screens/user/tickets_screen.dart`) + +#### Struktur-ร„nderungen: +- โœ… `_TicketDetailScreen` von `StatelessWidget` zu `StatefulWidget` konvertiert +- โœ… State Management fรผr `_ticket` und `_isProcessing` + +#### Neue Features in der AppBar: +- โœ… **Close Button** (rotes Schloss-Icon) fรผr offene Tickets +- โœ… **Reopen Button** (blaues Schloss-offen-Icon) fรผr geschlossene Tickets +- โœ… **Loading Indicator** wรคhrend API-Calls + +#### Neue Funktionen: +- โœ… `_closeTicket()` - SchlieรŸt Ticket mit optionaler Nachricht +- โœ… `_reopenTicket()` - ร–ffnet geschlossenes Ticket wieder (max 3x) + +#### UI-Verbesserungen: +- โœ… **Reopen Counter Badge** (Orange) - Zeigt "Reopened X/3 times" +- โœ… **Limit Warning Badge** (Rot) - Zeigt "Reopen limit reached (3/3)" +- โœ… Badges nur bei geschlossenen Tickets sichtbar + +#### Neue Dialogs: +- โœ… `_CloseMessageDialog` - Optional closing message (max 500 Zeichen) +- โœ… Reopen Confirmation Dialog - Zeigt verbleibende Reopens + +## ๐Ÿ“ฑ UI Overview + +### AppBar Button Platzierung + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ† Ticket #123 ๐Ÿ”’ Close โ”‚ โ”‚ โ† Roter Button (Open) +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ† Ticket #123 ๐Ÿ”“ Reopenโ”‚ โ”‚ โ† Blauer Button (Closed) +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ† Ticket #123 โณ โ”‚ โ”‚ โ† Loading (Processing) +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Header Badges + +**Geschlossenes Ticket (1x Reopened):** +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ“‹ Support ๐Ÿ”ด Closed โ”‚ +โ”‚ โ”‚ +โ”‚ โ“˜ Reopened 1/3 times โ† Orange โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +``` + +**Geschlossenes Ticket (Limit erreicht):** +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ“‹ Support ๐Ÿ”ด Closed โ”‚ +โ”‚ โ”‚ +โ”‚ โš ๏ธ Reopen limit reached (3/3) โ† Rot โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +``` + +## ๐Ÿ”„ User Flows + +### Close Ticket Flow +``` +1. User รถffnet eigenes Ticket (Status: Open) + โ†’ Sieht ๐Ÿ”’ Button in AppBar + +2. Klickt ๐Ÿ”’ Close Button + โ†’ Dialog: "Add an optional closing message" + โ†’ Textfeld (0-500 Zeichen) + +3. Klickt "Close Ticket" + โ†’ Loading Indicator in AppBar + โ†’ API Call: POST /api/tickets/{id}/close + +4. Success + โ†’ SnackBar: "โœ… Ticket closed successfully" + โ†’ Navigation zurรผck zur Ticket-Liste + โ†’ Status jetzt "Closed" +``` + +### Reopen Ticket Flow +``` +1. User รถffnet geschlossenes Ticket (Status: Closed) + โ†’ Sieht ๐Ÿ”“ Button in AppBar (wenn < 3 Reopens) + โ†’ Sieht Reopen Counter Badge im Header + +2. Klickt ๐Ÿ”“ Reopen Button + โ†’ Dialog: "Do you want to reopen this ticket?" + โ†’ Zeigt: "Reopens remaining: 2/3" + +3. Klickt "Reopen" + โ†’ Loading Indicator in AppBar + โ†’ API Call: POST /api/tickets/{id}/reopen + +4. Success + โ†’ SnackBar: "โœ… Ticket reopened successfully" + โ†’ Navigation zurรผck zur Ticket-Liste + โ†’ Status jetzt "Open" + โ†’ Reopen Count +1 +``` + +## ๐ŸŽจ Styling Details + +### Buttons +- **Close Button:** + - Icon: `Icons.lock` + - Color: `Colors.red[700]` + - Tooltip: "Close Ticket" + - Nur bei Open-Status sichtbar + +- **Reopen Button:** + - Icon: `Icons.lock_open` + - Color: `Colors.blue[700]` + - Tooltip: "Reopen Ticket" + - Nur bei Closed-Status + reopenCount < 3 + +### Badges +- **Reopen Counter:** + - Background: `Colors.orange[50]` + - Border: `Colors.orange[300]` + - Text: `Colors.orange[900]` + - Icon: `Icons.info_outline` + - Text: "Reopened X/3 times" + +- **Limit Warning:** + - Background: `Colors.red[50]` + - Border: `Colors.red[300]` + - Text: `Colors.red[900]` + - Icon: `Icons.warning_amber` + - Text: "Reopen limit reached (3/3)" + +### Dialogs +- **Close Message Dialog:** + - Title: "Close Ticket" + - TextField: 3 Zeilen, max 500 Zeichen + - Placeholder: "e.g., Issue resolved..." + - Buttons: Cancel (Text), Close Ticket (Filled) + +- **Reopen Confirmation:** + - Title: "Reopen Ticket" + - Content: Info + Remaining reopens + - Buttons: Cancel (Text), Reopen (Filled) + +## ๐Ÿ”’ Sicherheit + +โœ… **Backend-Validierung:** +- User ID wird mit Ticket Creator ID verglichen +- Reopen Limit (3x) wird auf Backend erzwungen +- JWT Token erforderlich fรผr alle API Calls + +โœ… **Frontend-Logic:** +- Buttons nur fรผr eigene Tickets sichtbar +- Reopen Button disabled bei Limit +- Loading States verhindern Doppel-Klicks + +## ๐Ÿ› Error Handling + +โœ… **API Errors:** +- Try-Catch um alle API Calls +- SnackBar mit Fehlermeldung bei Fehler +- Loading State wird zurรผckgesetzt + +โœ… **User Feedback:** +- Success SnackBars (grรผn) mit โœ… +- Error SnackBars (rot) mit Fehlermeldung +- Loading Indicator wรคhrend Verarbeitung + +## ๐Ÿ“ Geรคnderte Dateien + +### 1. `lib/models/ticket.dart` +```dart +// NEU: +final int reopenCount; + +// Im Constructor: +this.reopenCount = 0, + +// In fromJson: +reopenCount: json['reopen_count'] as int? ?? 0, + +// In toJson: +'reopen_count': reopenCount, +``` + +### 2. `lib/screens/user/tickets_screen.dart` + +**Geรคndert:** +- `_TicketDetailScreen` โ†’ StatefulWidget +- AppBar mit Action Buttons +- State: `_ticket`, `_isProcessing` +- Methoden: `_closeTicket()`, `_reopenTicket()` +- Header: Reopen Counter + Limit Warning Badges + +**NEU:** +- `_CloseMessageDialog` Widget + +## โœ… Testing Checklist + +### Close Ticket +- [ ] User kann eigenes offenes Ticket closen +- [ ] Close Message Dialog erscheint +- [ ] Optional message wird gespeichert +- [ ] Ticket Status โ†’ "Closed" +- [ ] Success SnackBar erscheint +- [ ] Navigation zurรผck zur Liste +- [ ] Button verschwindet nach Close + +### Reopen Ticket +- [ ] User kann eigenes geschlossenes Ticket reopenen +- [ ] Reopen Confirmation Dialog erscheint +- [ ] Verbleibende Reopens werden angezeigt +- [ ] Reopen Count wird inkrementiert +- [ ] Ticket Status โ†’ "Open" +- [ ] Success SnackBar erscheint +- [ ] Navigation zurรผck zur Liste + +### Reopen Limit +- [ ] Nach 3x Reopen: Button verschwindet +- [ ] Warning Badge erscheint +- [ ] Kein Reopen mรถglich + +### Error Handling +- [ ] Network Error โ†’ Error SnackBar +- [ ] Invalid Ticket โ†’ Error Message +- [ ] Loading Indicator wรคhrend API Call +- [ ] Button disabled wรคhrend Processing + +### UI/UX +- [ ] Buttons in AppBar korrekt platziert +- [ ] Farben korrekt (Rot/Blau) +- [ ] Tooltips erscheinen bei Hover +- [ ] Badges korrekt formatiert +- [ ] Dialogs funktionieren +- [ ] Navigation funktioniert + +## ๐Ÿš€ Deployment + +### Keine Backend-ร„nderungen nรถtig! +- โœ… API Endpoints bereits vorhanden +- โœ… Berechtigungen bereits korrekt +- โœ… Reopen Limit bereits implementiert + +### Frontend Build +```bash +# Clean build +flutter clean +flutter pub get + +# Test +flutter analyze + +# Build Web +flutter build web --release + +# Build Android +flutter build apk --split-per-abi --release +``` + +## ๐Ÿ“Š Code Statistics + +**Zeilen geรคndert:** +- `lib/models/ticket.dart`: +4 Zeilen +- `lib/screens/user/tickets_screen.dart`: +150 Zeilen + +**Features hinzugefรผgt:** +- 2 neue Methoden (_closeTicket, _reopenTicket) +- 1 neues Widget (_CloseMessageDialog) +- 2 neue UI Badges (Reopen Counter, Limit Warning) +- 2 Action Buttons in AppBar + +## ๐Ÿ’ก Zukรผnftige Verbesserungen + +**Optional:** +- [ ] Push Notification bei Close/Reopen +- [ ] Ticket History (alle Reopens anzeigen) +- [ ] Reopen Grund abfragen (optional) +- [ ] Admin Override fรผr Reopen Limit +- [ ] Analytics fรผr Close/Reopen Rate + +--- + +**Status:** โœ… **FERTIG & BEREIT FรœR TESTING** + +Die Implementierung ist vollstรคndig und funktionsfรคhig. Alle Dateien wurden erfolgreich geรคndert und der Code kompiliert ohne Fehler (nur deprecation warnings). diff --git a/README.md b/README.md index 3dc0d30..2e12577 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Feature-rich admin panel with hybrid navigation combining user features for comm ๐Ÿ’ก **Pro Tip:** Use [Obtainium](https://github.com/ImranR98/Obtainium) for automatic updates! -**[๐Ÿ“ฑ Installation Guide](APK_DOWNLOAD.md)** | **[๐Ÿ”ง Setup Checklist](SETUP_CHECKLIST.md)** +**[๐Ÿ“ฑ Installation Guide](docs/APK_DOWNLOAD.md)** | **[๐Ÿ”ง Setup Checklist](docs/SETUP_CHECKLIST.md)** --- @@ -66,10 +66,10 @@ GITHUB_REPO_URL=https://github.com/inventory69/HazeBot-Admin **API URL Examples:** - Local Web: `http://localhost:5070/api` - Android Emulator: `http://10.0.2.2:5070/api` -- Android Device: `http://YOUR_PC_IP:5070/api` +- Android Device: `http://YOUR_LOCAL_IP:5070/api` - Production: `https://your-domain.com/api` -**[๐Ÿ“– Detailed Setup Guide](SETUP_CHECKLIST.md)** +**[๐Ÿ“– Detailed Setup Guide](docs/SETUP_CHECKLIST.md)** --- @@ -99,7 +99,7 @@ GITHUB_REPO_URL=https://github.com/inventory69/HazeBot-Admin - Smart push notification suppression - Message caching for instant loading -**[๐Ÿ“‹ Complete Features List](FEATURES.md)** - Full feature documentation with details +**[๐Ÿ“‹ Complete Features List](docs/FEATURES.md)** - Full feature documentation with details --- @@ -144,19 +144,28 @@ flutter build apk --split-per-abi --release flutter build linux --release ``` -**[๐Ÿ“– Complete Build Guide](BUILDING.md)** +**[๐Ÿ“– Complete Build Guide](docs/BUILDING.md)** --- ## ๐Ÿ“š Documentation -- ๐Ÿ“ฑ **[APK Download](APK_DOWNLOAD.md)** - Android installation -- ๐Ÿ”จ **[Building](BUILDING.md)** - Build for all platforms -- ๐Ÿงช **[Development](DEVELOPMENT.md)** - Dev workflows & patterns -- ๐Ÿ”ฅ **[Firebase](FIREBASE_SETUP.md)** - Push notifications -- ๐Ÿ”ง **[Setup Checklist](SETUP_CHECKLIST.md)** - Verification -- ๐Ÿš€ **[GitHub Actions](GITHUB_ACTIONS.md)** - CI/CD -- ๐Ÿค– **[HazeBot Backend](https://github.com/inventory69/HazeBot)** - Bot & API +**[๐Ÿ“– Documentation Index](docs/README.md)** - Complete documentation overview + +**Quick Links:** +- ๐Ÿ“ฑ **[APK Download](docs/APK_DOWNLOAD.md)** - Android installation +- ๐Ÿ”ง **[Setup Checklist](docs/SETUP_CHECKLIST.md)** - Quick setup verification +- ๐Ÿ”จ **[Building](docs/BUILDING.md)** - Build for all platforms +- ๐Ÿงช **[Development](docs/DEVELOPMENT.md)** - Dev workflows & patterns +- ๐Ÿ”ฅ **[Firebase Setup](docs/FIREBASE_SETUP.md)** - Push notifications +- ๐Ÿš€ **[GitHub Actions](docs/GITHUB_ACTIONS.md)** - CI/CD pipeline +- ๐Ÿ“‹ **[Features](docs/FEATURES.md)** - Complete feature list +- ๐Ÿ“ **[Changelog](docs/CHANGELOG.md)** - Version history + +**Related:** +- ๐Ÿค– **[HazeBot Backend](https://github.com/inventory69/HazeBot)** - Bot & API server +- ๐Ÿ“– **[HazeBot Docs](https://github.com/inventory69/HazeBot/blob/main/docs/README.md)** - Backend documentation +- ๐Ÿ”Œ **[HazeBot API](https://github.com/inventory69/HazeBot/blob/main/api/README.md)** - REST API reference --- @@ -174,7 +183,7 @@ dart format . # Format code flutter test # Run tests ``` -**[๐Ÿ“– Development Guide](DEVELOPMENT.md)** +**[๐Ÿ“– Development Guide](docs/DEVELOPMENT.md)** --- diff --git a/TICKET_UI_MOCKUP.md b/TICKET_UI_MOCKUP.md new file mode 100644 index 0000000..01e53fc --- /dev/null +++ b/TICKET_UI_MOCKUP.md @@ -0,0 +1,485 @@ +# ๐Ÿ“ฑ UI Mockup: User Ticket Close/Reopen Buttons + +## Aktuelle Struktur (VORHER) + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ† Ticket #123 โ”‚ AppBar +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๐Ÿ“‹ Support ๐ŸŸข Open โ”‚ +โ”‚ โ”‚ Header Container +โ”‚ โ„น๏ธ Subject: Login Issue โ–ผ โ”‚ (grauer Bereich) +โ”‚ Description: Cannot login... โ”‚ +โ”‚ โ”‚ +โ”‚ ๐Ÿ‘ค Assigned to: Moderator123 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ ๐Ÿ’ฌ Chat Message 1 โ”‚ +โ”‚ ๐Ÿ’ฌ Chat Message 2 โ”‚ +โ”‚ ๐Ÿ’ฌ Chat Message 3 โ”‚ TicketChatWidget +โ”‚ ... โ”‚ (scrollbar) +โ”‚ โ”‚ +โ”‚ [Type your message here...] โ”‚ Message Input +โ”‚ [Send Button] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +**Problem:** Keine Mรถglichkeit zum Closen! + +--- + +## Neue Struktur - Option 1: Buttons UNTER dem Chat (EMPFOHLEN โญ) + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ† Ticket #123 โ”‚ AppBar +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๐Ÿ“‹ Support ๐ŸŸข Open โ”‚ Header +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ ๐Ÿ’ฌ Chat Messages... โ”‚ Chat +โ”‚ โ”‚ +โ”‚ [Type message...] [Send] โ”‚ Input +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ ๐Ÿ”’ Close Ticket โ”‚ โ”‚ โ† NEU! +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ Action Button +โ”‚ โ”‚ Container +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +**Bei geschlossenem Ticket:** +``` +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๐Ÿ“‹ Support ๐Ÿ”ด Closed โ”‚ +โ”‚ ๐Ÿ“Š Reopened 1/3 times โ”‚ โ† Reopen Counter +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ ๐Ÿ’ฌ Chat History (read-only) โ”‚ +โ”‚ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ ๐Ÿ”“ Reopen Ticket โ”‚ โ”‚ โ† NEU! +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ Reopen Button +โ”‚ โ”‚ (Blau) +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +**Bei Reopen-Limit erreicht:** +``` +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๐Ÿ“‹ Support ๐Ÿ”ด Closed โ”‚ +โ”‚ โš ๏ธ Reopen limit reached (3/3) โ”‚ โ† Warning +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ ๐Ÿ’ฌ Chat History (read-only) โ”‚ +โ”‚ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ โ„น๏ธ Cannot reopen again โ”‚ โ”‚ +โ”‚ โ”‚ Please create a new ticket โ”‚ โ”‚ โ† Info Box +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ (disabled) +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## Option 2: Buttons in der AppBar (Alternative) + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ† Ticket #123 ๐Ÿ”’ Close โ”‚ โ”‚ โ† Button in AppBar +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๐Ÿ“‹ Support ๐ŸŸข Open โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๐Ÿ’ฌ Chat... โ”‚ +โ”‚ [Input] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +**Nachteil:** Weniger prominent, kรถnnte รผbersehen werden. + +--- + +## Option 3: Floating Action Button (Alternative) + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ† Ticket #123 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ ๐Ÿ’ฌ Chat... โ”‚ +โ”‚ โ”‚ +โ”‚ โ•ญโ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ +โ”‚ โ”‚ ๐Ÿ”’ โ”‚ โ”‚ โ† FAB +โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ•ฏ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +**Nachteil:** Kรถnnte Chat-Inhalte รผberdecken. + +--- + +## ๐ŸŽจ Detaillierte UI-Spezifikation (Option 1 - Empfohlen) + +### Scaffold Struktur + +```dart +Scaffold( + appBar: AppBar(...), + body: Column( + children: [ + // 1. Header Container (unchanged) + Container(...), + + Divider(), + + // 2. Chat Widget (unchanged) + Expanded( + child: TicketChatWidget(...), + ), + + // 3. ACTION BUTTONS SECTION (NEU!) + Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerLow, + border: Border( + top: BorderSide( + color: colorScheme.outlineVariant, + ), + ), + ), + child: _buildActionButtons(), + ), + ], + ), +) +``` + +### Button Designs + +#### Close Button (Offenes Ticket) +```dart +SizedBox( + width: double.infinity, + child: FilledButton.icon( + onPressed: _closeTicket, + icon: Icon(Icons.lock), + label: Text('Close Ticket'), + style: FilledButton.styleFrom( + backgroundColor: Colors.red[700], + padding: EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), +) +``` + +**Visuell:** +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ”’ Close Ticket โ”‚ โ† Roter Button +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +#### Reopen Button (Geschlossenes Ticket) +```dart +Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Reopen Counter Badge + if (ticket.reopenCount > 0) + Container( + padding: EdgeInsets.all(12), + margin: EdgeInsets.only(bottom: 12), + decoration: BoxDecoration( + color: Colors.orange[50], + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.orange[300]!), + ), + child: Row( + children: [ + Icon(Icons.info_outline, color: Colors.orange[700]), + SizedBox(width: 8), + Text( + 'Reopened ${ticket.reopenCount}/3 times', + style: TextStyle(color: Colors.orange[900]), + ), + ], + ), + ), + + // Reopen Button + FilledButton.icon( + onPressed: ticket.reopenCount < 3 ? _reopenTicket : null, + icon: Icon(Icons.lock_open), + label: Text( + ticket.reopenCount < 3 + ? 'Reopen Ticket (${3 - ticket.reopenCount} left)' + : 'Reopen Limit Reached', + ), + style: FilledButton.styleFrom( + backgroundColor: Colors.blue[700], + padding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ], +) +``` + +**Visuell (1 Reopen):** +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โ“˜ Reopened 1/3 times โ”‚ โ† Orange Info Box +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ”“ Reopen Ticket (2 left) โ”‚ โ† Blauer Button +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +**Visuell (Limit erreicht):** +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ โš ๏ธ Reopen limit reached (3/3) โ”‚ โ† Rote Warning Box +โ”‚ โ”‚ +โ”‚ You cannot reopen this ticket again.โ”‚ +โ”‚ Please create a new ticket if you โ”‚ +โ”‚ still need assistance. โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ๐Ÿ”“ Reopen Limit Reached โ”‚ โ† Disabled (grau) +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๐Ÿ’ฌ Dialogs + +### Close Ticket Dialog +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Close Ticket ร— โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ Add an optional closing message: โ”‚ +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚ +โ”‚ โ”‚ Thank you for reporting this. โ”‚โ”‚ +โ”‚ โ”‚ The issue has been resolved. โ”‚โ”‚ +โ”‚ โ”‚ โ”‚โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚ +โ”‚ 0/500 characters โ”‚ +โ”‚ โ”‚ +โ”‚ [Cancel] [Close] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Reopen Confirmation Dialog +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Reopen Ticket ร— โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ Do you want to reopen this โ”‚ +โ”‚ ticket? โ”‚ +โ”‚ โ”‚ +โ”‚ Reopens remaining: 2/3 โ”‚ +โ”‚ โ”‚ +โ”‚ The ticket will be reopened and โ”‚ +โ”‚ you can continue the conversation.โ”‚ +โ”‚ โ”‚ +โ”‚ [Cancel] [Reopen] โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +## ๐ŸŽฌ User Flow + +### Close Ticket Flow +``` +1. User รถffnet eigenes Ticket + โ†’ Status: Open + โ†’ Sieht "๐Ÿ”’ Close Ticket" Button + +2. User klickt "Close Ticket" + โ†’ Dialog รถffnet sich + โ†’ Optional: Close Message eingeben + +3. User klickt "Close" + โ†’ Loading Indicator + โ†’ API Call /api/tickets/{id}/close + +4. Success + โ†’ SnackBar: "โœ… Ticket closed successfully" + โ†’ Navigate back zur Ticket-Liste + โ†’ Ticket Status jetzt "Closed" +``` + +### Reopen Ticket Flow +``` +1. User รถffnet eigenes geschlossenes Ticket + โ†’ Status: Closed + โ†’ Sieht "๐Ÿ”“ Reopen Ticket (3 left)" Button + โ†’ Sieht Reopen Counter wenn > 0 + +2. User klickt "Reopen Ticket" + โ†’ Confirmation Dialog + โ†’ Zeigt verbleibende Reopens + +3. User bestรคtigt + โ†’ Loading Indicator + โ†’ API Call /api/tickets/{id}/reopen + +4. Success + โ†’ SnackBar: "โœ… Ticket reopened successfully" + โ†’ Navigate back zur Ticket-Liste + โ†’ Ticket Status jetzt "Open" +``` + +--- + +## ๐Ÿ“Š State Diagram + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ TICKET STATE โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ User opens ticket + โ†“ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ OPEN โ”‚ โ† Shows: Close Button + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ User clicks Close + โ†“ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ CLOSED โ”‚ โ† Shows: Reopen Button (if < 3) + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ User clicks Reopen + โ”‚ (reopen_count++) + โ†“ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ OPEN โ”‚ โ† Shows: Close Button + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + "Reopened 1/3 times" badge + โ”‚ + โ”‚ ... repeat up to 3x ... + โ†“ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ CLOSED โ”‚ โ† Shows: Disabled message + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ "Reopen limit reached (3/3)" + Cannot reopen anymore +``` + +--- + +## ๐ŸŽจ Color Scheme + +### Close Button +- Background: `Colors.red[700]` (Rot) +- Icon: `Icons.lock` +- Text: "Close Ticket" + +### Reopen Button +- Background: `Colors.blue[700]` (Blau) +- Icon: `Icons.lock_open` +- Text: "Reopen Ticket (X left)" + +### Reopen Counter Badge +- Background: `Colors.orange[50]` +- Border: `Colors.orange[300]` +- Text: `Colors.orange[900]` +- Icon: `Icons.info_outline` + +### Warning (Limit Reached) +- Background: `Colors.red[50]` +- Border: `Colors.red[300]` +- Text: `Colors.red[900]` +- Icon: `Icons.warning_amber` + +--- + +## ๐Ÿ” Responsive Design + +### Mobile (< 600dp) +- Buttons: Full width +- Padding: 16px +- Button height: 48dp (Material touch target) + +### Tablet/Desktop (> 600dp) +- Buttons: Full width (in ticket detail) +- Max width kรถnnte begrenzt werden: 400px centered + +--- + +## โœ… Warum Option 1 (Buttons unter Chat)? + +1. **Prominent:** Immer sichtbar ohne scrollen +2. **Nicht stรถrend:** รœberdeckt keinen Chat-Inhalt +3. **Konsistent:** ร„hnlich wie Admin Dialog Layout +4. **Klar getrennt:** Eigener Container = klare Action Section +5. **Mobile-friendly:** Leicht erreichbar am unteren Bildschirmrand +6. **Flexibel:** Platz fรผr zusรคtzliche Info-Boxen (Reopen Counter) + +--- + +## ๐Ÿ“ฑ Screenshot-ร„hnliche Darstellung + +### Offenes Ticket auf Mobile +``` +โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— +โ•‘ โ† Ticket #123 โ•‘ +โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ ๐Ÿ“‹ Support ๐ŸŸข Open โ•‘ +โ•‘ โ•‘ +โ•‘ โ„น๏ธ Subject: Cannot login โ–ผ โ•‘ +โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ โ•‘ +โ•‘ Admin: How can I help you? โ•‘ +โ•‘ โ”—โ” 2 hours ago โ•‘ +โ•‘ โ•‘ +โ•‘ You: I forgot my password โ•‘ +โ•‘ โ”—โ” 1 hour ago โ•‘ +โ•‘ โ•‘ +โ•‘ Admin: I've reset it, check email โ•‘ +โ•‘ โ”—โ” 30 minutes ago โ•‘ +โ•‘ โ•‘ +โ•‘ [Type your message here... Send] โ•‘ +โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ•‘ +โ•‘ โ”ƒ ๐Ÿ”’ Close Ticket โ”ƒ โ•‘ +โ•‘ โ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”› โ•‘ +โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +``` + +### Geschlossenes Ticket (1x Reopened) +``` +โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— +โ•‘ โ† Ticket #123 โ•‘ +โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ ๐Ÿ“‹ Support ๐Ÿ”ด Closed โ•‘ +โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ โ•‘ +โ•‘ Admin: Issue resolved! โ•‘ +โ•‘ โ”—โ” 1 day ago โ•‘ +โ•‘ โ•‘ +โ•‘ [This ticket has been closed] โ•‘ +โ•‘ โ•‘ +โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ•‘ +โ•‘ โ”ƒ โ“˜ Reopened 1/3 times โ”ƒ โ•‘ +โ•‘ โ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”› โ•‘ +โ•‘ โ•‘ +โ•‘ โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“ โ•‘ +โ•‘ โ”ƒ ๐Ÿ”“ Reopen Ticket (2 left) โ”ƒ โ•‘ +โ•‘ โ”—โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”› โ•‘ +โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• +``` + +**Perfekt klar und prominent platziert!** โœ… diff --git a/TICKET_USER_ACTIONS_PLAN.md b/TICKET_USER_ACTIONS_PLAN.md new file mode 100644 index 0000000..3ed10fb --- /dev/null +++ b/TICKET_USER_ACTIONS_PLAN.md @@ -0,0 +1,339 @@ +# Plan: User Ticket Close/Reopen Funktionen + +## ๐Ÿ“‹ Zusammenfassung + +Normale User sollen ihre eigenen Tickets closen und wieder รถffnen kรถnnen (begrenzt auf 3x Reopen). + +## โœ… Was bereits vorhanden ist + +### Backend (HazeBot API) +1. **API Endpoints existieren bereits:** + - `POST /api/tickets//close` - Ticket schlieรŸen + - `POST /api/tickets//reopen` - Ticket wieder รถffnen + +2. **Berechtigungen bereits korrekt:** + - `is_allowed_for_ticket_actions()` in `Cogs/TicketSystem.py` Zeile 98: + - **Close**: Creator, Admins oder Moderators โœ… + - **Reopen**: Creator, Admins oder Moderators โœ… + - Reopen limitiert auf 3 Mal (Zeile 868) + +3. **API Permission Decorators:** + - `close_ticket_endpoint`: `require_permission("all")` - bedeutet alle authentifizierten User โœ… + - `reopen_ticket_endpoint`: `require_permission("all")` - bedeutet alle authentifizierten User โœ… + +### Discord Bot +- User kรถnnen im Discord bereits ihre Tickets closen/reopenen via Buttons +- Reopen Count wird getrackt und auf 3 limitiert + +### Frontend Admin Screen +- `lib/screens/admin/ticket_detail_dialog.dart` hat bereits: + - `_closeTicket()` Methode (Zeile 334) + - `_reopenTicket()` Methode (Zeile 378) + - UI Buttons fรผr beide Aktionen + - Close mit optionaler Message + +### Frontend API Service +- `lib/services/api_service.dart` hat bereits: + - `closeTicket(String ticketId, {String? closeMessage})` (Zeile 1598) + - `reopenTicket(String ticketId)` (Zeile 1610) + +## ๐Ÿšง Was fehlt + +### User Tickets Screen +Die Datei `lib/screens/user/tickets_screen.dart` zeigt nur: +- Liste der eigenen Tickets (My Tickets Tab) +- Create New Ticket Tab +- `_TicketDetailScreen` Widget (Zeile 892) - zeigt nur Chat, **KEINE** Action Buttons + +**Problem:** User sehen zwar ihre Tickets und kรถnnen Nachrichten schreiben, aber haben keine Buttons zum Close/Reopen. + +## ๐Ÿ“ Implementierungsplan + +### Phase 1: User Ticket Detail Screen erweitern + +**Datei:** `lib/screens/user/tickets_screen.dart` + +#### 1.1 _TicketDetailScreen zu StatefulWidget รคndern +- Aktuell: StatelessWidget (Zeile 892) +- Neu: StatefulWidget mit State Management + +#### 1.2 Action Buttons hinzufรผgen +**Fรผr offene Tickets (Status: 'Open' oder 'Claimed'):** +```dart +// Close Button +FilledButton.icon( + onPressed: _closeTicket, + icon: Icon(Icons.lock), + label: Text('Close Ticket'), + style: FilledButton.styleFrom( + backgroundColor: Colors.red, + ), +) +``` + +**Fรผr geschlossene Tickets (Status: 'Closed'):** +```dart +// Reopen Button (nur wenn reopen_count < 3) +if (ticket.reopenCount < 3) + FilledButton.icon( + onPressed: _reopenTicket, + icon: Icon(Icons.lock_open), + label: Text('Reopen Ticket'), + ) + +// Reopen Counter anzeigen +if (ticket.reopenCount > 0) + Text('Reopened ${ticket.reopenCount}/3 times') +``` + +#### 1.3 Methoden implementieren + +**_closeTicket():** +```dart +Future _closeTicket() async { + // 1. Optional: Close Message Dialog (wie im Admin Screen) + final closeMessage = await showDialog( + context: context, + builder: (context) => _CloseMessageDialog(), + ); + + if (closeMessage == null) return; + + // 2. API Call + await ApiService().closeTicket( + ticket.ticketId, + closeMessage: closeMessage.isEmpty ? null : closeMessage, + ); + + // 3. Refresh & Navigate back + if (mounted) { + Navigator.pop(context); // Zurรผck zur Ticket-Liste + // Ticket-Liste wird automatisch refreshed + } +} +``` + +**_reopenTicket():** +```dart +Future _reopenTicket() async { + // 1. Confirmation Dialog + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Reopen Ticket'), + content: Text( + 'Do you want to reopen this ticket?\n' + 'Reopens remaining: ${3 - ticket.reopenCount}/3' + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: Text('Cancel'), + ), + FilledButton( + onPressed: () => Navigator.pop(context, true), + child: Text('Reopen'), + ), + ], + ), + ); + + if (confirmed != true) return; + + // 2. API Call + await ApiService().reopenTicket(ticket.ticketId); + + // 3. Refresh & Navigate back + if (mounted) { + Navigator.pop(context); + } +} +``` + +#### 1.4 _CloseMessageDialog Widget +- Copy from `lib/screens/admin/ticket_detail_dialog.dart` (Zeile 1326) +- Optionale Nachricht beim SchlieรŸen +- Max 500 Zeichen + +### Phase 2: Ticket Model erweitern (falls nรถtig) + +**Datei:** `lib/models/ticket.dart` + +Prรผfen ob `reopenCount` bereits im Model ist: +```dart +final int reopenCount; // Anzahl wie oft reopened +``` + +Falls nicht vorhanden: hinzufรผgen. + +### Phase 3: UI/UX Verbesserungen + +#### 3.1 Status Badge in Ticket Liste +- Zeige visuell ob Ticket offen/geschlossen ist +- Farbcodierung: Grรผn (Open), Orange (Claimed), Rot (Closed) + +#### 3.2 Reopen Counter +- Zeige in der Ticket-Detail-Ansicht wie oft noch reopened werden kann +- Warning wenn Limit erreicht: "Cannot reopen again (limit reached)" + +#### 3.3 Loading States +- Loading Indicator wรคhrend API Calls +- Disable Buttons wรคhrend Verarbeitung + +#### 3.4 Error Handling +- SnackBar bei Fehlern +- Spezifische Fehlermeldungen: + - "Ticket already closed" + - "Ticket cannot be reopened more than 3 times" + - "Network error" + +### Phase 4: Testing + +#### 4.1 Test Cases +1. **Close Ticket:** + - โœ… User kann eigenes offenes Ticket closen + - โœ… Close Message wird gespeichert + - โœ… Ticket Status wird auf "Closed" gesetzt + - โœ… User kann nach Close keine Nachrichten mehr senden + +2. **Reopen Ticket:** + - โœ… User kann eigenes geschlossenes Ticket reopenen + - โœ… Reopen Count wird inkrementiert + - โœ… Nach 3x Reopen: Button disabled + Warning + - โœ… Nach Reopen kann User wieder Nachrichten senden + +3. **Berechtigungen:** + - โŒ User kann NICHT Tickets von anderen Usern closen/reopenen + - โœ… Admin kann alle Tickets closen/reopenen (bereits vorhanden) + +4. **UI:** + - โœ… Buttons erscheinen zur richtigen Zeit + - โœ… Loading States funktionieren + - โœ… Error Messages werden angezeigt + - โœ… Navigation funktioniert + +## ๐Ÿ“‚ Betroffene Dateien + +### Zu รคndern: +1. โœ๏ธ `lib/screens/user/tickets_screen.dart` - Hauptรคnderung + - `_TicketDetailScreen` zu StatefulWidget + - Action Buttons hinzufรผgen + - `_closeTicket()` und `_reopenTicket()` Methoden + - `_CloseMessageDialog` Widget + +### Ggf. zu รคndern: +2. โœ๏ธ `lib/models/ticket.dart` - Falls `reopenCount` fehlt + +### Bereits vorhanden (keine ร„nderung): +3. โœ… `lib/services/api_service.dart` - API Calls vorhanden +4. โœ… `HazeBot/api/ticket_routes.py` - Backend Endpoints vorhanden +5. โœ… `HazeBot/Cogs/TicketSystem.py` - Berechtigungen korrekt + +## โฑ๏ธ Aufwandsschรคtzung + +**Gesamt: ~2-3 Stunden** + +- Phase 1: Widget Refactoring & UI: **1-1.5h** + - StatefulWidget Conversion: 15min + - Action Buttons UI: 30min + - `_closeTicket()` Methode: 20min + - `_reopenTicket()` Methode: 20min + - `_CloseMessageDialog`: 10min (Copy & Adapt) + +- Phase 2: Model Check: **10min** + - Prรผfen ob `reopenCount` vorhanden + - Falls nรถtig hinzufรผgen + +- Phase 3: UI/UX Polish: **30-45min** + - Status Badges + - Loading States + - Error Handling + - Reopen Counter Display + +- Phase 4: Testing: **30min** + - Manuelle Tests + - Edge Cases prรผfen + +## โœ… Vorteile dieser Implementierung + +1. **Minimal Invasiv:** Nur Frontend-ร„nderungen nรถtig +2. **Backend Ready:** API Endpoints bereits vorhanden und getestet +3. **Konsistent:** Nutzt gleiche API Calls wie Admin Screen +4. **Sicher:** Berechtigungen werden auf Backend geprรผft +5. **User Experience:** User kรถnnen Tickets selbst verwalten ohne Admin/Mod + +## ๐Ÿ”’ Sicherheit + +- โœ… Backend validiert User ID vs. Ticket Creator ID +- โœ… Reopen Limit wird auf Backend erzwungen +- โœ… JWT Token erforderlich fรผr API Calls +- โœ… Keine zusรคtzlichen Security Concerns + +## ๐Ÿ“ฑ UI Mockup (Textbeschreibung) + +### Offenes Ticket (_TicketDetailScreen): +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Ticket #123 โ”‚ +โ”‚ Status: Open ๐ŸŸข โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ [Chat Messages hier] โ”‚ +โ”‚ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [Message Input Field] โ”‚ +โ”‚ [Send Button] โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [๐Ÿ”’ Close Ticket] (Red) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Geschlossenes Ticket (_TicketDetailScreen): +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Ticket #123 โ”‚ +โ”‚ Status: Closed ๐Ÿ”ด โ”‚ +โ”‚ Reopened 1/3 times โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ [Chat History (read-only)] โ”‚ +โ”‚ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ [๐Ÿ”“ Reopen Ticket] (Blue) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### Geschlossenes Ticket (Limit erreicht): +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Ticket #123 โ”‚ +โ”‚ Status: Closed ๐Ÿ”ด โ”‚ +โ”‚ โš ๏ธ Reopen limit reached โ”‚ +โ”‚ (3/3 reopens used) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ [Chat History (read-only)] โ”‚ +โ”‚ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ„น๏ธ Cannot reopen again โ”‚ +โ”‚ Please create a new ticket โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## ๐ŸŽฏ Nรคchste Schritte + +1. โœ… Plan Review (Done - dieser Plan) +2. โณ `lib/models/ticket.dart` prรผfen auf `reopenCount` +3. โณ `_TicketDetailScreen` zu StatefulWidget refactoren +4. โณ Action Buttons implementieren +5. โณ `_closeTicket()` und `_reopenTicket()` Methoden +6. โณ UI/UX Polish +7. โณ Testing +8. โณ Dokumentation Update + +## ๐Ÿ’ก Hinweise + +- Die Admin Screen Implementierung kann als Referenz dienen +- Error Handling sollte spezifisch sein (z.B. "Already closed", "Reopen limit") +- Loading States wichtig fรผr User Feedback +- Reopen Counter prominent anzeigen wenn > 0 diff --git a/APK_DOWNLOAD.md b/docs/APK_DOWNLOAD.md similarity index 87% rename from APK_DOWNLOAD.md rename to docs/APK_DOWNLOAD.md index 6d41ea3..f32de64 100644 --- a/APK_DOWNLOAD.md +++ b/docs/APK_DOWNLOAD.md @@ -104,8 +104,8 @@ Choose download option above and save APK to your device. ### API URL Setup **Pre-configured APKs:** -- Test builds: `https://test-hazebot-admin.hzwd.xyz/api` -- Production builds: Your production API URL +- Test builds: Connect to your test/staging API +- Production builds: Connect to your production API URL **Manual Configuration:** 1. Open app @@ -115,8 +115,8 @@ Choose download option above and save APK to your device. 5. Save and return to login **API URL Examples:** -- **Local Testing:** `http://10.0.2.2:5070/api` (emulator) -- **LAN Testing:** `http://192.168.1.100:5070/api` (physical device) +- **Local Testing (Emulator):** `http://10.0.2.2:5070/api` +- **Local Testing (Device):** `http://YOUR_LOCAL_IP:5070/api` (find with `ip addr` or `ifconfig`) - **Production:** `https://your-domain.com/api` ### API Configuration @@ -249,15 +249,26 @@ Creates versioned release alongside automated builds. --- +## ๐Ÿ”— Next Steps + +After installing: +- ๐Ÿ”ง [Setup Checklist](SETUP_CHECKLIST.md) - Complete setup verification +- ๐Ÿ”ฅ [Firebase Setup](FIREBASE_SETUP.md) - Enable push notifications (optional) +- ๐Ÿ“‹ [Features](FEATURES.md) - Explore what the app can do +- ๐Ÿ  [Documentation Index](README.md) - All documentation + +--- + ## ๐Ÿ†˜ Support **Problems?** Open an issue: -- GitHub Issues: https://github.com/inventory69/HazeBot-Admin/issues +- GitHub Issues: `https://github.com/YOUR_USERNAME/HazeBot-Admin/issues` - Include: Android version, APK version, error messages **Questions?** -- Check [README.md](README.md) for full documentation -- Review [SETUP_CHECKLIST.md](SETUP_CHECKLIST.md) +- Check [Documentation Index](README.md) for all guides +- Review [Setup Checklist](SETUP_CHECKLIST.md) for configuration +- See [Troubleshooting section](#-troubleshooting) above --- diff --git a/BUILDING.md b/docs/BUILDING.md similarity index 72% rename from BUILDING.md rename to docs/BUILDING.md index 07e4978..977a0f2 100644 --- a/BUILDING.md +++ b/docs/BUILDING.md @@ -26,13 +26,11 @@ flutter build web --release --pwa-strategy=none ### Test Locally ```bash -# Navigate to build directory -cd build/web - -# Start local server (port 8000) -python3 ../../../spa_server.py +# Start local server (port 8080) +python3 scripts/spa_server.py --dir build/web --port 8080 -# Or use Python's built-in server +# Or navigate to build directory and use Python's built-in server +cd build/web python3 -m http.server 8000 ``` @@ -343,9 +341,77 @@ Run: `./build.sh` --- -## Next Steps +## ๏ฟฝ๏ธ Build Scripts + +The `scripts/` directory contains helper scripts for building and development: + +### spa_server.py +**Purpose:** Minimal SPA (Single Page Application) server for testing web builds locally. + +**Usage:** +```bash +# Serve web build +python3 scripts/spa_server.py --dir build/web --port 8080 + +# Custom directory and port +python3 scripts/spa_server.py --dir path/to/build --port 3000 +``` + +**Features:** +- Serves static files from build directory +- Falls back to `index.html` for client-side routing +- Perfect for testing web builds before deployment + +### generate_adaptive_icons.sh +**Purpose:** Generate Android adaptive launcher icons from a source PNG. + +**Requirements:** +- ImageMagick (`sudo apt-get install imagemagick`) +- Source image: `app_icon_source.png` in project root + +**Usage:** +```bash +# Run from project root +./scripts/generate_adaptive_icons.sh +``` + +**Output:** +- Foreground/background layers for all densities (mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi) +- XML descriptors in `mipmap-anydpi-v26/` +- Adaptive icons for Android 8.0+ (API 26) + +### create_notification_icon.sh +**Purpose:** Create monochrome notification icons for Android status bar. + +**Requirements:** +- ImageMagick +- Existing launcher icon in `android/app/src/main/res/mipmap-xxxhdpi/` + +**Usage:** +```bash +# Run from project root +./scripts/create_notification_icon.sh +``` + +**Output:** +- White monochrome icons in `drawable-*/` directories +- All densities: 24dp (mdpi) to 96dp (xxxhdpi) +- Ready for Android notification system + +--- + +## ๏ฟฝ๐Ÿ”— Next Steps + +- ๐Ÿ“ฑ [APK Installation Guide](APK_DOWNLOAD.md) - Install built APKs +- ๐Ÿ”ฅ [Firebase Setup](FIREBASE_SETUP.md) - Configure push notifications +- ๐Ÿš€ [GitHub Actions](GITHUB_ACTIONS.md) - Automated CI/CD builds +- ๐Ÿงช [Development Guide](DEVELOPMENT.md) - Development workflows +- ๐Ÿ  [Documentation Index](README.md) - All documentation + +--- + +## ๐Ÿ†˜ Getting Help -- ๐Ÿ“ฑ [APK Installation Guide](APK_DOWNLOAD.md) -- ๐Ÿ”ฅ [Firebase Setup](FIREBASE_SETUP.md) -- ๐Ÿš€ [GitHub Actions CI/CD](GITHUB_ACTIONS.md) -- ๐Ÿ  [Back to README](README.md) +- **Build Issues:** Check troubleshooting section above +- **Platform-Specific:** Review [Flutter Documentation](https://docs.flutter.dev/deployment) +- **Questions:** Open an issue on [GitHub](https://github.com/YOUR_USERNAME/HazeBot-Admin/issues) diff --git a/CHANGELOG.md b/docs/CHANGELOG.md similarity index 88% rename from CHANGELOG.md rename to docs/CHANGELOG.md index bf38a6a..98fba06 100644 --- a/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -98,6 +98,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Contributing -See [CONTRIBUTING.md](CONTRIBUTING.md) for details on contributing to this project. +Report bugs and request features via [GitHub Issues](https://github.com/YOUR_USERNAME/HazeBot-Admin/issues). -Report bugs and request features via [GitHub Issues](https://github.com/inventory69/HazeBot-Admin/issues). +See the main [README](../README.md#-contributing) for contribution guidelines. + +--- + +## ๐Ÿ”— Documentation + +- ๐Ÿ“– [Documentation Index](README.md) - All documentation +- ๐Ÿ“‹ [Features](FEATURES.md) - Complete feature list +- ๐Ÿ”จ [Building](BUILDING.md) - Build instructions +- ๐Ÿ  [Main README](../README.md) - Project overview diff --git a/DEVELOPMENT.md b/docs/DEVELOPMENT.md similarity index 92% rename from DEVELOPMENT.md rename to docs/DEVELOPMENT.md index 219ab68..05f0c6b 100644 --- a/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -574,8 +574,22 @@ docs: update setup instructions --- -## Next Steps +## ๐Ÿ”— Next Steps -- ๐Ÿ  [Back to README](README.md) -- ๐Ÿ”จ [Building Guide](BUILDING.md) -- ๐Ÿ—๏ธ [Architecture](../HazeBot/ARCHITECTURE.md) +- ๐Ÿ”จ [Building Guide](BUILDING.md) - Build for all platforms +- ๐Ÿ“‹ [Features](FEATURES.md) - Understand existing features +- ๐Ÿ”ฅ [Firebase Setup](FIREBASE_SETUP.md) - Configure notifications +- ๐Ÿ  [Documentation Index](README.md) - All documentation + +**Related Resources:** +- ๐Ÿค– [HazeBot Backend](https://github.com/inventory69/HazeBot) - Bot & API server +- ๐Ÿ“– [HazeBot API](https://github.com/inventory69/HazeBot/blob/main/api/README.md) - REST API reference +- ๐Ÿ—๏ธ [Flutter Documentation](https://docs.flutter.dev) - Official Flutter guides + +--- + +## ๐Ÿ†˜ Getting Help + +- **Development Issues:** Check troubleshooting section above +- **Flutter Questions:** [Flutter Documentation](https://docs.flutter.dev) +- **Project Questions:** Open an issue on [GitHub](https://github.com/YOUR_USERNAME/HazeBot-Admin/issues) diff --git a/FEATURES.md b/docs/FEATURES.md similarity index 100% rename from FEATURES.md rename to docs/FEATURES.md diff --git a/FIREBASE_SETUP.md b/docs/FIREBASE_SETUP.md similarity index 87% rename from FIREBASE_SETUP.md rename to docs/FIREBASE_SETUP.md index 3479334..2ddd69d 100644 --- a/FIREBASE_SETUP.md +++ b/docs/FIREBASE_SETUP.md @@ -206,4 +206,21 @@ HazeBot-Admin/ --- -For more info, see [Firebase Cloud Messaging Docs](https://firebase.google.com/docs/cloud-messaging) +## ๐Ÿ”— Next Steps + +- ๐Ÿ”จ [Building Guide](BUILDING.md) - Build for all platforms with Firebase +- ๐Ÿงช [Development Guide](DEVELOPMENT.md) - Development workflows +- ๐Ÿ  [Documentation Index](README.md) - All documentation + +**Related Resources:** +- [Firebase Cloud Messaging Docs](https://firebase.google.com/docs/cloud-messaging) - Official FCM documentation +- [Flutter Firebase Messaging](https://pub.dev/packages/firebase_messaging) - Flutter plugin docs +- ๐Ÿค– [HazeBot Backend](https://github.com/inventory69/HazeBot) - Backend notification setup + +--- + +## ๐Ÿ†˜ Getting Help + +- **Firebase Issues:** Check troubleshooting section above +- **Flutter Questions:** [Flutter Firebase Setup](https://firebase.google.com/docs/flutter/setup) +- **Project Questions:** Open an issue on [GitHub](https://github.com/YOUR_USERNAME/HazeBot-Admin/issues) diff --git a/GITHUB_ACTIONS.md b/docs/GITHUB_ACTIONS.md similarity index 92% rename from GITHUB_ACTIONS.md rename to docs/GITHUB_ACTIONS.md index 7fd61af..d3fc16f 100644 --- a/GITHUB_ACTIONS.md +++ b/docs/GITHUB_ACTIONS.md @@ -48,7 +48,7 @@ The workflow requires secrets for configuration. Add them in your repository: 3. Value: e.g., `true` 4. Click **Add secret** -๐Ÿ“š **Detailed Setup Guide:** See [GITHUB_SECRETS_SETUP.md](docs/GITHUB_SECRETS_SETUP.md) +๐Ÿ“š **Detailed Setup Guide:** See [GITHUB_SECRETS_SETUP.md](GITHUB_SECRETS_SETUP.md) --- @@ -436,6 +436,12 @@ VERSION_TAG="v$(date +'%Y.%m.%d')-build-${{ github.run_number }}" ## ๐Ÿ“š Additional Resources +**Related Documentation:** +- [GITHUB_SECRETS_SETUP.md](GITHUB_SECRETS_SETUP.md) - Configure repository secrets +- [BUILDING.md](BUILDING.md) - Local build instructions +- [APK_DOWNLOAD.md](APK_DOWNLOAD.md) - Download and install guide +- [Documentation Index](README.md) - All documentation + **GitHub Actions Documentation:** - [Workflow Syntax](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions) - [Secrets Management](https://docs.github.com/en/actions/security-guides/encrypted-secrets) @@ -467,15 +473,25 @@ jobs: --- +## ๐Ÿ”— Next Steps + +- ๐Ÿ“ฑ [APK Download Guide](APK_DOWNLOAD.md) - Install the built APK +- ๐Ÿ”‘ [GitHub Secrets Setup](GITHUB_SECRETS_SETUP.md) - Configure secrets +- ๐Ÿ”จ [Building Guide](BUILDING.md) - Local build instructions +- ๐Ÿ  [Documentation Index](README.md) - All documentation + +--- + ## ๐Ÿ†˜ Support **Issues?** -- GitHub Issues: https://github.com/inventory69/HazeBot-Admin/issues +- GitHub Issues: `https://github.com/YOUR_USERNAME/HazeBot-Admin/issues` - Include workflow run URL and error logs **Questions?** -- Review [README.md](README.md) +- Review [Documentation Index](README.md) - Check [APK_DOWNLOAD.md](APK_DOWNLOAD.md) +- See [GITHUB_SECRETS_SETUP.md](GITHUB_SECRETS_SETUP.md) --- diff --git a/docs/GITHUB_SECRETS_SETUP.md b/docs/GITHUB_SECRETS_SETUP.md index fb3a405..4cd2f70 100644 --- a/docs/GITHUB_SECRETS_SETUP.md +++ b/docs/GITHUB_SECRETS_SETUP.md @@ -34,7 +34,7 @@ Die GitHub Actions Workflow benรถtigt folgende Secrets, um die App korrekt zu ba ## โš™๏ธ Secrets hinzufรผgen ### Schritt 1: GitHub Repository รถffnen -1. Gehe zu deinem Repository: https://github.com/inventory69/HazeBot-Admin +1. Gehe zu deinem Repository: `https://github.com/YOUR_USERNAME/HazeBot-Admin` 2. Klicke auf **Settings** (Zahnrad-Symbol) 3. In der linken Sidebar: **Secrets and variables** โ†’ **Actions** @@ -151,6 +151,7 @@ PROD_MODE=false # Verwende false fรผr lokale Entwicklung ## ๐Ÿ“š Siehe auch -- [build-apk.yml](.github/workflows/build-apk.yml) - GitHub Actions Workflow -- [app_config.dart](lib/utils/app_config.dart) - PROD_MODE Logik -- [.env.example](.env.example) - Environment Variables Beispiel +- [GITHUB_ACTIONS.md](GITHUB_ACTIONS.md) - Vollstรคndige CI/CD Dokumentation +- [BUILDING.md](BUILDING.md) - Build-Anweisungen +- [SETUP_CHECKLIST.md](SETUP_CHECKLIST.md) - Setup-Checkliste +- [Documentation Index](README.md) - Alle Dokumentationen diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..01e9485 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,102 @@ +# ๐Ÿ“š HazeBot Admin Documentation + +Welcome to the comprehensive documentation for HazeBot Admin - a modern, cross-platform Flutter admin panel for HazeBot. + +## ๐Ÿ“– Documentation Index + +### ๐Ÿš€ Getting Started + +| Document | Description | +|----------|-------------| +| [Setup Checklist](SETUP_CHECKLIST.md) | Quick setup verification guide | +| [APK Download](APK_DOWNLOAD.md) | Android APK installation instructions | +| [Firebase Setup](FIREBASE_SETUP.md) | Push notification configuration | + +### ๐Ÿ’ป Development + +| Document | Description | +|----------|-------------| +| [Development Guide](DEVELOPMENT.md) | Development workflows, patterns, and best practices | +| [Building Guide](BUILDING.md) | Build instructions for Web, Android, iOS, and Desktop | + +### ๐Ÿšข Deployment + +| Document | Description | +|----------|-------------| +| [GitHub Actions](GITHUB_ACTIONS.md) | CI/CD pipeline documentation | +| [GitHub Secrets](GITHUB_SECRETS_SETUP.md) | Repository secrets configuration | + +### ๐Ÿ“‹ Reference + +| Document | Description | +|----------|-------------| +| [Features](FEATURES.md) | Complete feature documentation | +| [Changelog](CHANGELOG.md) | Version history and release notes | + +--- + +## ๐ŸŽฏ Quick Navigation by Use Case + +### I want to... + +#### **Set up the app for the first time** +1. Start with [Setup Checklist](SETUP_CHECKLIST.md) for prerequisites +2. Follow the [Main README](../README.md#-quick-start) for installation +3. Configure [Firebase Setup](FIREBASE_SETUP.md) for push notifications + +#### **Install the Android app** +- Download APK using [APK Download Guide](APK_DOWNLOAD.md) +- Use Obtainium for automatic updates + +#### **Develop new features** +1. Read [Development Guide](DEVELOPMENT.md) for workflows and patterns +2. Check [Features](FEATURES.md) to understand existing features +3. Review [Building Guide](BUILDING.md) for platform-specific builds + +#### **Build for production** +1. Follow [Building Guide](BUILDING.md) for your target platform +2. Configure [GitHub Actions](GITHUB_ACTIONS.md) for automated builds +3. Set up [GitHub Secrets](GITHUB_SECRETS_SETUP.md) for CI/CD + +#### **Understand what the app can do** +- Browse [Features](FEATURES.md) for complete feature list +- Check [Changelog](CHANGELOG.md) for recent additions + +--- + +## ๐Ÿ”— Related Documentation + +- **[HazeBot Repository](https://github.com/inventory69/HazeBot)** - Main bot and API server +- **[HazeBot Documentation](https://github.com/inventory69/HazeBot/blob/main/docs/README.md)** - Backend documentation +- **[HazeBot API](https://github.com/inventory69/HazeBot/blob/main/api/README.md)** - REST API reference + +--- + +## ๐Ÿ“ฑ Platform Support + +| Platform | Status | Notes | +|----------|--------|-------| +| **Web** | โœ… Primary | Best experience, recommended for desktop | +| **Android** | โœ… Supported | APK releases available | +| **iOS** | โš ๏ธ Untested | Should work, needs signing | +| **Linux** | โœ… Supported | Desktop build available | +| **Windows** | โš ๏ธ Untested | Should work with Flutter 3.0+ | +| **macOS** | โš ๏ธ Untested | Should work, needs signing | + +--- + +## ๐Ÿ†˜ Getting Help + +1. **Check Documentation** - Search this documentation first +2. **GitHub Issues** - [Open an issue](https://github.com/inventory69/HazeBot-Admin/issues) +3. **HazeBot Backend** - Some issues may be API-related, check [HazeBot docs](https://github.com/inventory69/HazeBot/blob/main/docs/README.md) + +--- + +## ๐Ÿค Contributing + +See the main [README](../README.md#-contributing) for contribution guidelines. + +--- + +*Made with ๐Ÿ’– for The Chillventory* ๐Ÿ“ฑโœจ diff --git a/SETUP_CHECKLIST.md b/docs/SETUP_CHECKLIST.md similarity index 94% rename from SETUP_CHECKLIST.md rename to docs/SETUP_CHECKLIST.md index 4c88dae..19823a1 100644 --- a/SETUP_CHECKLIST.md +++ b/docs/SETUP_CHECKLIST.md @@ -324,11 +324,11 @@ Output: `build/web/` **Deploy to:** - Static hosting (Netlify, Vercel, GitHub Pages) - Own server with nginx/Apache -- Use `spa_server.py` for local testing +- Use `python3 scripts/spa_server.py --dir build/web` for local testing - [ ] Web build successful - [ ] Static files generated -- [ ] Tested locally with `spa_server.py` +- [ ] Tested locally with `python3 scripts/spa_server.py --dir build/web` - [ ] Deployed to hosting ### Android APK @@ -617,9 +617,16 @@ Once all items are checked: --- **Need Help?** -- GitHub Issues: https://github.com/inventory69/HazeBot-Admin/issues -- Documentation: [README.md](README.md) -- Backend Setup: [HazeBot README](https://github.com/inventory69/HazeBot) +- GitHub Issues: `https://github.com/YOUR_USERNAME/HazeBot-Admin/issues` +- Documentation: [Documentation Index](README.md) +- Backend Setup: [HazeBot Repository](https://github.com/inventory69/HazeBot) +- Backend Docs: [HazeBot Documentation](https://github.com/inventory69/HazeBot/blob/main/docs/README.md) + +**Related Guides:** +- ๐Ÿ”จ [Building Guide](BUILDING.md) - Build for all platforms +- ๐Ÿ”ฅ [Firebase Setup](FIREBASE_SETUP.md) - Push notifications +- ๐Ÿš€ [GitHub Actions](GITHUB_ACTIONS.md) - CI/CD pipeline +- ๐Ÿงช [Development Guide](DEVELOPMENT.md) - Development workflows --- diff --git a/lib/models/ticket.dart b/lib/models/ticket.dart index 2e8821e..8bdbd83 100644 --- a/lib/models/ticket.dart +++ b/lib/models/ticket.dart @@ -17,6 +17,7 @@ class Ticket { final String? assignedToName; final String? assignedToAvatar; final String? initialMessage; + final int reopenCount; Ticket({ required this.ticketId, @@ -37,6 +38,7 @@ class Ticket { this.assignedToName, this.assignedToAvatar, this.initialMessage, + this.reopenCount = 0, }); factory Ticket.fromJson(Map json) { @@ -59,6 +61,7 @@ class Ticket { assignedToName: json['assigned_to_name'] as String?, assignedToAvatar: json['assigned_to_avatar'] as String?, initialMessage: json['initial_message'] as String?, + reopenCount: json['reopen_count'] as int? ?? 0, ); } @@ -82,6 +85,7 @@ class Ticket { 'assigned_to_name': assignedToName, 'assigned_to_avatar': assignedToAvatar, 'initial_message': initialMessage, + 'reopen_count': reopenCount, }; } diff --git a/lib/screens/user/tickets_screen.dart b/lib/screens/user/tickets_screen.dart index 4dec511..a141bf3 100644 --- a/lib/screens/user/tickets_screen.dart +++ b/lib/screens/user/tickets_screen.dart @@ -889,22 +889,159 @@ class _CreateTicketTabState extends State<_CreateTicketTab> { } // Ticket Detail Screen (for users) - using shared TicketChatWidget -class _TicketDetailScreen extends StatelessWidget { +class _TicketDetailScreen extends StatefulWidget { final Ticket ticket; const _TicketDetailScreen({required this.ticket}); + @override + State<_TicketDetailScreen> createState() => _TicketDetailScreenState(); +} + +class _TicketDetailScreenState extends State<_TicketDetailScreen> { + late Ticket _ticket; + bool _isProcessing = false; + + @override + void initState() { + super.initState(); + _ticket = widget.ticket; + } + + Future _closeTicket() async { + // Show close message dialog + final closeMessage = await showDialog( + context: context, + builder: (context) => _CloseMessageDialog(), + ); + + if (closeMessage == null) return; // User cancelled + + setState(() => _isProcessing = true); + + try { + final authService = Provider.of(context, listen: false); + await authService.apiService.closeTicket( + _ticket.ticketId, + closeMessage: closeMessage.isEmpty ? null : closeMessage, + ); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('โœ… Ticket closed successfully'), + backgroundColor: Colors.green, + ), + ); + Navigator.pop(context); // Go back to ticket list + } + } catch (e) { + if (mounted) { + setState(() => _isProcessing = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to close ticket: $e'), + backgroundColor: Colors.red, + ), + ); + } + } + } + + Future _reopenTicket() async { + // Show confirmation dialog + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Reopen Ticket'), + content: Text( + 'Do you want to reopen this ticket?\n\n' + 'Reopens remaining: ${3 - _ticket.reopenCount}/3', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('Cancel'), + ), + FilledButton( + onPressed: () => Navigator.pop(context, true), + child: const Text('Reopen'), + ), + ], + ), + ); + + if (confirmed != true) return; + + setState(() => _isProcessing = true); + + try { + final authService = Provider.of(context, listen: false); + await authService.apiService.reopenTicket(_ticket.ticketId); + + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('โœ… Ticket reopened successfully'), + backgroundColor: Colors.green, + ), + ); + Navigator.pop(context); // Go back to ticket list + } + } catch (e) { + if (mounted) { + setState(() => _isProcessing = false); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Failed to reopen ticket: $e'), + backgroundColor: Colors.red, + ), + ); + } + } + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; final statusColor = - ticket.status.toLowerCase() == 'open' ? Colors.green : Colors.grey; + _ticket.status.toLowerCase() == 'open' ? Colors.green : Colors.grey; return Scaffold( appBar: AppBar( - title: Text('Ticket #${ticket.ticketNum}'), + title: Text('Ticket #${_ticket.ticketNum}'), backgroundColor: colorScheme.surface, + actions: [ + // Show Close button for open tickets + if (_ticket.isOpen && !_isProcessing) + IconButton( + onPressed: _closeTicket, + icon: const Icon(Icons.lock), + tooltip: 'Close Ticket', + color: Colors.red[700], + ), + // Show Reopen button for closed tickets (if limit not reached) + if (_ticket.isClosed && _ticket.reopenCount < 3 && !_isProcessing) + IconButton( + onPressed: _reopenTicket, + icon: const Icon(Icons.lock_open), + tooltip: 'Reopen Ticket', + color: Colors.blue[700], + ), + // Show loading indicator when processing + if (_isProcessing) + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Center( + child: SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ), + ], ), body: Column( children: [ @@ -925,7 +1062,7 @@ class _TicketDetailScreen extends StatelessWidget { borderRadius: BorderRadius.circular(4), ), child: Text( - ticket.type, + _ticket.type, style: TextStyle( color: colorScheme.onPrimaryContainer, fontSize: 12, @@ -948,7 +1085,7 @@ class _TicketDetailScreen extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Icon( - ticket.status.toLowerCase() == 'open' + _ticket.status.toLowerCase() == 'open' ? Icons.circle : Icons.check_circle, color: statusColor, @@ -956,7 +1093,7 @@ class _TicketDetailScreen extends StatelessWidget { ), const SizedBox(width: 4), Text( - ticket.status, + _ticket.status, style: TextStyle( color: statusColor, fontSize: 12, @@ -968,7 +1105,69 @@ class _TicketDetailScreen extends StatelessWidget { ), ], ), - if (ticket.initialMessage != null) ...[ + // Show reopen count badge if ticket has been reopened + if (_ticket.isClosed && _ticket.reopenCount > 0) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.orange[50], + borderRadius: BorderRadius.circular(4), + border: Border.all(color: Colors.orange[300]!), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.info_outline, + size: 14, + color: Colors.orange[700], + ), + const SizedBox(width: 4), + Text( + 'Reopened ${_ticket.reopenCount}/3 times', + style: TextStyle( + fontSize: 12, + color: Colors.orange[900], + ), + ), + ], + ), + ), + ], + // Show limit reached warning + if (_ticket.isClosed && _ticket.reopenCount >= 3) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.red[50], + borderRadius: BorderRadius.circular(4), + border: Border.all(color: Colors.red[300]!), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.warning_amber, + size: 14, + color: Colors.red[700], + ), + const SizedBox(width: 4), + Text( + 'Reopen limit reached (3/3)', + style: TextStyle( + fontSize: 12, + color: Colors.red[900], + ), + ), + ], + ), + ), + ], + if (_ticket.initialMessage != null) ...[ const SizedBox(height: 12), ExpansionTile( tilePadding: EdgeInsets.zero, @@ -984,7 +1183,7 @@ class _TicketDetailScreen extends StatelessWidget { const SizedBox(width: 8), Expanded( child: Text( - _extractSubject(ticket.initialMessage!), + _extractSubject(_ticket.initialMessage!), style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), @@ -1006,7 +1205,7 @@ class _TicketDetailScreen extends StatelessWidget { ), ), child: Text( - _extractDescription(ticket.initialMessage!), + _extractDescription(_ticket.initialMessage!), style: theme.textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), @@ -1015,26 +1214,6 @@ class _TicketDetailScreen extends StatelessWidget { ], ), ], - if (ticket.assignedToName != null) ...[ - const SizedBox(height: 12), - Row( - children: [ - Icon( - Icons.person, - size: 16, - color: colorScheme.onSurfaceVariant, - ), - const SizedBox(width: 6), - Text( - 'Assigned to: ${ticket.assignedToName}', - style: TextStyle( - fontSize: 13, - color: colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ], ], ), ), @@ -1044,7 +1223,7 @@ class _TicketDetailScreen extends StatelessWidget { // Chat widget (full screen mode) Expanded( child: TicketChatWidget( - ticket: ticket, + ticket: _ticket, isFullScreen: true, ), ), @@ -1066,3 +1245,47 @@ class _TicketDetailScreen extends StatelessWidget { return descMatch?.group(1)?.trim() ?? ''; } } + +// Old duplicate removed below (was causing the error) + +// Close Message Dialog +class _CloseMessageDialog extends StatelessWidget { + final TextEditingController _controller = TextEditingController(); + + _CloseMessageDialog(); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: const Text('Close Ticket'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('Add an optional closing message:'), + const SizedBox(height: 16), + TextField( + controller: _controller, + decoration: const InputDecoration( + hintText: + 'e.g., Issue resolved, let me know if you need more help.', + border: OutlineInputBorder(), + ), + maxLines: 3, + maxLength: 500, + autofocus: true, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + FilledButton( + onPressed: () => Navigator.pop(context, _controller.text), + child: const Text('Close Ticket'), + ), + ], + ); + } +} diff --git a/create_notification_icon.sh b/scripts/create_notification_icon.sh similarity index 100% rename from create_notification_icon.sh rename to scripts/create_notification_icon.sh diff --git a/generate_adaptive_icons.sh b/scripts/generate_adaptive_icons.sh similarity index 100% rename from generate_adaptive_icons.sh rename to scripts/generate_adaptive_icons.sh diff --git a/spa_server.py b/scripts/spa_server.py similarity index 100% rename from spa_server.py rename to scripts/spa_server.py diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index ec0e6c1..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,12 +0,0 @@ -// Basic test file for HazeBot Admin -// Add your widget tests here - -import 'package:flutter_test/flutter_test.dart'; - -void main() { - test('Placeholder test', () { - // This is a placeholder test to prevent test failures - // Add real tests as the app develops - expect(1 + 1, equals(2)); - }); -}