[34588f42] Feature: BCC-Kopie an Kontaktadresse und UI-Übersicht für Formularantworten integriert
This commit is contained in:
@@ -1 +1 @@
|
||||
{"task_id": "34588f42-8544-8046-85d4-d7895ed9b29c", "token": "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", "readme_path": "readme.md", "session_start_time": "2026-04-17T22:14:17.456915"}
|
||||
{"task_id": "34588f42-8544-8046-85d4-d7895ed9b29c", "token": "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", "readme_path": null, "session_start_time": "2026-04-18T11:12:01.291297"}
|
||||
@@ -121,6 +121,7 @@ class GmailService:
|
||||
message = MIMEText(body_html, 'html')
|
||||
message['to'] = to
|
||||
message['subject'] = subject
|
||||
message['bcc'] = 'kontakt@kinderfotos-erding.de'
|
||||
|
||||
raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
||||
|
||||
|
||||
@@ -118,6 +118,11 @@ def get_stats(db: Session = Depends(get_db)):
|
||||
available = total - used
|
||||
return {"total": total, "used": used, "available": available}
|
||||
|
||||
@router.get("/responses")
|
||||
def get_responses(db: Session = Depends(get_db)):
|
||||
responses = db.query(DiscountCode).filter(DiscountCode.is_used == 1).all()
|
||||
return [{"email": r.assigned_to_email, "code": r.code, "used_at": r.used_at.isoformat()} for r in responses]
|
||||
|
||||
@router.post("/codes")
|
||||
def upload_codes(data: CodesUpload, db: Session = Depends(get_db)):
|
||||
codes_list = [c.strip() for c in data.codes.split(",") if c.strip()]
|
||||
|
||||
@@ -62,6 +62,8 @@ function App() {
|
||||
const [isSendingRelease, setIsSendingRelease] = useState(false);
|
||||
const [releaseMessage, setReleaseMessage] = useState("");
|
||||
const [scheduledTime, setScheduledTime] = useState(""); // New state
|
||||
const [releaseResponses, setReleaseResponses] = useState<any[] | null>(null);
|
||||
const [isFetchingResponses, setIsFetchingResponses] = useState(false);
|
||||
|
||||
const fetchReleaseStats = async () => {
|
||||
try {
|
||||
@@ -75,6 +77,20 @@ function App() {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchReleaseResponses = async () => {
|
||||
setIsFetchingResponses(true);
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/publish-request/responses`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setReleaseResponses(data);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch release responses", e);
|
||||
}
|
||||
setIsFetchingResponses(false);
|
||||
};
|
||||
|
||||
const handleUploadCodes = async () => {
|
||||
setIsUploadingCodes(true);
|
||||
setUploadMessage("Lädt hoch...");
|
||||
@@ -248,6 +264,8 @@ function App() {
|
||||
}
|
||||
fetchLatestFile();
|
||||
checkGmailAuth();
|
||||
fetchReleaseStats();
|
||||
fetchReleaseResponses();
|
||||
}, [activeTab]);
|
||||
|
||||
const handleRefresh = () => fetchJobs(activeTab, true);
|
||||
@@ -1053,6 +1071,45 @@ function App() {
|
||||
{!isGmailAuthenticated && <p className="text-center text-[10px] mt-2 text-red-500">Gmail nicht verbunden.</p>}
|
||||
{releaseMessage && <p className="text-center text-xs mt-2 font-bold text-indigo-600">{releaseMessage}</p>}
|
||||
</div>
|
||||
|
||||
{/* Responses List */}
|
||||
<div className="bg-white border border-gray-100 rounded-lg p-3 shadow-inner">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<h6 className="text-[10px] font-bold text-gray-500 uppercase">Eingegangene Antworten</h6>
|
||||
<button
|
||||
onClick={fetchReleaseResponses}
|
||||
disabled={isFetchingResponses}
|
||||
className="text-[10px] bg-indigo-50 text-indigo-600 px-2 py-1 rounded hover:bg-indigo-100 transition-colors flex items-center gap-1"
|
||||
>
|
||||
{isFetchingResponses ? '...' : '🔄 Aktualisieren'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!releaseResponses || releaseResponses.length === 0 ? (
|
||||
<p className="text-[10px] text-gray-400 italic text-center py-4">Noch keine Antworten eingegangen.</p>
|
||||
) : (
|
||||
<div className="max-h-40 overflow-y-auto rounded border border-gray-50">
|
||||
<table className="min-w-full text-[10px] text-left">
|
||||
<thead className="bg-gray-50 text-gray-400 sticky top-0">
|
||||
<tr>
|
||||
<th className="px-2 py-1">E-Mail</th>
|
||||
<th className="px-2 py-1">Code</th>
|
||||
<th className="px-2 py-1">Datum</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-50">
|
||||
{releaseResponses.map((res, idx) => (
|
||||
<tr key={idx} className="hover:bg-indigo-50/30">
|
||||
<td className="px-2 py-1 text-gray-700 truncate max-w-[120px]" title={res.email}>{res.email}</td>
|
||||
<td className="px-2 py-1 font-mono font-bold text-indigo-600">{res.code}</td>
|
||||
<td className="px-2 py-1 text-gray-400">{new Date(res.used_at).toLocaleDateString('de-DE', {day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit'})}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user