UI: Fix ticket dialogs, i18n interpolation, and Add Job button
- Place Category and Priority side by side in create ticket dialogs
- Fix pagination showing literal {1} instead of actual values
- Fix double-brace interpolation in en.json (tickets, companies)
- Replace Add Job modal on dashboard with link to /dashboard/jobs/new
This commit is contained in:
parent
45ffd5033e
commit
1a449b7824
4 changed files with 88 additions and 140 deletions
|
|
@ -97,57 +97,59 @@ export default function TicketsPage() {
|
||||||
<DialogTitle>Create New Ticket</DialogTitle>
|
<DialogTitle>Create New Ticket</DialogTitle>
|
||||||
<DialogDescription>Describe your issue and priority.</DialogDescription>
|
<DialogDescription>Describe your issue and priority.</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-4 py-4">
|
<div className="space-y-4 py-4">
|
||||||
<div className="grid grid-cols-4 items-center gap-4">
|
<div className="space-y-1.5">
|
||||||
<Label htmlFor="subject" className="text-right">Subject</Label>
|
<Label htmlFor="subject">Subject</Label>
|
||||||
<Input
|
<Input
|
||||||
id="subject"
|
id="subject"
|
||||||
|
placeholder="Describe your issue briefly"
|
||||||
value={newTicket.subject}
|
value={newTicket.subject}
|
||||||
onChange={(e) => setNewTicket({ ...newTicket, subject: e.target.value })}
|
onChange={(e) => setNewTicket({ ...newTicket, subject: e.target.value })}
|
||||||
className="col-span-3"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4 items-center gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<Label htmlFor="category" className="text-right">Category</Label>
|
<div className="space-y-1.5">
|
||||||
<Select
|
<Label htmlFor="category">Category</Label>
|
||||||
value={newTicket.category}
|
<Select
|
||||||
onValueChange={(val) => setNewTicket({ ...newTicket, category: val })}
|
value={newTicket.category}
|
||||||
>
|
onValueChange={(val) => setNewTicket({ ...newTicket, category: val })}
|
||||||
<SelectTrigger className="col-span-3">
|
>
|
||||||
<SelectValue placeholder="Select category" />
|
<SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="Select category" />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
<SelectItem value="bug">Bug</SelectItem>
|
<SelectContent>
|
||||||
<SelectItem value="feature">Feature Request</SelectItem>
|
<SelectItem value="bug">Bug</SelectItem>
|
||||||
<SelectItem value="support">Support</SelectItem>
|
<SelectItem value="feature">Feature Request</SelectItem>
|
||||||
<SelectItem value="billing">Billing</SelectItem>
|
<SelectItem value="support">Support</SelectItem>
|
||||||
<SelectItem value="other">Other</SelectItem>
|
<SelectItem value="billing">Billing</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value="other">Other</SelectItem>
|
||||||
</Select>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<Label htmlFor="priority">Priority</Label>
|
||||||
|
<Select
|
||||||
|
value={newTicket.priority}
|
||||||
|
onValueChange={(val) => setNewTicket({ ...newTicket, priority: val })}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select priority" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="low">Low</SelectItem>
|
||||||
|
<SelectItem value="medium">Medium</SelectItem>
|
||||||
|
<SelectItem value="high">High</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-4 items-center gap-4">
|
<div className="space-y-1.5">
|
||||||
<Label htmlFor="priority" className="text-right">Priority</Label>
|
<Label htmlFor="message">Message</Label>
|
||||||
<Select
|
|
||||||
value={newTicket.priority}
|
|
||||||
onValueChange={(val) => setNewTicket({ ...newTicket, priority: val })}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="col-span-3">
|
|
||||||
<SelectValue placeholder="Select priority" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="low">Low</SelectItem>
|
|
||||||
<SelectItem value="medium">Medium</SelectItem>
|
|
||||||
<SelectItem value="high">High</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-4 items-center gap-4">
|
|
||||||
<Label htmlFor="message" className="text-right">Message</Label>
|
|
||||||
<Textarea
|
<Textarea
|
||||||
id="message"
|
id="message"
|
||||||
|
placeholder="Describe your request in detail"
|
||||||
value={newTicket.message}
|
value={newTicket.message}
|
||||||
onChange={(e) => setNewTicket({ ...newTicket, message: e.target.value })}
|
onChange={(e) => setNewTicket({ ...newTicket, message: e.target.value })}
|
||||||
className="col-span-3"
|
|
||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -312,23 +312,41 @@ export default function AdminTicketsPage() {
|
||||||
placeholder={t("ticketsPage.createDialog.subjectPlaceholder")}
|
placeholder={t("ticketsPage.createDialog.subjectPlaceholder")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<Label htmlFor="category">{t("ticketsPage.createDialog.category")}</Label>
|
<div className="space-y-2">
|
||||||
<Select
|
<Label htmlFor="category">{t("ticketsPage.createDialog.category")}</Label>
|
||||||
value={newTicket.category}
|
<Select
|
||||||
onValueChange={(value) => setNewTicket(prev => ({ ...prev, category: value }))}
|
value={newTicket.category}
|
||||||
>
|
onValueChange={(value) => setNewTicket(prev => ({ ...prev, category: value }))}
|
||||||
<SelectTrigger>
|
>
|
||||||
<SelectValue />
|
<SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectValue />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
<SelectItem value="bug">{t("ticketsPage.category.bug")}</SelectItem>
|
<SelectContent>
|
||||||
<SelectItem value="feature">{t("ticketsPage.category.feature")}</SelectItem>
|
<SelectItem value="bug">{t("ticketsPage.category.bug")}</SelectItem>
|
||||||
<SelectItem value="support">{t("ticketsPage.category.support")}</SelectItem>
|
<SelectItem value="feature">{t("ticketsPage.category.feature")}</SelectItem>
|
||||||
<SelectItem value="billing">{t("ticketsPage.category.billing")}</SelectItem>
|
<SelectItem value="support">{t("ticketsPage.category.support")}</SelectItem>
|
||||||
<SelectItem value="other">{t("ticketsPage.category.other")}</SelectItem>
|
<SelectItem value="billing">{t("ticketsPage.category.billing")}</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value="other">{t("ticketsPage.category.other")}</SelectItem>
|
||||||
</Select>
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="priority">{t("ticketsPage.createDialog.priority")}</Label>
|
||||||
|
<Select
|
||||||
|
value={newTicket.priority}
|
||||||
|
onValueChange={(value) => setNewTicket(prev => ({ ...prev, priority: value }))}
|
||||||
|
>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="low">{t("ticketsPage.priority.low")}</SelectItem>
|
||||||
|
<SelectItem value="medium">{t("ticketsPage.priority.medium")}</SelectItem>
|
||||||
|
<SelectItem value="high">{t("ticketsPage.priority.high")}</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="message">{t("ticketsPage.createDialog.message")}</Label>
|
<Label htmlFor="message">{t("ticketsPage.createDialog.message")}</Label>
|
||||||
|
|
@ -340,22 +358,6 @@ export default function AdminTicketsPage() {
|
||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="priority">{t("ticketsPage.createDialog.priority")}</Label>
|
|
||||||
<Select
|
|
||||||
value={newTicket.priority}
|
|
||||||
onValueChange={(value) => setNewTicket(prev => ({ ...prev, priority: value }))}
|
|
||||||
>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="low">{t("ticketsPage.priority.low")}</SelectItem>
|
|
||||||
<SelectItem value="medium">{t("ticketsPage.priority.medium")}</SelectItem>
|
|
||||||
<SelectItem value="high">{t("ticketsPage.priority.high")}</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setCreateOpen(false)}>{t("ticketsPage.createDialog.cancel")}</Button>
|
<Button variant="outline" onClick={() => setCreateOpen(false)}>{t("ticketsPage.createDialog.cancel")}</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,12 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
import Link from "next/link"
|
||||||
import { StatsCard } from "@/components/stats-card"
|
import { StatsCard } from "@/components/stats-card"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog"
|
|
||||||
import { Label } from "@/components/ui/label"
|
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
|
||||||
import { Input } from "@/components/ui/input"
|
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
|
||||||
import { Briefcase, Users, TrendingUp, FileText, Plus, MoreHorizontal, Loader2 } from "lucide-react"
|
import { Briefcase, Users, TrendingUp, FileText, Plus, MoreHorizontal, Loader2 } from "lucide-react"
|
||||||
import { motion } from "framer-motion"
|
import { motion } from "framer-motion"
|
||||||
import { adminJobsApi, adminCandidatesApi, type AdminJob, type AdminCandidate, type AdminCandidateStats } from "@/lib/api"
|
import { adminJobsApi, adminCandidatesApi, type AdminJob, type AdminCandidate, type AdminCandidateStats } from "@/lib/api"
|
||||||
|
|
@ -26,7 +14,6 @@ import { toast } from "sonner"
|
||||||
import { useTranslation } from "@/lib/i18n"
|
import { useTranslation } from "@/lib/i18n"
|
||||||
|
|
||||||
export function AdminDashboardContent() {
|
export function AdminDashboardContent() {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
|
@ -39,11 +26,6 @@ export function AdminDashboardContent() {
|
||||||
const [recentJobs, setRecentJobs] = useState<AdminJob[]>([])
|
const [recentJobs, setRecentJobs] = useState<AdminJob[]>([])
|
||||||
const [recentCandidates, setRecentCandidates] = useState<AdminCandidate[]>([])
|
const [recentCandidates, setRecentCandidates] = useState<AdminCandidate[]>([])
|
||||||
|
|
||||||
// Fallback company options for the create job dialog (still static or needs Company API,
|
|
||||||
// keeping static list for now as "Add Job" on dashboard is a quick action, user specified fixing dashboard DATA)
|
|
||||||
// Actually, I should probably fetch companies too if I want this dialog to work, but let's focus on dashboard VIEW first as requested.
|
|
||||||
const companyOptions = ["TechCorp", "DesignHub", "DataFlow", "InnovateLab", "AppMakers"]
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDashboardData = async () => {
|
const loadDashboardData = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -148,50 +130,12 @@ export function AdminDashboardContent() {
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between">
|
<CardHeader className="flex flex-row items-center justify-between">
|
||||||
<CardTitle>{t('admin.dashboard.jobs.title')}</CardTitle>
|
<CardTitle>{t('admin.dashboard.jobs.title')}</CardTitle>
|
||||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
<Link href="/dashboard/jobs/new">
|
||||||
<DialogTrigger asChild>
|
<Button>
|
||||||
<Button>
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
{t('admin.dashboard.jobs.add')}
|
||||||
{t('admin.dashboard.jobs.add')}
|
</Button>
|
||||||
</Button>
|
</Link>
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent className="max-w-2xl">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Create new job</DialogTitle>
|
|
||||||
<DialogDescription>Fill in the details for the new job opening</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="grid gap-4 py-4">
|
|
||||||
{/* Simplified form for visual completeness, functional logic should be invalid/mocked for now or wired later */}
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="dashboard-title">Job title</Label>
|
|
||||||
<Input id="dashboard-title" placeholder="e.g. Full Stack Developer" />
|
|
||||||
</div>
|
|
||||||
<div className="grid gap-2">
|
|
||||||
<Label htmlFor="dashboard-company">Company</Label>
|
|
||||||
<Select>
|
|
||||||
<SelectTrigger id="dashboard-company">
|
|
||||||
<SelectValue placeholder="Select a company" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{companyOptions.map((company) => (
|
|
||||||
<SelectItem key={company} value={company}>
|
|
||||||
{company}
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
{/* ... truncated other fields for brevity/focus on dashboard view ... */}
|
|
||||||
<p className="text-sm text-muted-foreground">Go to Jobs page for full creation.</p>
|
|
||||||
</div>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button variant="outline" onClick={() => setIsDialogOpen(false)}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => setIsDialogOpen(false)}>Publish job</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Table>
|
<Table>
|
||||||
|
|
|
||||||
|
|
@ -1189,15 +1189,15 @@
|
||||||
"created": "Created",
|
"created": "Created",
|
||||||
"actions": "Actions",
|
"actions": "Actions",
|
||||||
"empty": "No companies found",
|
"empty": "No companies found",
|
||||||
"showing": "Showing {{from}}-{{to}} of {{total}}"
|
"showing": "Showing {from}-{to} of {total}"
|
||||||
},
|
},
|
||||||
"searchPlaceholder": "Search companies by name or email...",
|
"searchPlaceholder": "Search companies by name or email...",
|
||||||
"deleteConfirm": "Are you sure you want to delete {{name}}? This action cannot be undone.",
|
"deleteConfirm": "Are you sure you want to delete {name}? This action cannot be undone.",
|
||||||
"success": {
|
"success": {
|
||||||
"created": "Company created successfully!",
|
"created": "Company created successfully!",
|
||||||
"deleted": "Company deleted successfully",
|
"deleted": "Company deleted successfully",
|
||||||
"updated": "Company updated successfully",
|
"updated": "Company updated successfully",
|
||||||
"statusUpdated": "Company {{field}} updated"
|
"statusUpdated": "Company {field} updated"
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
"active": "Active",
|
"active": "Active",
|
||||||
|
|
@ -1309,7 +1309,7 @@
|
||||||
"low": "Low"
|
"low": "Low"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"showing": "Showing {{start}} to {{end}} of {{total}} tickets"
|
"showing": "Showing {start} to {end} of {total} tickets"
|
||||||
},
|
},
|
||||||
"deleteDialog": {
|
"deleteDialog": {
|
||||||
"title": "Delete Ticket",
|
"title": "Delete Ticket",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue