111 lines
4.5 KiB
TypeScript
111 lines
4.5 KiB
TypeScript
import { useState } from 'react';
|
|
import { Alert, StyleSheet, Text, View } from 'react-native';
|
|
import * as DocumentPicker from 'expo-document-picker';
|
|
import * as FileSystem from 'expo-file-system/legacy';
|
|
import * as Sharing from 'expo-sharing';
|
|
import { useMutation } from '@tanstack/react-query';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { downloadImportTemplate, importRecipesFromExcel } from '@/src/api/recipe-management.api';
|
|
import { Button } from '@/src/components/Button';
|
|
import { useAppearance } from '@/src/context/AppearanceContext';
|
|
import { extractApiError } from '@/src/utils/apiError';
|
|
|
|
interface ExcelImportPanelProps {
|
|
onImported?: () => void;
|
|
}
|
|
|
|
export function ExcelImportPanel({ onImported }: ExcelImportPanelProps) {
|
|
const { t } = useTranslation();
|
|
const { colors } = useAppearance();
|
|
const [selectedFile, setSelectedFile] = useState<{ uri: string; name: string } | null>(null);
|
|
const [downloading, setDownloading] = useState(false);
|
|
|
|
const importMutation = useMutation({
|
|
mutationFn: () => {
|
|
if (!selectedFile) throw new Error('No file');
|
|
return importRecipesFromExcel(selectedFile.uri, selectedFile.name);
|
|
},
|
|
onSuccess: (result) => {
|
|
setSelectedFile(null);
|
|
onImported?.();
|
|
Alert.alert(
|
|
t('manage.importComplete'),
|
|
`${t('manage.createdCount', { count: result.createdCount })} · ${t('manage.skippedCount', { count: result.skippedCount })}`,
|
|
);
|
|
},
|
|
});
|
|
|
|
async function handleDownloadTemplate() {
|
|
setDownloading(true);
|
|
try {
|
|
const blob = await downloadImportTemplate();
|
|
const reader = new FileReader();
|
|
const base64 = await new Promise<string>((resolve, reject) => {
|
|
reader.onloadend = () => {
|
|
const dataUrl = reader.result as string;
|
|
resolve(dataUrl.split(',')[1] ?? '');
|
|
};
|
|
reader.onerror = reject;
|
|
reader.readAsDataURL(blob);
|
|
});
|
|
const path = `${FileSystem.cacheDirectory}DailyMeals-recipe-import-template.xlsx`;
|
|
await FileSystem.writeAsStringAsync(path, base64, { encoding: FileSystem.EncodingType.Base64 });
|
|
if (await Sharing.isAvailableAsync()) {
|
|
await Sharing.shareAsync(path);
|
|
}
|
|
} catch (err) {
|
|
Alert.alert(t('errors.generic'), extractApiError(err, t('errors.saveRecipeFailed')));
|
|
} finally {
|
|
setDownloading(false);
|
|
}
|
|
}
|
|
|
|
async function handlePickFile() {
|
|
const result = await DocumentPicker.getDocumentAsync({
|
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
copyToCacheDirectory: true,
|
|
});
|
|
if (!result.canceled && result.assets[0]) {
|
|
setSelectedFile({ uri: result.assets[0].uri, name: result.assets[0].name });
|
|
}
|
|
}
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<Text style={{ color: colors.text, fontWeight: '700', fontSize: 16 }}>{t('manage.importTitle')}</Text>
|
|
<Text style={{ color: colors.textMuted, fontSize: 13 }}>{t('manage.importDescription')}</Text>
|
|
<Button
|
|
title={t('manage.downloadTemplate')}
|
|
variant="secondary"
|
|
loading={downloading}
|
|
onPress={() => void handleDownloadTemplate()}
|
|
/>
|
|
<Button title={t('manage.uploadLabel')} variant="secondary" onPress={() => void handlePickFile()} />
|
|
{selectedFile ? (
|
|
<Text style={{ color: colors.text, fontSize: 13 }}>{selectedFile.name}</Text>
|
|
) : null}
|
|
{importMutation.isError ? (
|
|
<Text style={{ color: colors.danger }}>{extractApiError(importMutation.error, t('errors.saveRecipeFailed'))}</Text>
|
|
) : null}
|
|
<Button
|
|
title={importMutation.isPending ? t('manage.importing') : t('manage.importRecipes')}
|
|
loading={importMutation.isPending}
|
|
disabled={!selectedFile}
|
|
onPress={() => importMutation.mutate()}
|
|
/>
|
|
<View style={[styles.hint, { borderColor: colors.border }]}>
|
|
<Text style={{ color: colors.text, fontWeight: '600' }}>{t('manage.excelFormatTitle')}</Text>
|
|
<Text style={{ color: colors.textMuted, fontSize: 13 }}>• {t('manage.excelRecipesSheet')}</Text>
|
|
<Text style={{ color: colors.textMuted, fontSize: 13 }}>• {t('manage.excelIngredientsSheet')}</Text>
|
|
<Text style={{ color: colors.textMuted, fontSize: 13 }}>• {t('manage.excelStepsSheet')}</Text>
|
|
<Text style={{ color: colors.textMuted, fontSize: 13, marginTop: 4 }}>{t('manage.duplicateSkipped')}</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { gap: 12 },
|
|
hint: { borderWidth: 1, borderStyle: 'dashed', borderRadius: 12, padding: 12, gap: 4 },
|
|
});
|