Hot Desk 0.2.0: room layout proportions, name quick-select, remove notes
This commit is contained in:
@@ -104,16 +104,16 @@ legacy/ # Original HTML tools for reference
|
|||||||
| 4 | **Registratura** | `/registratura` | 0.4.0 | CRUD registry, dynamic doc types, bidirectional Address Book, threads, backdating, **legal deadline tracking**, recipient registration, document expiry, **NAS network path attachments** (A/O/P/T drives, copy-to-clipboard), **detail sheet side panel**, **configurable column visibility**, **QuickLook attachment preview** (images: zoom/pan, PDFs: native viewer, multi-file navigation) |
|
| 4 | **Registratura** | `/registratura` | 0.4.0 | CRUD registry, dynamic doc types, bidirectional Address Book, threads, backdating, **legal deadline tracking**, recipient registration, document expiry, **NAS network path attachments** (A/O/P/T drives, copy-to-clipboard), **detail sheet side panel**, **configurable column visibility**, **QuickLook attachment preview** (images: zoom/pan, PDFs: native viewer, multi-file navigation) |
|
||||||
| 5 | **Tag Manager** | `/tag-manager` | 0.2.0 | CRUD tags, category/scope/color, US/SDT seeds, mandatory categories, **ManicTime bidirectional sync** |
|
| 5 | **Tag Manager** | `/tag-manager` | 0.2.0 | CRUD tags, category/scope/color, US/SDT seeds, mandatory categories, **ManicTime bidirectional sync** |
|
||||||
| 6 | **IT Inventory** | `/it-inventory` | 0.2.0 | Dynamic equipment types, rented status (purple pulse), **42U rack visualization**, type/status/company filters |
|
| 6 | **IT Inventory** | `/it-inventory` | 0.2.0 | Dynamic equipment types, rented status (purple pulse), **42U rack visualization**, type/status/company filters |
|
||||||
| 7 | **Address Book** | `/address-book` | 0.1.1 | CRUD contacts, card grid, vCard export, Registratura reverse lookup, **dynamic types (creatable)**, **alphabetically sorted type dropdown** |
|
| 7 | **Address Book** | `/address-book` | 0.1.1 | CRUD contacts, card grid, vCard export, Registratura reverse lookup, **dynamic types (creatable)**, **alphabetically sorted type dropdown** |
|
||||||
| 8 | **Password Vault** | `/password-vault` | 0.3.0 | CRUD credentials, 9 categorii cu iconițe, **WiFi QR code real**, context-aware form, strength meter, company scope, **AES-256-GCM encryption** |
|
| 8 | **Password Vault** | `/password-vault` | 0.3.0 | CRUD credentials, 9 categorii cu iconițe, **WiFi QR code real**, context-aware form, strength meter, company scope, **AES-256-GCM encryption** |
|
||||||
| 9 | **Mini Utilities** | `/mini-utilities` | 0.1.1 | Text case, char counter, percentage, **TVA calculator (19%)**, area converter, U→R, num→text, artifact cleaner, MDLPA, PDF reducer, OCR, color palette |
|
| 9 | **Mini Utilities** | `/mini-utilities` | 0.1.1 | Text case, char counter, percentage, **TVA calculator (19%)**, area converter, U→R, num→text, artifact cleaner, MDLPA, PDF reducer, OCR, color palette |
|
||||||
| 10 | **Prompt Generator** | `/prompt-generator` | 0.2.0 | Template-driven prompt builder, **18 templates** (14 text + 4 image), search bar, target type filter |
|
| 10 | **Prompt Generator** | `/prompt-generator` | 0.2.0 | Template-driven prompt builder, **18 templates** (14 text + 4 image), search bar, target type filter |
|
||||||
| 11 | **Digital Signatures** | `/digital-signatures` | 0.1.0 | CRUD assets, drag-and-drop file upload, tag chips |
|
| 11 | **Digital Signatures** | `/digital-signatures` | 0.1.0 | CRUD assets, drag-and-drop file upload, tag chips |
|
||||||
| 12 | **Word Templates** | `/word-templates` | 0.1.0 | Template library, 8 categories, version tracking, .docx placeholder auto-detection |
|
| 12 | **Word Templates** | `/word-templates` | 0.1.0 | Template library, 8 categories, version tracking, .docx placeholder auto-detection |
|
||||||
| 13 | **AI Chat** | `/ai-chat` | 0.2.0 | Multi-provider (OpenAI/Claude/Ollama/demo), **project linking via Tag Manager**, provider status badge |
|
| 13 | **AI Chat** | `/ai-chat` | 0.2.0 | Multi-provider (OpenAI/Claude/Ollama/demo), **project linking via Tag Manager**, provider status badge |
|
||||||
| 14 | **Hot Desk** | `/hot-desk` | 0.1.1 | 4 desks, week-ahead calendar, room layout (window+door proportioned), reserve/cancel |
|
| 14 | **Hot Desk** | `/hot-desk` | 0.1.1 | 4 desks, week-ahead calendar, room layout (window+door proportioned), reserve/cancel |
|
||||||
| 15 | **ParcelSync** | `/parcel-sync` | 0.5.0 | eTerra ANCPI integration, **PostGIS database**, background sync, 23-layer catalog, enrichment pipeline, owner search, **per-UAT analytics dashboard**, **health check + maintenance detection** |
|
| 15 | **ParcelSync** | `/parcel-sync` | 0.5.0 | eTerra ANCPI integration, **PostGIS database**, background sync, 23-layer catalog, enrichment pipeline, owner search, **per-UAT analytics dashboard**, **health check + maintenance detection** |
|
||||||
| 16 | **Visual Copilot** | `/visual-copilot` | 0.1.0 | AI-powered image analysis — **developed in separate repo** (`https://git.beletage.ro/gitadmin/vim`), placeholder in ArchiTools, will be merged as module later |
|
| 16 | **Visual Copilot** | `/visual-copilot` | 0.1.0 | AI-powered image analysis — **developed in separate repo** (`https://git.beletage.ro/gitadmin/vim`), placeholder in ArchiTools, will be merged as module later |
|
||||||
|
|
||||||
### Registratura — Legal Deadline Tracking (Termene Legale)
|
### Registratura — Legal Deadline Tracking (Termene Legale)
|
||||||
|
|
||||||
|
|||||||
+15
-10
@@ -724,59 +724,64 @@ Env vars (hardcoded in docker-compose.yml for Portainer CE):
|
|||||||
### 4B.04 `[STANDARD]` Registratura — Legal Deadline Workflow Fixes
|
### 4B.04 `[STANDARD]` Registratura — Legal Deadline Workflow Fixes
|
||||||
|
|
||||||
**What:** Fix gaps in the legal deadline tracking logic:
|
**What:** Fix gaps in the legal deadline tracking logic:
|
||||||
|
|
||||||
- Chain deadline workflow (resolving one → prompt to add next in sequence)
|
- Chain deadline workflow (resolving one → prompt to add next in sequence)
|
||||||
- Backward deadline edge cases (e.g., AC extension 45 working days BEFORE expiry)
|
- Backward deadline edge cases (e.g., AC extension 45 working days BEFORE expiry)
|
||||||
- Tacit approval auto-detection when overdue + applicable type
|
- Tacit approval auto-detection when overdue + applicable type
|
||||||
- UI polish for deadline dashboard (filters, sorting, edge states)
|
- UI polish for deadline dashboard (filters, sorting, edge states)
|
||||||
**Files:** `src/modules/registratura/services/deadline-service.ts`, `components/deadline-dashboard.tsx`, `components/deadline-add-dialog.tsx`
|
**Files:** `src/modules/registratura/services/deadline-service.ts`, `components/deadline-dashboard.tsx`, `components/deadline-add-dialog.tsx`
|
||||||
**Status:** TODO
|
**Status:** TODO
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4B.05 `[STANDARD]` Tag Manager — Logic/Workflow Fix + ERP API
|
### 4B.05 `[STANDARD]` Tag Manager — Logic/Workflow Fix + ERP API
|
||||||
|
|
||||||
**What:**
|
**What:**
|
||||||
|
|
||||||
- Fix tag assignment and filtering logic/workflow issues
|
- Fix tag assignment and filtering logic/workflow issues
|
||||||
- Expose tags via API for external ERP integration (read-only endpoint for tag list + project assignments)
|
- Expose tags via API for external ERP integration (read-only endpoint for tag list + project assignments)
|
||||||
- Verify ManicTime bidirectional sync still works
|
- Verify ManicTime bidirectional sync still works
|
||||||
**Files:** `src/modules/tag-manager/`, `src/app/api/` (new tags API route)
|
**Files:** `src/modules/tag-manager/`, `src/app/api/` (new tags API route)
|
||||||
**Status:** TODO
|
**Status:** TODO
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4B.06 `[STANDARD]` Prompt Generator — Bug Fixes + New Features
|
### 4B.06 `[STANDARD]` Prompt Generator — Bug Fixes + New Features
|
||||||
|
|
||||||
**What:** Address known bugs and implement new ideas (details TBD from user testing):
|
**What:** Address known bugs and implement new ideas (details TBD from user testing):
|
||||||
|
|
||||||
- Fix any template rendering issues
|
- Fix any template rendering issues
|
||||||
- Add new templates as requested
|
- Add new templates as requested
|
||||||
- Implement user-suggested feature improvements
|
- Implement user-suggested feature improvements
|
||||||
**Files:** `src/modules/prompt-generator/`
|
**Files:** `src/modules/prompt-generator/`
|
||||||
**Status:** TODO — awaiting user feedback from testing
|
**Status:** TODO — awaiting user feedback from testing
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4B.07 `[HEAVY]` Authentik SSO — Verify & Fix
|
### 4B.07 `[HEAVY]` Authentik SSO — Verify & Fix
|
||||||
|
|
||||||
**What:** End-to-end verification that Authentik OIDC login works:
|
**What:** End-to-end verification that Authentik OIDC login works:
|
||||||
|
|
||||||
- Verify auth.beletage.ro accessibility
|
- Verify auth.beletage.ro accessibility
|
||||||
- Test login flow (redirect → auth → callback → session)
|
- Test login flow (redirect → auth → callback → session)
|
||||||
- Verify group→role/company mapping
|
- Verify group→role/company mapping
|
||||||
- Fix any issues with NextAuth v4 + Authentik provider config
|
- Fix any issues with NextAuth v4 + Authentik provider config
|
||||||
- Ensure session persistence and token refresh
|
- Ensure session persistence and token refresh
|
||||||
**Files:** `src/core/auth/`, `src/app/api/auth/`, `.env` / `docker-compose.yml`
|
**Files:** `src/core/auth/`, `src/app/api/auth/`, `.env` / `docker-compose.yml`
|
||||||
**Status:** TODO — critical for multi-user testing
|
**Status:** TODO — critical for multi-user testing
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4B.08 `[STANDARD]` DB/Storage — End-to-End Verification
|
### 4B.08 `[STANDARD]` DB/Storage — End-to-End Verification
|
||||||
|
|
||||||
**What:** Verify all 14 modules correctly persist to PostgreSQL:
|
**What:** Verify all 14 modules correctly persist to PostgreSQL:
|
||||||
|
|
||||||
- Test CRUD operations for each module
|
- Test CRUD operations for each module
|
||||||
- Verify data survives container restart
|
- Verify data survives container restart
|
||||||
- Check storage adapter fallback behavior
|
- Check storage adapter fallback behavior
|
||||||
- Validate MinIO file storage connection (adapter pending)
|
- Validate MinIO file storage connection (adapter pending)
|
||||||
**Files:** `src/core/storage/`, `src/app/api/storage/`, `prisma/schema.prisma`
|
**Files:** `src/core/storage/`, `src/app/api/storage/`, `prisma/schema.prisma`
|
||||||
**Status:** TODO
|
**Status:** TODO
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,10 @@ export function DeskRoomLayout({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
{/* Room container — styled like a top-down floor plan */}
|
{/* Room container — styled like a top-down floor plan */}
|
||||||
<div className="relative w-full max-w-[340px] rounded-xl border border-border/60 bg-muted/20 p-5">
|
<div className="relative w-full max-w-[420px] rounded-xl border border-border/60 bg-muted/20 px-5 py-10">
|
||||||
{/* Window indicator — LEFT wall (landmark for orientation) */}
|
{/* Window indicator — LEFT wall (landmark for orientation) */}
|
||||||
<div className="absolute top-[35%] bottom-[35%] left-0 w-1.5 rounded-r-sm bg-sky-300/30 dark:bg-sky-500/20" />
|
<div className="absolute top-[26.5%] bottom-[26.5%] left-0 w-1.5 rounded-r-sm bg-sky-300/30 dark:bg-sky-500/20" />
|
||||||
<div className="absolute top-[37%] bottom-[37%] left-0 flex flex-col justify-between">
|
<div className="absolute top-[28.5%] bottom-[28.5%] left-0 flex flex-col justify-between">
|
||||||
{Array.from({ length: 3 }).map((_, i) => (
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
@@ -30,21 +30,14 @@ export function DeskRoomLayout({
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{/* Window label */}
|
|
||||||
<div className="absolute left-1.5 top-1/2 -translate-y-1/2 -rotate-90 text-[9px] font-medium text-muted-foreground/40 tracking-widest uppercase select-none">
|
|
||||||
Fereastră
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Door indicator — RIGHT wall */}
|
{/* Door indicator — RIGHT wall */}
|
||||||
<div className="absolute top-[55%] right-0 h-16 w-1.5 rounded-l-sm bg-amber-400/25 dark:bg-amber-500/15" />
|
<div className="absolute top-[63%] right-0 h-16 w-1.5 rounded-l-sm bg-amber-400/25 dark:bg-amber-500/15" />
|
||||||
<div className="absolute top-[55%] right-1.5 translate-y-1 text-[8px] text-muted-foreground/30 select-none">
|
|
||||||
Ușă
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Central table */}
|
{/* Central table */}
|
||||||
<div className="mx-auto mt-4 mb-4 flex flex-col items-center">
|
<div className="mr-auto -ml-16 mt-4 mb-4 flex flex-col items-center">
|
||||||
{/* Top row desks */}
|
{/* Top row desks */}
|
||||||
<div className="flex gap-3 mb-2">
|
<div className="flex gap-0 mb-0">
|
||||||
{DESKS.filter((d) => d.position.startsWith("top")).map((desk) => {
|
{DESKS.filter((d) => d.position.startsWith("top")).map((desk) => {
|
||||||
const reservation = getReservationForDesk(
|
const reservation = getReservationForDesk(
|
||||||
desk.id,
|
desk.id,
|
||||||
@@ -65,10 +58,10 @@ export function DeskRoomLayout({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* The table surface */}
|
{/* The table surface */}
|
||||||
<div className="h-12 w-full max-w-[280px] rounded-md border border-border/50 bg-muted/40" />
|
<div className="h-3 w-[232px] rounded-sm border border-border/50 bg-muted/40" />
|
||||||
|
|
||||||
{/* Bottom row desks */}
|
{/* Bottom row desks */}
|
||||||
<div className="flex gap-3 mt-2">
|
<div className="flex gap-0 mt-0">
|
||||||
{DESKS.filter((d) => d.position.startsWith("bottom")).map(
|
{DESKS.filter((d) => d.position.startsWith("bottom")).map(
|
||||||
(desk) => {
|
(desk) => {
|
||||||
const reservation = getReservationForDesk(
|
const reservation = getReservationForDesk(
|
||||||
@@ -111,7 +104,7 @@ function DeskSlot({ label, reservation, side, onClick }: DeskSlotProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
"group relative flex w-[125px] cursor-pointer flex-col items-center rounded-lg border p-3 transition-all",
|
"group relative flex w-[116px] cursor-pointer flex-col items-center rounded-lg border p-3 transition-all",
|
||||||
side === "top" ? "rounded-b-sm" : "rounded-t-sm",
|
side === "top" ? "rounded-b-sm" : "rounded-t-sm",
|
||||||
isBooked
|
isBooked
|
||||||
? "border-primary/30 bg-primary/8 hover:border-primary/50 hover:bg-primary/12"
|
? "border-primary/30 bg-primary/8 hover:border-primary/50 hover:bg-primary/12"
|
||||||
|
|||||||
@@ -199,9 +199,6 @@ export function HotDeskModule() {
|
|||||||
<th className="px-3 py-1.5 text-left text-xs font-medium text-muted-foreground">
|
<th className="px-3 py-1.5 text-left text-xs font-medium text-muted-foreground">
|
||||||
Persoana
|
Persoana
|
||||||
</th>
|
</th>
|
||||||
<th className="px-3 py-1.5 text-left text-xs font-medium text-muted-foreground">
|
|
||||||
Note
|
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -216,9 +213,6 @@ export function HotDeskModule() {
|
|||||||
r.deskId}
|
r.deskId}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-1.5 text-xs">{r.userName}</td>
|
<td className="px-3 py-1.5 text-xs">{r.userName}</td>
|
||||||
<td className="px-3 py-1.5 text-xs text-muted-foreground truncate max-w-[120px]">
|
|
||||||
{r.notes || "—"}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -11,11 +11,21 @@ import {
|
|||||||
import { Button } from "@/shared/components/ui/button";
|
import { Button } from "@/shared/components/ui/button";
|
||||||
import { Input } from "@/shared/components/ui/input";
|
import { Input } from "@/shared/components/ui/input";
|
||||||
import { Label } from "@/shared/components/ui/label";
|
import { Label } from "@/shared/components/ui/label";
|
||||||
import { Textarea } from "@/shared/components/ui/textarea";
|
import { useAuth } from "@/core/auth";
|
||||||
|
import { cn } from "@/shared/lib/utils";
|
||||||
import type { DeskId, DeskReservation } from "../types";
|
import type { DeskId, DeskReservation } from "../types";
|
||||||
import { getDeskLabel } from "../types";
|
import { getDeskLabel } from "../types";
|
||||||
import { formatDateRo, isDateBookable } from "../services/reservation-service";
|
import { formatDateRo, isDateBookable } from "../services/reservation-service";
|
||||||
|
|
||||||
|
const TEAM_NAMES = [
|
||||||
|
"Alexandra",
|
||||||
|
"Mihaela",
|
||||||
|
"Ștefan",
|
||||||
|
"Mara",
|
||||||
|
"Denisa",
|
||||||
|
"Daria",
|
||||||
|
];
|
||||||
|
|
||||||
interface ReservationDialogProps {
|
interface ReservationDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -36,8 +46,22 @@ export function ReservationDialog({
|
|||||||
onCancel,
|
onCancel,
|
||||||
}: ReservationDialogProps) {
|
}: ReservationDialogProps) {
|
||||||
const [userName, setUserName] = useState("");
|
const [userName, setUserName] = useState("");
|
||||||
const [notes, setNotes] = useState("");
|
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
// Auto-fill first name from Authentik session
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && !userName && user?.name) {
|
||||||
|
const firstName = user.name.split(" ")[0];
|
||||||
|
if (firstName) setUserName(firstName);
|
||||||
|
}
|
||||||
|
}, [open, user?.name]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
const filteredNames = userName.trim()
|
||||||
|
? TEAM_NAMES.filter((n) =>
|
||||||
|
n.toLowerCase().startsWith(userName.toLowerCase()),
|
||||||
|
)
|
||||||
|
: TEAM_NAMES;
|
||||||
|
|
||||||
const bookable = isDateBookable(dateKey);
|
const bookable = isDateBookable(dateKey);
|
||||||
const isBooked = !!existingReservation;
|
const isBooked = !!existingReservation;
|
||||||
@@ -46,9 +70,8 @@ export function ReservationDialog({
|
|||||||
if (!userName.trim()) return;
|
if (!userName.trim()) return;
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
await onBook(userName.trim(), notes.trim());
|
await onBook(userName.trim(), "");
|
||||||
setUserName("");
|
setUserName("");
|
||||||
setNotes("");
|
|
||||||
onClose();
|
onClose();
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
@@ -84,12 +107,6 @@ export function ReservationDialog({
|
|||||||
{existingReservation.userName}
|
{existingReservation.userName}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{existingReservation.notes && (
|
|
||||||
<div className="text-sm">
|
|
||||||
<span className="text-muted-foreground">Note: </span>
|
|
||||||
<span>{existingReservation.notes}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : bookable ? (
|
) : bookable ? (
|
||||||
@@ -104,17 +121,23 @@ export function ReservationDialog({
|
|||||||
onKeyDown={(e) => e.key === "Enter" && handleBook()}
|
onKeyDown={(e) => e.key === "Enter" && handleBook()}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
<div className="flex flex-wrap gap-1.5">
|
||||||
<div className="space-y-2">
|
{filteredNames.map((name) => (
|
||||||
<Label htmlFor="notes">Note (opțional)</Label>
|
<button
|
||||||
<Textarea
|
key={name}
|
||||||
id="notes"
|
type="button"
|
||||||
placeholder="Ex: lucrez la proiect X, am nevoie de monitor extern..."
|
onClick={() => setUserName(name)}
|
||||||
value={notes}
|
className={cn(
|
||||||
onChange={(e) => setNotes(e.target.value)}
|
"rounded-md border px-2.5 py-1 text-xs transition-colors",
|
||||||
rows={2}
|
userName === name
|
||||||
className="resize-none"
|
? "border-primary bg-primary/10 text-primary font-medium"
|
||||||
/>
|
: "border-border/50 bg-muted/30 text-muted-foreground hover:border-primary/40 hover:bg-muted/50",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
Reference in New Issue
Block a user