pre-launch hardening: Address Book type sort, Hot Desk proportions, TVA calculator, ROADMAP Phase 4B
- Address Book: type dropdown always sorted alphabetically (ro locale), including custom types - Hot Desk: window ~half height (top-[35%] bottom-[35%]), door ~double height (h-16) - Mini Utilities: TVA calculator (19%) with add/extract modes, RON formatting, copy buttons - ROADMAP: new Phase 4B Pre-Launch Hardening with 10 structured tasks - CLAUDE.md: bumped versions (Address Book 0.1.1, Mini Utilities 0.1.1, Hot Desk 0.1.1), Visual Copilot separate repo note
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.0 | CRUD contacts, card grid, vCard export, Registratura reverse lookup, **dynamic types (creatable)** |
|
| 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.0 | Text case, char counter, percentage, area converter, U→R, artifact cleaner, MDLPA, PDF reducer, OCR |
|
| 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.0 | 4 desks, week-ahead calendar, room layout (window+door), 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 (placeholder/early stage) |
|
| 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)
|
||||||
|
|
||||||
|
|||||||
+122
-19
@@ -30,26 +30,26 @@
|
|||||||
|
|
||||||
## Current Module Status (after Phase 3 completion)
|
## Current Module Status (after Phase 3 completion)
|
||||||
|
|
||||||
| # | Module | Version | Status | Remaining Gaps | Future Enhancements |
|
| # | Module | Version | Status | Remaining Gaps | Future Enhancements |
|
||||||
| --- | ------------------ | ------- | -------- | ---------------------------------------- | ------------------------------------------------- |
|
| --- | ------------------ | ------- | --------- | --------------------------------------------------- | ------------------------------------------------- |
|
||||||
| 1 | Registratura | 0.3.0 | COMPLETE | — | Workflow automation, email integration, OCR |
|
| 1 | Registratura | 0.4.0 | HARDENING | Legal deadline workflow gaps, chain logic | Workflow automation, email integration, OCR |
|
||||||
| 2 | Email Signature | 0.1.0 | COMPLETE | US/SDT addresses may need update | AD sync, branding packs, promo banners |
|
| 2 | Email Signature | 0.1.0 | COMPLETE | US/SDT addresses may need update | AD sync, branding packs, promo banners |
|
||||||
| 3 | Word XML | 0.1.0 | COMPLETE | — | Schema validator, visual mapper |
|
| 3 | Word XML | 0.1.0 | COMPLETE | — | Schema validator, visual mapper |
|
||||||
| 4 | Digital Signatures | 0.1.0 | COMPLETE | — | Permission layers, document insertion |
|
| 4 | Digital Signatures | 0.1.0 | COMPLETE | — | Permission layers, document insertion |
|
||||||
| 5 | Password Vault | 0.3.0 | COMPLETE | — | Hardware key, rotation reminders, Passbolt |
|
| 5 | Password Vault | 0.3.0 | COMPLETE | — | Hardware key, rotation reminders, Passbolt |
|
||||||
| 6 | IT Inventory | 0.2.0 | COMPLETE | — | Network scan import |
|
| 6 | IT Inventory | 0.2.0 | COMPLETE | — | Network scan import |
|
||||||
| 7 | Address Book | 0.1.0 | COMPLETE | — | Email sync, deduplication |
|
| 7 | Address Book | 0.1.1 | COMPLETE | — | Email sync, deduplication |
|
||||||
| 8 | Prompt Generator | 0.2.0 | COMPLETE | — | Prompt scoring, more image templates |
|
| 8 | Prompt Generator | 0.2.0 | HARDENING | Bug fixes, new idea TBD | Prompt scoring, more image templates |
|
||||||
| 9 | Word Templates | 0.1.0 | COMPLETE | No clause library; no Word generation | Diff compare, document generator |
|
| 9 | Word Templates | 0.1.0 | COMPLETE | No clause library; no Word generation | Diff compare, document generator |
|
||||||
| 10 | Tag Manager | 0.2.0 | COMPLETE | ManicTime needs SMB mount on Docker host | Smart suggestions |
|
| 10 | Tag Manager | 0.2.0 | HARDENING | Logic/workflow fix, ERP API exposure needed | Smart suggestions |
|
||||||
| 11 | Mini Utilities | 0.1.0 | COMPLETE | — | More converters, DWG→DXF |
|
| 11 | Mini Utilities | 0.1.1 | COMPLETE | — | More converters, DWG→DXF, more tools TBD |
|
||||||
| 12 | Dashboard | 0.1.0 | COMPLETE | — | Custom dashboards per role |
|
| 12 | Dashboard | 0.1.0 | COMPLETE | — | Custom dashboards per role |
|
||||||
| 13 | AI Chat | 0.2.0 | COMPLETE | Needs API key env vars for real AI | Streaming, model selector, conversation templates |
|
| 13 | AI Chat | 0.2.0 | COMPLETE | Needs API key env vars for real AI | Streaming, model selector, conversation templates |
|
||||||
| 14 | Hot Desk | 0.1.0 | COMPLETE | — | — |
|
| 14 | Hot Desk | 0.1.1 | COMPLETE | — | — |
|
||||||
| 15 | ParcelSync | 0.5.0 | COMPLETE | Needs real-world UAT testing at scale | Map visualization, batch enrichment, export tools |
|
| 15 | ParcelSync | 0.5.0 | COMPLETE | Needs real-world UAT testing at scale | Map visualization, batch enrichment, export tools |
|
||||||
| 16 | Visual Copilot | 0.1.0 | STUB | Placeholder only | AI image analysis integration |
|
| 16 | Visual Copilot | 0.1.0 | SEPARATE | Dev in separate repo (git.beletage.ro/gitadmin/vim) | AI image analysis, merge into ArchiTools later |
|
||||||
|
|
||||||
**Phases 1–3 COMPLETE (all 42 tasks).** Phase 7B (ParcelSync) COMPLETE. Next: Phase 4 (Quality & Testing) or module hardening.
|
**Phases 1–3 COMPLETE (all 42 tasks).** Phase 7B (ParcelSync) COMPLETE. Phase 4B (Pre-Launch Hardening) IN PROGRESS.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -684,6 +684,7 @@ Env vars (hardcoded in docker-compose.yml for Portainer CE):
|
|||||||
**Status:** ✅ Done. `eterra-health.ts` singleton, `/api/eterra/health` endpoint, session route blocks login, UI shows amber pill with message extraction.
|
**Status:** ✅ Done. `eterra-health.ts` singleton, `/api/eterra/health` endpoint, session route blocks login, UI shows amber pill with message extraction.
|
||||||
|
|
||||||
**Bugs found & fixed during ParcelSync development:**
|
**Bugs found & fixed during ParcelSync development:**
|
||||||
|
|
||||||
- Timeout 40s too low for geometry pages → increased to 120s
|
- Timeout 40s too low for geometry pages → increased to 120s
|
||||||
- `arr[0]` access fails TS strict even after length check → assign to const
|
- `arr[0]` access fails TS strict even after length check → assign to const
|
||||||
- `?? ""` on `{}` typed field produces `{}` → use `typeof` check
|
- `?? ""` on `{}` typed field produces `{}` → use `typeof` check
|
||||||
@@ -691,6 +692,108 @@ Env vars (hardcoded in docker-compose.yml for Portainer CE):
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## PHASE 4B — Pre-Launch Hardening (2026-03 — OFFICE TESTING)
|
||||||
|
|
||||||
|
> Final fixes and polish before rolling out to the office for daily use.
|
||||||
|
> Official testing start: **2026-03-09**. All critical items must be resolved.
|
||||||
|
|
||||||
|
### 4B.01 ✅ `[LIGHT]` Address Book — Type Dropdown Alphabetical Sort (2026-03-08)
|
||||||
|
|
||||||
|
**What:** Type dropdown (filter + creation form) always sorted alphabetically by Romanian label, including newly created custom types.
|
||||||
|
**Files:** `src/modules/address-book/components/address-book-module.tsx`
|
||||||
|
**Status:** ✅ Done. `allTypes` memo sorted with `localeCompare('ro')`. `CreatableTypeSelect` entries merged and sorted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4B.02 ✅ `[LIGHT]` Hot Desk — Window/Door Proportions (2026-03-08)
|
||||||
|
|
||||||
|
**What:** Room layout: window ~half current height, door ~double current height. Window indicator dots reduced from 6 to 3.
|
||||||
|
**Files:** `src/modules/hot-desk/components/desk-room-layout.tsx`
|
||||||
|
**Status:** ✅ Done. Window: `top-[35%] bottom-[35%]` (was `top-4 bottom-4`). Door: `h-16` (was `h-8`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4B.03 ✅ `[STANDARD]` Mini Utilities — TVA Calculator (2026-03-08)
|
||||||
|
|
||||||
|
**What:** Quick VAT calculator with 19% Romanian rate. Two modes: "Adaugă TVA" (add to net) and "Extrage TVA" (extract from gross). Copy buttons, formatted RON output.
|
||||||
|
**Files:** `src/modules/mini-utilities/components/mini-utilities-module.tsx`
|
||||||
|
**Status:** ✅ Done. New `TvaCalculator` component with mode toggle, RON formatting, copy-to-clipboard.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4B.04 `[STANDARD]` Registratura — Legal Deadline Workflow Fixes
|
||||||
|
|
||||||
|
**What:** Fix gaps in the legal deadline tracking logic:
|
||||||
|
- Chain deadline workflow (resolving one → prompt to add next in sequence)
|
||||||
|
- Backward deadline edge cases (e.g., AC extension 45 working days BEFORE expiry)
|
||||||
|
- Tacit approval auto-detection when overdue + applicable type
|
||||||
|
- 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`
|
||||||
|
**Status:** TODO
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4B.05 `[STANDARD]` Tag Manager — Logic/Workflow Fix + ERP API
|
||||||
|
|
||||||
|
**What:**
|
||||||
|
- Fix tag assignment and filtering logic/workflow issues
|
||||||
|
- Expose tags via API for external ERP integration (read-only endpoint for tag list + project assignments)
|
||||||
|
- Verify ManicTime bidirectional sync still works
|
||||||
|
**Files:** `src/modules/tag-manager/`, `src/app/api/` (new tags API route)
|
||||||
|
**Status:** TODO
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4B.06 `[STANDARD]` Prompt Generator — Bug Fixes + New Features
|
||||||
|
|
||||||
|
**What:** Address known bugs and implement new ideas (details TBD from user testing):
|
||||||
|
- Fix any template rendering issues
|
||||||
|
- Add new templates as requested
|
||||||
|
- Implement user-suggested feature improvements
|
||||||
|
**Files:** `src/modules/prompt-generator/`
|
||||||
|
**Status:** TODO — awaiting user feedback from testing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4B.07 `[HEAVY]` Authentik SSO — Verify & Fix
|
||||||
|
|
||||||
|
**What:** End-to-end verification that Authentik OIDC login works:
|
||||||
|
- Verify auth.beletage.ro accessibility
|
||||||
|
- Test login flow (redirect → auth → callback → session)
|
||||||
|
- Verify group→role/company mapping
|
||||||
|
- Fix any issues with NextAuth v4 + Authentik provider config
|
||||||
|
- Ensure session persistence and token refresh
|
||||||
|
**Files:** `src/core/auth/`, `src/app/api/auth/`, `.env` / `docker-compose.yml`
|
||||||
|
**Status:** TODO — critical for multi-user testing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4B.08 `[STANDARD]` DB/Storage — End-to-End Verification
|
||||||
|
|
||||||
|
**What:** Verify all 14 modules correctly persist to PostgreSQL:
|
||||||
|
- Test CRUD operations for each module
|
||||||
|
- Verify data survives container restart
|
||||||
|
- Check storage adapter fallback behavior
|
||||||
|
- Validate MinIO file storage connection (adapter pending)
|
||||||
|
**Files:** `src/core/storage/`, `src/app/api/storage/`, `prisma/schema.prisma`
|
||||||
|
**Status:** TODO
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4B.09 `[STANDARD]` Mini Utilities — Additional Tools (TBD)
|
||||||
|
|
||||||
|
**What:** User will provide a list of additional quick tools to add to the Mini Utilities module.
|
||||||
|
**Status:** TODO — awaiting user list
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4B.10 `[LIGHT]` Visual Copilot — Separate Repo Documentation
|
||||||
|
|
||||||
|
**What:** Visual Copilot is being developed in a **separate repository**: `https://git.beletage.ro/gitadmin/vim`. Current ArchiTools placeholder stays as-is. Module will be merged back as a proper module when ready.
|
||||||
|
**Status:** DOCUMENTED — no code changes needed in ArchiTools
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## PHASE 8 — Advanced Features
|
## PHASE 8 — Advanced Features
|
||||||
|
|
||||||
> Cross-cutting features that enhance the entire platform.
|
> Cross-cutting features that enhance the entire platform.
|
||||||
|
|||||||
+6
-6
@@ -48,12 +48,12 @@ ParcelSync module development continuation — owner search, UAT dashboard, eTer
|
|||||||
|
|
||||||
### Bugs Found & Fixed
|
### Bugs Found & Fixed
|
||||||
|
|
||||||
| Bug | Root Cause | Fix |
|
| Bug | Root Cause | Fix |
|
||||||
|-----|-----------|-----|
|
| -------------------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------- |
|
||||||
| `timeout of 40000ms exceeded` on TERENURI_ACTIVE | Default 40s too short for 1000-feature geometry pages | Increased to 120s |
|
| `timeout of 40000ms exceeded` on TERENURI_ACTIVE | Default 40s too short for 1000-feature geometry pages | Increased to 120s |
|
||||||
| `suprafata` type `{}` not assignable to `string\|number` | `?? ""` on enrichment field typed `{}` produces `{}` | `typeof x === 'number'` explicit check |
|
| `suprafata` type `{}` not assignable to `string\|number` | `?? ""` on enrichment field typed `{}` produces `{}` | `typeof x === 'number'` explicit check |
|
||||||
| `Object is possibly 'undefined'` on `topOwners[0]` | TS strict: `arr[0]` is `T\|undefined` even after `length > 0` | Assign to const, `if (const)` guard |
|
| `Object is possibly 'undefined'` on `topOwners[0]` | TS strict: `arr[0]` is `T\|undefined` even after `length > 0` | Assign to const, `if (const)` guard |
|
||||||
| eTerra maintenance shows as "Eroare conectare" | No health check, login attempt fails with generic error | Health check + maintenance detection + amber UI state |
|
| eTerra maintenance shows as "Eroare conectare" | No health check, login attempt fails with generic error | Health check + maintenance detection + amber UI state |
|
||||||
|
|
||||||
### Learnings
|
### Learnings
|
||||||
|
|
||||||
|
|||||||
@@ -80,15 +80,22 @@ export function AddressBookModule() {
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Collect all contact types (defaults + custom ones from existing contacts)
|
// Collect all contact types (defaults + custom ones from existing contacts), sorted alphabetically by label
|
||||||
const allTypes = useMemo(() => {
|
const allTypes = useMemo(() => {
|
||||||
const types = { ...DEFAULT_TYPE_LABELS };
|
const types: Record<string, string> = { ...DEFAULT_TYPE_LABELS };
|
||||||
for (const c of allContacts) {
|
for (const c of allContacts) {
|
||||||
if (c.type && !types[c.type]) {
|
if (c.type && !types[c.type]) {
|
||||||
types[c.type] = c.type; // custom type — label is the type itself
|
types[c.type] = c.type; // custom type — label is the type itself
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return types;
|
// Sort entries alphabetically by label
|
||||||
|
const sorted: Record<string, string> = {};
|
||||||
|
for (const [k, v] of Object.entries(types).sort((a, b) =>
|
||||||
|
a[1].localeCompare(b[1], "ro"),
|
||||||
|
)) {
|
||||||
|
sorted[k] = v;
|
||||||
|
}
|
||||||
|
return sorted;
|
||||||
}, [allContacts]);
|
}, [allContacts]);
|
||||||
|
|
||||||
const handleSubmit = async (
|
const handleSubmit = async (
|
||||||
@@ -652,15 +659,16 @@ function CreatableTypeSelect({
|
|||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{Object.entries(DEFAULT_TYPE_LABELS).map(([k, label]) => (
|
{Object.entries(DEFAULT_TYPE_LABELS)
|
||||||
<SelectItem key={k} value={k}>
|
.concat(
|
||||||
{label}
|
value && !DEFAULT_TYPE_LABELS[value] ? [[value, value]] : [],
|
||||||
</SelectItem>
|
)
|
||||||
))}
|
.sort((a, b) => a[1].localeCompare(b[1], "ro"))
|
||||||
{/* Show current custom value if not in defaults */}
|
.map(([k, label]) => (
|
||||||
{value && !DEFAULT_TYPE_LABELS[value] && (
|
<SelectItem key={k} value={k}>
|
||||||
<SelectItem value={value}>{value}</SelectItem>
|
{label}
|
||||||
)}
|
</SelectItem>
|
||||||
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ export function DeskRoomLayout({
|
|||||||
{/* 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-[340px] rounded-xl border border-border/60 bg-muted/20 p-5">
|
||||||
{/* Window indicator — LEFT wall (landmark for orientation) */}
|
{/* Window indicator — LEFT wall (landmark for orientation) */}
|
||||||
<div className="absolute top-4 bottom-4 left-0 w-1.5 rounded-r-sm bg-sky-300/30 dark:bg-sky-500/20" />
|
<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-6 bottom-6 left-0 flex flex-col justify-between">
|
<div className="absolute top-[37%] bottom-[37%] left-0 flex flex-col justify-between">
|
||||||
{Array.from({ length: 6 }).map((_, i) => (
|
{Array.from({ length: 3 }).map((_, i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="ml-0.5 h-3 w-0.5 rounded-full bg-sky-400/25 dark:bg-sky-400/15"
|
className="ml-0.5 h-3 w-0.5 rounded-full bg-sky-400/25 dark:bg-sky-400/15"
|
||||||
@@ -36,8 +36,8 @@ export function DeskRoomLayout({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Door indicator — RIGHT wall */}
|
{/* Door indicator — RIGHT wall */}
|
||||||
<div className="absolute top-[60%] right-0 h-8 w-1.5 rounded-l-sm bg-amber-400/25 dark:bg-amber-500/15" />
|
<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-[60%] right-1.5 translate-y-1 text-[8px] text-muted-foreground/30 select-none">
|
<div className="absolute top-[55%] right-1.5 translate-y-1 text-[8px] text-muted-foreground/30 select-none">
|
||||||
Ușă
|
Ușă
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
CaseUpper,
|
CaseUpper,
|
||||||
Palette,
|
Palette,
|
||||||
Upload,
|
Upload,
|
||||||
|
Receipt,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
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";
|
||||||
@@ -206,6 +207,103 @@ function PercentageCalculator() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TvaCalculator() {
|
||||||
|
const TVA_RATE = 19; // Romania standard VAT
|
||||||
|
const [amount, setAmount] = useState("");
|
||||||
|
const [mode, setMode] = useState<"add" | "extract">("add");
|
||||||
|
|
||||||
|
const val = parseFloat(amount);
|
||||||
|
const tvaMultiplier = TVA_RATE / 100;
|
||||||
|
|
||||||
|
const cuTva = !isNaN(val) && mode === "add" ? val * (1 + tvaMultiplier) : NaN;
|
||||||
|
const faraTva =
|
||||||
|
!isNaN(val) && mode === "extract" ? val / (1 + tvaMultiplier) : NaN;
|
||||||
|
const tvaAmount = !isNaN(val)
|
||||||
|
? mode === "add"
|
||||||
|
? val * tvaMultiplier
|
||||||
|
: val - val / (1 + tvaMultiplier)
|
||||||
|
: NaN;
|
||||||
|
|
||||||
|
const fmt = (n: number) =>
|
||||||
|
isNaN(n)
|
||||||
|
? "—"
|
||||||
|
: n.toLocaleString("ro-RO", {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant={mode === "add" ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setMode("add")}
|
||||||
|
>
|
||||||
|
Adaugă TVA
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={mode === "extract" ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setMode("extract")}
|
||||||
|
>
|
||||||
|
Extrage TVA
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>{mode === "add" ? "Sumă fără TVA" : "Sumă cu TVA"}</Label>
|
||||||
|
<div className="mt-1 flex gap-2">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={amount}
|
||||||
|
onChange={(e) => setAmount(e.target.value)}
|
||||||
|
placeholder={mode === "add" ? "Ex: 1000" : "Ex: 1190"}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<span className="flex items-center text-sm text-muted-foreground">
|
||||||
|
RON
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!isNaN(val) && val > 0 && (
|
||||||
|
<div className="rounded-md border bg-muted/30 p-4 space-y-2 text-sm">
|
||||||
|
{mode === "add" ? (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Sumă fără TVA: <strong>{fmt(val)} RON</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
TVA ({TVA_RATE}%): <strong>{fmt(tvaAmount)} RON</strong>
|
||||||
|
<CopyButton text={fmt(tvaAmount)} />
|
||||||
|
</p>
|
||||||
|
<p className="text-base pt-1 border-t">
|
||||||
|
Total cu TVA:{" "}
|
||||||
|
<strong className="text-primary">{fmt(cuTva)} RON</strong>
|
||||||
|
<CopyButton text={fmt(cuTva)} />
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Sumă cu TVA: <strong>{fmt(val)} RON</strong>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
TVA ({TVA_RATE}%): <strong>{fmt(tvaAmount)} RON</strong>
|
||||||
|
<CopyButton text={fmt(tvaAmount)} />
|
||||||
|
</p>
|
||||||
|
<p className="text-base pt-1 border-t">
|
||||||
|
Sumă fără TVA:{" "}
|
||||||
|
<strong className="text-primary">{fmt(faraTva)} RON</strong>
|
||||||
|
<CopyButton text={fmt(faraTva)} />
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function AreaConverter() {
|
function AreaConverter() {
|
||||||
const units = [
|
const units = [
|
||||||
{ key: "mp", label: "mp (m²)", factor: 1 },
|
{ key: "mp", label: "mp (m²)", factor: 1 },
|
||||||
@@ -1207,6 +1305,9 @@ export function MiniUtilitiesModule() {
|
|||||||
<TabsTrigger value="percentage">
|
<TabsTrigger value="percentage">
|
||||||
<Percent className="mr-1 h-3.5 w-3.5" /> Procente
|
<Percent className="mr-1 h-3.5 w-3.5" /> Procente
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="tva">
|
||||||
|
<Receipt className="mr-1 h-3.5 w-3.5" /> TVA
|
||||||
|
</TabsTrigger>
|
||||||
<TabsTrigger value="area">
|
<TabsTrigger value="area">
|
||||||
<Ruler className="mr-1 h-3.5 w-3.5" /> Suprafețe
|
<Ruler className="mr-1 h-3.5 w-3.5" /> Suprafețe
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
@@ -1263,6 +1364,16 @@ export function MiniUtilitiesModule() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
<TabsContent value="tva">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-base">Calculator TVA (19%)</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<TvaCalculator />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
<TabsContent value="area">
|
<TabsContent value="area">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
|
|||||||
Reference in New Issue
Block a user