Skip to content

Commit 64109d2

Browse files
committed
Merge branch 'master' into prod
2 parents 1c88676 + 7ffd9a7 commit 64109d2

2 files changed

Lines changed: 238 additions & 83 deletions

File tree

src/components/CompanyLogo.tsx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import styled from 'styled-components';
2+
3+
import React, { useState } from 'react';
4+
5+
import { color, padding } from '@Styles/theme';
6+
7+
const LogoContainer = styled.div`
8+
display: flex;
9+
justify-content: center;
10+
align-items: center;
11+
margin-bottom: ${padding.normal};
12+
background-color: ${color.background.lightGray};
13+
border-radius: 0.5rem;
14+
padding: ${padding.normal};
15+
min-height: 80px;
16+
17+
img {
18+
max-width: 100%;
19+
max-height: 120px;
20+
object-fit: contain;
21+
}
22+
`;
23+
24+
const FallbackText = styled.div`
25+
font-size: 0.875rem;
26+
color: ${color.inactive};
27+
text-align: center;
28+
font-style: italic;
29+
`;
30+
31+
interface ICompanyLogo {
32+
logoUrl?: string;
33+
companyName: string;
34+
}
35+
36+
export const CompanyLogo: React.FC<ICompanyLogo> = ({ logoUrl, companyName }) => {
37+
const [loadError, setLoadError] = useState(false);
38+
39+
if (!logoUrl || loadError) {
40+
return null;
41+
}
42+
43+
const handleImageError = () => {
44+
setLoadError(true);
45+
console.warn(`Failed to load logo for ${companyName} from ${logoUrl}`);
46+
};
47+
48+
return (
49+
<LogoContainer>
50+
<img
51+
src={logoUrl}
52+
alt={`${companyName} logo`}
53+
loading="lazy"
54+
onError={handleImageError}
55+
/>
56+
</LogoContainer>
57+
);
58+
};
59+
60+
export default CompanyLogo;

src/search/components/product-modal/ProductDetails.tsx

Lines changed: 178 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import { CompanyLogo } from '../../../components/CompanyLogo';
12
import React from 'react';
23
import { RussiaInfoBox } from '../results-list/RussiaInfoBox';
3-
import { Product, IManufacturer } from 'search';
4+
import { Product} from 'search';
45
import styled from 'styled-components';
56
import { ScoreBar } from '@Components/ScoreBar';
67
import { ScoreGauge } from '@Components/ScoreGauge';
@@ -98,6 +99,45 @@ const ManufacturerDesc = styled.p`
9899
color: ${color.text};
99100
`;
100101

102+
// ---- STYLE DLA LOGO MAREK ----
103+
const BrandsGrid = styled.div`
104+
display: grid;
105+
grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
106+
gap: 20px;
107+
margin-top: 16px;
108+
`;
109+
110+
111+
const BrandTile = styled.div`
112+
background: #fafafa;
113+
border-radius: 12px;
114+
padding: 10px;
115+
box-shadow: 0 2px 6px rgba(0,0,0,0.07);
116+
display: flex;
117+
justify-content: center;
118+
align-items: center;
119+
transition: transform .2s ease, box-shadow .2s ease;
120+
121+
&:hover {
122+
transform: translateY(-3px);
123+
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
124+
}
125+
`;
126+
127+
const BrandLogo = styled.img`
128+
max-height: 45px;
129+
max-width: 100%;
130+
object-fit: contain;
131+
`;
132+
133+
// ---- POMOCNICZA FUNKCJA ----
134+
const isRealUrl = (url?: string | null) => {
135+
if (!url) return false;
136+
const u = url.toLowerCase();
137+
return !u.includes("example.pl") && !u.includes("example.com");
138+
};
139+
140+
101141
// ---- KOMPONENTY ----
102142

