File size: 5,554 Bytes
16533c4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { useState } from "react";
import { ChevronDown, ChevronUp, Eye } from "lucide-react";
import type { ImageResult } from "@/types";
import { getImageUrl } from "@/services/api";

interface ImageResultCardProps {
  result: ImageResult;
  index: number;
}

export function ImageResultCard({ result, index }: ImageResultCardProps) {
  const [expanded, setExpanded] = useState(false);

  const hasError = !!result.error;

  return (
    <div
      className={[
        "rounded-xl border transition-all duration-200",
        hasError
          ? "border-red-500/20 bg-red-500/5"
          : "border-surface-500 bg-surface-700 hover:border-surface-400",
      ].join(" ")}
    >
      <div
        className="flex items-center gap-4 p-4 cursor-pointer"
        onClick={() => setExpanded((e) => !e)}
      >
        {/* Index badge */}
        <div className="w-8 h-8 rounded-lg bg-surface-600 flex items-center justify-center flex-shrink-0">
          <span className="text-xs font-mono text-slate-400">{index + 1}</span>
        </div>

        {/* Preview thumbnail */}
        {result.previewUrl && (
          <div className="w-12 h-12 rounded-lg overflow-hidden flex-shrink-0 border border-surface-500">
            <img
              src={result.previewUrl}
              alt={result.filename}
              className="w-full h-full object-cover"
            />
          </div>
        )}

        {/* Name + status */}
        <div className="flex-1 min-w-0">
          <p className="font-medium text-white text-sm truncate">{result.filename}</p>
          {hasError ? (
            <p className="text-red-400 text-xs mt-0.5">
              {typeof result.error === "string" ? result.error : JSON.stringify(result.error)}
            </p>
          ) : (
            <div className="flex items-center gap-2 mt-1 flex-wrap">
              {result.angles.map((angle, i) => (
                <span
                  key={i}
                  className="text-xs px-2 py-0.5 rounded-full bg-brand-500/15 text-brand-400 font-medium border border-brand-500/20"
                >
                  {angle}
                </span>
              ))}
              {result.review && (
                <span className="text-xs px-2 py-0.5 rounded-full bg-amber-500/15 text-amber-400 font-medium border border-amber-500/20">
                  Needs review
                </span>
              )}
            </div>
          )}
        </div>

        {/* Expand */}
        <div className="flex-shrink-0 text-slate-500">
          {expanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
        </div>
      </div>

      {/* Expanded view */}
      {expanded && !hasError && (
        <div className="px-4 pb-4 border-t border-surface-600 pt-4 animate-fade-in space-y-4">
          {/* Local preview always shown */}
          {result.previewUrl && (
            <div>
              <p className="text-xs text-slate-400 mb-2 font-medium uppercase tracking-wider">
                Uploaded Image
              </p>
              <div className="rounded-lg overflow-hidden border border-surface-500 max-w-sm">
                <img
                  src={result.previewUrl}
                  alt={result.filename}
                  className="w-full object-contain max-h-64 bg-surface-900"
                />
              </div>
            </div>
          )}

          {/* Backend detection overlays (only when available) */}
          {result.fileDetails && (
            <div>
              <p className="text-xs text-slate-400 mb-2 font-medium uppercase tracking-wider">
                Detection Outputs
              </p>
              <div className="grid grid-cols-3 gap-3">
                {[
                  { label: "Original", path: result.fileDetails.main_img_name },
                  { label: "Car Detection", path: result.fileDetails.car_detection },
                  { label: "Part Detection", path: result.fileDetails.part_detection },
                ].map(({ label, path }) => (
                  <a
                    key={label}
                    href={getImageUrl(path)}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="group relative rounded-lg overflow-hidden border border-surface-500 hover:border-brand-500/50 transition-colors block"
                  >
                    <div className="aspect-square bg-surface-800">
                      <img
                        src={getImageUrl(path)}
                        alt={label}
                        className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-105"
                        onError={(e) => {
                          e.currentTarget.style.display = "none";
                        }}
                      />
                    </div>
                    <div className="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-end p-2">
                      <span className="text-white text-xs font-medium flex items-center gap-1">
                        <Eye className="w-3 h-3" /> {label}
                      </span>
                    </div>
                    <p className="text-center text-xs text-slate-400 py-1.5 bg-surface-800">
                      {label}
                    </p>
                  </a>
                ))}
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}