16 KiB
Reports Frontend Rework Plan
Overview
This plan outlines the modernization of Reports frontend pages (Index.vue and Show.vue) using shadcn-vue components and AppCard containers, following the same patterns established in the Settings pages rework.
Current State Analysis
Reports/Index.vue (30 lines)
Current Implementation:
- Simple grid layout with native divs
- Report cards:
border rounded-lg p-4 bg-white shadow-sm hover:shadow-md - Grid:
md:grid-cols-2 lg:grid-cols-3 - Each card shows: name (h2), description (p), Link to report
- No shadcn-vue components used
Identified Issues:
- Native HTML/Tailwind instead of shadcn-vue Card
- Inconsistent with Settings pages styling
- No icons for visual interest
- Basic hover effects only
Reports/Show.vue (314 lines)
Current Implementation:
- Complex page with filters, export buttons, and data table
- Header section: title, description, export buttons (lines 190-196)
- Buttons:
px-3 py-2 rounded bg-gray-200 hover:bg-gray-300
- Buttons:
- Filter section: grid layout
md:grid-cols-4(lines 218-270)- Native inputs:
border rounded px-2 py-1 - Native selects:
border rounded px-2 py-1 - DatePicker component (already working)
- Filter buttons: Apply (
bg-indigo-600) and Reset (bg-gray-100)
- Native inputs:
- Data table: DataTableServer component (lines 285-300)
- Formatting functions: formatNumberEU, formatDateEU, formatDateTimeEU, formatCell
Identified Issues:
- No Card containers for sections
- Native buttons instead of shadcn Button
- Native input/select elements instead of shadcn Input/Select
- No visual separation between sections
- Filter section could be extracted to partial
Target Architecture
Pattern Reference from Settings Pages
Settings/Index.vue Pattern:
<Card class="hover:shadow-lg transition-shadow">
<CardHeader>
<div class="flex items-center gap-2">
<component :is="icon" class="h-5 w-5 text-muted-foreground" />
<CardTitle>Title</CardTitle>
</div>
<CardDescription>Description</CardDescription>
</CardHeader>
<CardContent>
<Button variant="ghost">Action →</Button>
</CardContent>
</Card>
Settings/Archive/Index.vue Pattern:
- Uses AppCard for main container
- Extracted partials: ArchiveRuleCard, CreateRuleForm, EditRuleForm
- Alert components for warnings
- Badge components for status indicators
Implementation Plan
Phase 1: Reports/Index.vue Rework (Simple)
Goal: Replace native divs with shadcn-vue Card components, add icons
Changes:
-
Import shadcn-vue components:
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/Components/ui/card"; import { Button } from "@/Components/ui/button"; import { BarChart3, FileText, Activity, Users, TrendingUp, Calendar } from "lucide-vue-next"; -
Add icon mapping for reports:
const reportIcons = { 'contracts': FileText, 'field': TrendingUp, 'activities': Activity, // fallback icon default: BarChart3, }; function getReportIcon(category) { return reportIcons[category] || reportIcons.default; } -
Replace report card structure:
- Remove native
<div class="border rounded-lg p-4 bg-white shadow-sm hover:shadow-md"> - Use
<Card class="hover:shadow-lg transition-shadow cursor-pointer"> - Structure:
<Card> <CardHeader> <div class="flex items-center gap-2"> <component :is="getReportIcon(report.category)" class="h-5 w-5 text-muted-foreground" /> <CardTitle>{{ report.name }}</CardTitle> </div> <CardDescription>{{ report.description }}</CardDescription> </CardHeader> <CardContent> <Link :href="route('reports.show', report.slug)"> <Button variant="ghost" size="sm" class="w-full justify-start"> Odpri → </Button> </Link> </CardContent> </Card>
- Remove native
-
Update page header:
- Wrap in proper container with consistent spacing
- Match Settings/Index.vue header style
Estimated Changes:
- Lines: 30 → ~65 lines (with imports and icon logic)
- Files modified: 1 (Index.vue)
- Files created: 0
Risk Level: Low (simple page, straightforward replacement)
Phase 2: Reports/Show.vue Rework - Structure (Medium)
Goal: Add Card containers for sections, replace native buttons
Changes:
-
Import shadcn-vue components:
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/Components/ui/card"; import { Button } from "@/Components/ui/button"; import { Input } from "@/Components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/Components/ui/select"; import { Label } from "@/Components/ui/label"; import { Badge } from "@/Components/ui/badge"; import { Separator } from "@/Components/ui/separator"; import { Download, Filter, RotateCcw } from "lucide-vue-next"; -
Wrap header + export buttons in Card:
<Card class="mb-6"> <CardHeader> <div class="flex items-start justify-between"> <div> <CardTitle>{{ name }}</CardTitle> <CardDescription v-if="description">{{ description }}</CardDescription> </div> <div class="flex gap-2"> <Button variant="outline" size="sm" @click="exportFile('csv')"> <Download class="mr-2 h-4 w-4" /> CSV </Button> <Button variant="outline" size="sm" @click="exportFile('pdf')"> <Download class="mr-2 h-4 w-4" /> PDF </Button> <Button variant="outline" size="sm" @click="exportFile('xlsx')"> <Download class="mr-2 h-4 w-4" /> Excel </Button> </div> </div> </CardHeader> </Card> -
Wrap filters in Card:
<Card class="mb-6"> <CardHeader> <div class="flex items-center gap-2"> <Filter class="h-5 w-5 text-muted-foreground" /> <CardTitle>Filtri</CardTitle> </div> </CardHeader> <CardContent> <!-- Filter grid here --> <div class="grid gap-4 md:grid-cols-4"> <!-- Filter inputs --> </div> <Separator class="my-4" /> <div class="flex gap-2"> <Button @click="applyFilters"> <Filter class="mr-2 h-4 w-4" /> Prikaži </Button> <Button variant="outline" @click="resetFilters"> <RotateCcw class="mr-2 h-4 w-4" /> Ponastavi </Button> </div> </CardContent> </Card> -
Wrap DataTableServer in Card:
<Card> <CardHeader> <CardTitle>Rezultati</CardTitle> <CardDescription> Skupaj {{ meta?.total || 0 }} {{ meta?.total === 1 ? 'rezultat' : 'rezultatov' }} </CardDescription> </CardHeader> <CardContent> <DataTableServer <!-- props --> /> </CardContent> </Card> -
Replace all native buttons with shadcn Button:
- Export buttons:
variant="outline" size="sm" - Apply filter button: default variant
- Reset button:
variant="outline"
- Export buttons:
Estimated Changes:
- Lines: 314 → ~350 lines (with imports and Card wrappers)
- Files modified: 1 (Show.vue)
- Files created: 0
- Keep formatting functions unchanged (working correctly)
Risk Level: Low-Medium (more complex but no logic changes)
Phase 3: Reports/Show.vue - Replace Native Inputs (Medium)
Goal: Replace native input/select elements with shadcn-vue components
Changes:
-
Replace date inputs:
<!-- Keep DatePicker as-is (already working) --> <div class="space-y-2"> <Label>{{ inp.label || inp.key }}</Label> <DatePicker v-model="filters[inp.key]" format="dd.MM.yyyy" placeholder="Izberi datum" /> </div> -
Replace text/number inputs:
<div class="space-y-2"> <Label>{{ inp.label || inp.key }}</Label> <Input v-model="filters[inp.key]" :type="inp.type === 'integer' ? 'number' : 'text'" placeholder="Vnesi vrednost" /> </div> -
Replace select inputs (user/client):
<div class="space-y-2"> <Label>{{ inp.label || inp.key }}</Label> <Select v-model="filters[inp.key]"> <SelectTrigger> <SelectValue placeholder="— brez —" /> </SelectTrigger> <SelectContent> <SelectItem :value="null">— brez —</SelectItem> <SelectItem v-for="u in userOptions" :key="u.id" :value="u.id"> {{ u.name }} </SelectItem> </SelectContent> </Select> <div v-if="userLoading" class="text-xs text-muted-foreground">Nalagam…</div> </div> -
Update filter grid layout:
- Change from
md:grid-cols-4tomd:grid-cols-2 lg:grid-cols-4 - Use
space-y-2for label/input spacing - Consistent gap:
gap-4
- Change from
Estimated Changes:
- Lines: ~350 → ~380 lines (shadcn Input/Select have more markup)
- Files modified: 1 (Show.vue)
- Files created: 0
Risk Level: Medium (v-model binding changes, test thoroughly)
Phase 4: Optional - Extract Filter Section Partial (Optional)
Goal: Reduce Show.vue complexity by extracting filter logic
Decision Criteria:
- If filter section exceeds ~80 lines → extract to partial
- If multiple filter types need separate handling → extract
Potential Partial Structure:
resources/js/Pages/Reports/Partials/
FilterSection.vue
FilterSection.vue:
- Props:
inputs,filters(reactive object),userOptions,clientOptions,loading states - Emits:
@apply,@reset - Contains: entire filter grid + buttons
Benefits:
- Show.vue reduced from ~380 lines to ~300 lines
- Filter logic isolated and reusable
- Easier to maintain filter types
Risks:
- Adds complexity with props/emits
- Might not be worth it if filter logic is simple
Recommendation: Evaluate after Phase 3 completion. If filter section is clean and under 80 lines, skip this phase.
Component Inventory
shadcn-vue Components Needed
Already Installed (verify):
- Card, CardHeader, CardTitle, CardDescription, CardContent
- Button
- Input
- Select, SelectTrigger, SelectValue, SelectContent, SelectItem
- Label
- Badge
- Separator
Need to Check:
- lucide-vue-next icons (Download, Filter, RotateCcw, BarChart3, FileText, Activity, TrendingUp, Calendar)
Custom Components
- AppCard (if needed for consistency)
- DatePicker (already working, keep as-is)
- DataTableServer (keep as-is)
Testing Checklist
Reports/Index.vue Testing:
- Cards display with correct icons
- Card hover effects work
- Links navigate to correct report
- Grid layout responsive (2 cols MD, 3 cols LG)
- Icons match report categories
Reports/Show.vue Testing:
- Header Card displays title, description, export buttons
- Export buttons work (CSV, PDF, Excel)
- Filter Card displays all filter inputs correctly
- Date filters use DatePicker component
- User/Client selects load options async
- Apply filters button triggers report refresh
- Reset button clears all filters
- DataTableServer Card displays results
- Formatting functions work (dates, numbers, currencies)
- Pagination works
- All 6 reports render correctly:
- active-contracts
- field-jobs-completed
- decisions-counts
- segment-activity-counts
- actions-decisions-counts
- activities-per-period
Implementation Order
Step 1: Reports/Index.vue (30 min)
- Import shadcn-vue components + icons
- Add icon mapping function
- Replace native divs with Card structure
- Test navigation and layout
- Verify responsive grid
Step 2: Reports/Show.vue - Structure (45 min)
- Import shadcn-vue components + icons
- Wrap header + exports in Card
- Wrap filters in Card
- Wrap DataTableServer in Card
- Replace all native buttons
- Test all 6 reports
Step 3: Reports/Show.vue - Inputs (60 min)
- Replace text/number inputs with shadcn Input
- Replace select inputs with shadcn Select
- Add Label components
- Test v-model bindings
- Test async user/client loading
- Test filter apply/reset
- Verify all filter types work
Step 4: Optional Partial Extraction (30 min, if needed)
- Create FilterSection.vue partial
- Move filter logic to partial
- Set up props/emits
- Test with all reports
Step 5: Final Testing (30 min)
- Test complete workflow (Index → Show → Filters → Export)
- Verify all 6 reports
- Test responsive layouts (mobile, tablet, desktop)
- Check formatting consistency
- Verify no regressions
Total Estimated Time: 2.5 - 3.5 hours
Risk Assessment
Low Risk:
- Index.vue rework (simple structure, straightforward replacement)
- Adding Card containers to Show.vue
- Replacing native buttons with shadcn Button
Medium Risk:
- Replacing native inputs with shadcn Input/Select
- v-model bindings might need adjustments
- Async select loading needs testing
- Number input behavior might differ
Mitigation Strategies:
- Test each phase incrementally
- Keep formatting functions unchanged (already working)
- Test v-model bindings immediately after input replacement
- Verify async loading with console logs
- Test all 6 reports after each phase
- Keep git commits small and atomic
Success Criteria
Functional Requirements:
✅ All reports navigate from Index page
✅ All filters work correctly (date, text, number, user select, client select)
✅ Apply filters refreshes report data
✅ Reset filters clears all inputs
✅ Export buttons generate CSV/PDF/Excel files
✅ DataTableServer displays results correctly
✅ Pagination works
✅ Formatting functions work (dates, numbers)
Visual Requirements:
✅ Consistent Card-based layout
✅ shadcn-vue components throughout
✅ Icons for visual interest
✅ Hover effects on cards
✅ Proper spacing and alignment
✅ Responsive layout (mobile, tablet, desktop)
✅ Matches Settings pages style
Code Quality:
✅ No code duplication
✅ Clean component imports
✅ Consistent naming conventions
✅ Proper TypeScript/Vue 3 patterns
✅ Formatting functions unchanged
✅ No regressions in functionality
Notes
- DatePicker component: Already working, imported correctly, no changes needed
- Formatting functions: Keep unchanged (formatNumberEU, formatDateEU, formatDateTimeEU, formatCell)
- DataTableServer: Keep as-is, already working well
- Async loading: User/client select loading works, just needs shadcn Select wrapper
- Pattern consistency: Follow Settings/Index.vue and Settings/Archive/Index.vue patterns
- Icon usage: Add icons to Index.vue for visual interest, use lucide-vue-next
- Button variants: Use
variant="outline"for secondary actions, default for primary
Post-Implementation
After completing all phases:
-
Documentation:
- Update this document with actual implementation notes
- Document any deviations from plan
- Note any unexpected issues
-
Code Review:
- Check for consistent component usage
- Verify no native HTML/CSS buttons/inputs remain
- Ensure proper import structure
-
User Feedback:
- Test with actual users
- Gather feedback on UI improvements
- Note any requested adjustments
-
Performance:
- Verify no performance regressions
- Check bundle size impact
- Monitor async loading times
Conclusion
This plan provides a structured approach to modernizing the Reports frontend pages using shadcn-vue components. The phased approach allows for incremental testing and reduces risk. The estimated total time is 2.5-3.5 hours, with low to medium risk level.
Recommendation: Start with Phase 1 (Index.vue) as a proof of concept, then proceed to Phase 2 and 3 for Show.vue. Evaluate Phase 4 (partial extraction) after Phase 3 completion based on actual complexity.