103143
const CriterionItem: React.FC<{ condition: boolean; label: string }> = ({ condition, label }) => (
@@ -148,95 +188,150 @@ interface IProductDetails {
148188

149189
export const ProductDetails: React.FC<IProductDetails> = ({ product }) => {
150190
const manufacturer = product.manufacturer;
191+
const brandsWithLogo = manufacturer.brands?.filter(b => b.logotype_url) || [];
151192
const workersProperty = getPropertiesFromManufacturer(manufacturer, PolishPropertyName.WORKERS);
152193
const researchProperty = getPropertiesFromManufacturer(manufacturer, PolishPropertyName.RnD);
153194
const registeredProperty = getPropertiesFromManufacturer(manufacturer, PolishPropertyName.REGISTERED);
154195
const notGlobalProperty = getPropertiesFromManufacturer(manufacturer, PolishPropertyName.NOT_GLOBAL);
155196
const capitalProperty = getPropertiesFromManufacturer(manufacturer, PolishPropertyName.CAPITAL);
156197

157-
return (
158-
<DetailsContainer>
159-
<Header>
160-
<h3>
161-
{product.name}
162-
</h3>
163-
</Header>
164-
<RussiaInfoBox product={product} />
165-
166-
{/* Ocena + progress bar */}
167-
<ScoreRow>
168-
{product.manufacturer.plScore === 100 && (
169-
<div style={{
170-
background: '#ffe5e5',
171-
color: '#c10028',
172-
padding: '10px 0',
173-
textAlign: 'center',
174-
fontWeight: '600',
175-
borderRadius: '8px',
176-
display: 'flex',
177-
justifyContent: 'space-between',
178-
alignItems: 'center',
179-
paddingLeft: '20px',
180-
paddingRight: '20px'
181-
}}>
182-
<span>❤️</span>
183-
<span>Ta firma jest Przyjacielem Poli</span>
184-
<span>❤️</span>
185-
</div>
186-
)}
187-
<ScoreLabel>
188-
<img src={InfoIcon} alt="Info" style={{ width: '14px', marginRight: '4px' }} />
189-
Nasza ocena: <b>{product.manufacturer.plScore ?? "-"}</b> pkt
190-
</ScoreLabel>
191-
<ScoreBarWrapper>
192-
<ScoreBar
193-
value={product.manufacturer.plScore}
194-
unit="pkt"
195-
missingValuePlaceholder="brak punktacji w rankingu Poli"
196-
animation={{ duration: 1, delay: 0.2 }}
197-
/>
198-
</ScoreBarWrapper>
199-
</ScoreRow>
200-
201-
{/* Kryteria oceniania */}
202-
<Heading><h2>Kryteria oceniania:</h2></Heading>
203-
<CriteriaRow>
204-
<ScoreGauge
205-
value={capitalProperty.value}
206-
unit="%"
207-
animation={{ duration: 1 }}
208-
missingValuePlaceholder="—"
209-
/>
210-
211-
<CriteriaList>
212-
<CriterionItem
213-
condition={workersProperty.value === 100}
214-
label="Produkuje w Polsce"
215-
/>
216-
<CriterionItem
217-
condition={researchProperty.value === 100}
218-
label="Prowadzi badania w Polsce"
219-
/>
220-
<CriterionItem
221-
condition={registeredProperty.value === 100}
222-
label="Zarejestrowana w Polsce"
223-
/>
224-
<CriterionItem
225-
condition={notGlobalProperty.value === 100}
226-
label="Nie jest częścią zagranicznego koncernu"
198+
return (
199+
<DetailsContainer>
200+
<Header>
201+
<h3>
202+
{product.name}
203+
</h3>
204+
</Header>
205+
<RussiaInfoBox product={product} />
206+
207+
{/* Ocena + progress bar */}
208+
<ScoreRow>
209+
{product.manufacturer.plScore === 100 && (
210+
<div style={{
211+
background: '#ffe5e5',
212+
color: '#c10028',
213+
padding: '10px 0',
214+
textAlign: 'center',
215+
fontWeight: '600',
216+
borderRadius: '8px',
217+
display: 'flex',
218+
justifyContent: 'space-between',
219+
alignItems: 'center',
220+
paddingLeft: '20px',
221+
paddingRight: '20px'
222+
}}>
223+
<span>❤️</span>
224+
<span>Ta firma jest Przyjacielem Poli</span>
225+
<span>❤️</span>
226+
</div>
227+
)}
228+
<ScoreLabel>
229+
<img src={InfoIcon} alt="Info" style={{ width: '14px', marginRight: '4px' }} />
230+
Nasza ocena: <b>{product.manufacturer.plScore ?? "-"}</b> pkt
231+
</ScoreLabel>
232+
<ScoreBarWrapper>
233+
<ScoreBar
234+
value={product.manufacturer.plScore}
235+
unit="pkt"
236+
missingValuePlaceholder="brak punktacji w rankingu Poli"
237+
animation={{ duration: 1, delay: 0.2 }}
238+
/>
239+
</ScoreBarWrapper>
240+
</ScoreRow>
241+
242+
{/* Kryteria oceniania */}
243+
<Heading><h2>Kryteria oceniania:</h2></Heading>
244+
<CriteriaRow>
245+
<ScoreGauge
246+
value={capitalProperty.value}
247+
unit="%"
248+
animation={{ duration: 1 }}
249+
missingValuePlaceholder="—"
227250
/>
228-
<br />
229-
</CriteriaList>
230-
</CriteriaRow>
251+
252+
<CriteriaList>
253+
<CriterionItem
254+
condition={workersProperty.value === 100}
255+
label="Produkuje w Polsce"
256+
/>
257+
<CriterionItem
258+
condition={researchProperty.value === 100}
259+
label="Prowadzi badania w Polsce"
260+
/>
261+
<CriterionItem
262+
condition={registeredProperty.value === 100}
263+
label="Zarejestrowana w Polsce"
264+
/>
265+
<CriterionItem
266+
condition={notGlobalProperty.value === 100}
267+
label="Nie jest częścią zagranicznego koncernu"
268+
/>
269+
<br />
270+
</CriteriaList>
271+
</CriteriaRow>
231272

232-
{/* Notatki o kapitale, producent */}
233-
{AppSettings.SHOW_POLISH_VALUE_NOTES && capitalProperty.notes && (
234-
<Notes>{capitalProperty.notes}</Notes>
235-
)}
273+
{/* Notatki o kapitale, producent */}
274+
{AppSettings.SHOW_POLISH_VALUE_NOTES && capitalProperty.notes && (
275+
<Notes>{capitalProperty.notes}</Notes>
276+
)}
236277

237-
{product.manufacturer.description && (
238-
<ManufacturerDesc><ReadMoreArea text={product.manufacturer.description} maxLength={184} /></ManufacturerDesc>
239-
)}
240-
</DetailsContainer>
241-
);
278+
{product.manufacturer.description && (
279+
<ManufacturerDesc><ReadMoreArea text={product.manufacturer.description} maxLength={184} /></ManufacturerDesc>
280+
)}
281+
282+
{/* Logo producenta */}
283+
{manufacturer.logotype_url && (
284+
isRealUrl(manufacturer.official_url) ? (
285+
<a
286+
href={manufacturer.official_url}
287+
target="_blank"
288+
rel="noopener noreferrer"
289+
style={{ marginTop: "-15px", display: "block" }}
290+
>
291+
<CompanyLogo
292+
logoUrl={manufacturer.logotype_url}
293+
companyName={manufacturer.name}
294+
/>
295+
</a>
296+
) : (
297+
<div style={{ marginTop: "10px" }}>
298+
<CompanyLogo
299+
logoUrl={manufacturer.logotype_url}
300+
companyName={manufacturer.name}
301+
/>
302+
</div>
303+
)
304+
)}
305+
306+
307+
{/* Logo marek */}
308+
{brandsWithLogo.length > 0 && (
309+
<div style={{ marginTop: "20px" }}>
310+
<h4 style={{ marginBottom: "10px", fontWeight: "700", color: "#212121" }}>
311+
Marki producenta:
312+
</h4>
313+
314+
<BrandsGrid>
315+
{brandsWithLogo.map((brand, i) => (
316+
<BrandTile key={i}>
317+
{isRealUrl(brand.website_url) ? (
318+
<a
319+
href={brand.website_url}
320+
target="_blank"
321+
rel="noopener noreferrer"
322+
style={{ display: "flex", justifyContent: "center" }}
323+
>
324+
<BrandLogo src={brand.logotype_url} alt={brand.name} />
325+
</a>
326+
) : (
327+
<BrandLogo src={brand.logotype_url} alt={brand.name} />
328+
)}
329+
</BrandTile>
330+
))}
331+
</BrandsGrid>
332+
</div>
333+
)}
334+
335+
</DetailsContainer>
336+
);
242337
};

0 commit comments

Comments
 (0)