[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 = MIMEText(body_html, 'html')
|
||||||
message['to'] = to
|
message['to'] = to
|
||||||
message['subject'] = subject
|
message['subject'] = subject
|
||||||
|
message['bcc'] = 'kontakt@kinderfotos-erding.de'
|
||||||
|
|
||||||
raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
raw_message = base64.urlsafe_b64encode(message.as_bytes()).decode()
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,11 @@ def get_stats(db: Session = Depends(get_db)):
|
|||||||
available = total - used
|
available = total - used
|
||||||
return {"total": total, "used": used, "available": available}
|
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")
|
@router.post("/codes")
|
||||||
def upload_codes(data: CodesUpload, db: Session = Depends(get_db)):
|
def upload_codes(data: CodesUpload, db: Session = Depends(get_db)):
|
||||||
codes_list = [c.strip() for c in data.codes.split(",") if c.strip()]
|
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 [isSendingRelease, setIsSendingRelease] = useState(false);
|
||||||
const [releaseMessage, setReleaseMessage] = useState("");
|
const [releaseMessage, setReleaseMessage] = useState("");
|
||||||
const [scheduledTime, setScheduledTime] = useState(""); // New state
|
const [scheduledTime, setScheduledTime] = useState(""); // New state
|
||||||
|
const [releaseResponses, setReleaseResponses] = useState<any[] | null>(null);
|
||||||
|
const [isFetchingResponses, setIsFetchingResponses] = useState(false);
|
||||||
|
|
||||||
const fetchReleaseStats = async () => {
|
const fetchReleaseStats = async () => {
|
||||||
try {
|
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 () => {
|
const handleUploadCodes = async () => {
|
||||||
setIsUploadingCodes(true);
|
setIsUploadingCodes(true);
|
||||||
setUploadMessage("Lädt hoch...");
|
setUploadMessage("Lädt hoch...");
|
||||||
@@ -248,6 +264,8 @@ function App() {
|
|||||||
}
|
}
|
||||||
fetchLatestFile();
|
fetchLatestFile();
|
||||||
checkGmailAuth();
|
checkGmailAuth();
|
||||||
|
fetchReleaseStats();
|
||||||
|
fetchReleaseResponses();
|
||||||
}, [activeTab]);
|
}, [activeTab]);
|
||||||
|
|
||||||
const handleRefresh = () => fetchJobs(activeTab, true);
|
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>}
|
{!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>}
|
{releaseMessage && <p className="text-center text-xs mt-2 font-bold text-indigo-600">{releaseMessage}</p>}
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user