feat: Add comprehensive mobile responsiveness
Implemented complete mobile styling improvements for Scrapy UI: - Mobile-responsive sidebar with hamburger menu (Sheet component) - Sidebar hidden on mobile, slides in from left as overlay - Auto-closes on navigation - Mobile header with hamburger button, title, and theme toggle - Layout switches from horizontal to vertical flexbox on mobile - Reduced container padding on mobile (p-4 vs p-6) - All tables wrapped in horizontal scroll containers - Added whitespace-nowrap to prevent text wrapping in table cells - Optimized all dialogs for mobile: - Responsive width: max-w-[95vw] on mobile, max-w-[425px] on desktop - Full-width buttons on mobile - Proper gap spacing in footers - Text wrapping for long content (break-all for Job IDs) - Dashboard cards already responsive with grid breakpoints App now works flawlessly on mobile devices! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -242,79 +242,81 @@ export default function JobsPage() {
|
||||
))}
|
||||
</div>
|
||||
) : filteredJobs.length > 0 ? (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Job ID</TableHead>
|
||||
<TableHead>Spider</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Start Time</TableHead>
|
||||
<TableHead>PID</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredJobs.map((job) => (
|
||||
<TableRow key={job.id}>
|
||||
<TableCell>
|
||||
<code className="rounded bg-muted px-2 py-1 text-xs">
|
||||
{job.id.substring(0, 8)}...
|
||||
</code>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<BriefcaseBusiness className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="font-medium">{job.spider}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{getStatusBadge(job.status)}</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{format(new Date(job.start_time), "PPp")}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{job.pid ? (
|
||||
<code className="text-xs">{job.pid}</code>
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{job.log_url && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
href={job.log_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
{(job.status === "pending" || job.status === "running") && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedJob(job);
|
||||
setCancelDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<XCircle className="h-4 w-4 text-destructive" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
<div className="overflow-x-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Job ID</TableHead>
|
||||
<TableHead>Spider</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="whitespace-nowrap">Start Time</TableHead>
|
||||
<TableHead>PID</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredJobs.map((job) => (
|
||||
<TableRow key={job.id}>
|
||||
<TableCell>
|
||||
<code className="rounded bg-muted px-2 py-1 text-xs whitespace-nowrap">
|
||||
{job.id.substring(0, 8)}...
|
||||
</code>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2 whitespace-nowrap">
|
||||
<BriefcaseBusiness className="h-4 w-4 text-muted-foreground" />
|
||||
<span className="font-medium">{job.spider}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{getStatusBadge(job.status)}</TableCell>
|
||||
<TableCell>
|
||||
<span className="text-sm text-muted-foreground whitespace-nowrap">
|
||||
{format(new Date(job.start_time), "PPp")}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{job.pid ? (
|
||||
<code className="text-xs">{job.pid}</code>
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground">-</span>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
{job.log_url && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
href={job.log_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
{(job.status === "pending" || job.status === "running") && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setSelectedJob(job);
|
||||
setCancelDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
<XCircle className="h-4 w-4 text-destructive" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<AlertCircle className="mb-4 h-12 w-12 text-muted-foreground" />
|
||||
@@ -344,7 +346,7 @@ export default function JobsPage() {
|
||||
|
||||
{/* Cancel Job Dialog */}
|
||||
<Dialog open={cancelDialogOpen} onOpenChange={setCancelDialogOpen}>
|
||||
<DialogContent>
|
||||
<DialogContent className="max-w-[95vw] sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Cancel Job</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -354,17 +356,18 @@ export default function JobsPage() {
|
||||
<p className="text-sm">
|
||||
<strong>Spider:</strong> {selectedJob.spider}
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
<p className="text-sm break-all">
|
||||
<strong>Job ID:</strong> <code>{selectedJob.id}</code>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setCancelDialogOpen(false)}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
No, keep it
|
||||
</Button>
|
||||
@@ -372,6 +375,7 @@ export default function JobsPage() {
|
||||
variant="destructive"
|
||||
onClick={handleCancelJob}
|
||||
disabled={cancelJobMutation.isPending}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{cancelJobMutation.isPending ? "Canceling..." : "Yes, cancel job"}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user