diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 8ee462b..05c6410 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -4,15 +4,18 @@
+
+
+
+
+
-
-
+
-
{
registers,
activeRegister,
selectedRegisters,
+ viewingRegisters,
+ setViewingRegisters,
setDefaultTargetPercentage,
updateAllRegistersTargetPercentage,
selectedSchedules,
@@ -54,7 +58,15 @@ const SettingsScreen: React.FC = () => {
const [newTargetValue, setNewTargetValue] = useState(defaultTargetPercentage.toString());
const [localLeadTime, setLocalLeadTime] = useState(notificationLeadTime);
const [localSchedules, setLocalSchedules] = useState(selectedSchedules);
-
+ // CSV filename modal state
+ const [showFileNameModal, setShowFileNameModal] = useState(false);
+ const [csvFileName, setCsvFileName] = useState('MySchedule');
+ const [isSaving, setIsSaving] = useState(false);
+
+ const registerOptions = Object.keys(registers).map(key => ({
+ id: key,
+ name: registers[parseInt(key)].name,
+ }));
// Effects
useEffect(() => {
setAppVersion(packageJson.version);
@@ -103,33 +115,33 @@ const SettingsScreen: React.FC = () => {
};
// Export functionality handlers
- const handleSaveScheduleToDevice = async () => {
+ // Show modal to enter filename
+ const handleSaveScheduleToDevice = () => {
+ setShowFileNameModal(true);
+ };
+
+ // Actually save with filename
+ const handleConfirmSaveFileName = async () => {
+ setIsSaving(true);
try {
- console.log('Starting save to device...');
- console.log('Available registers:', Object.keys(registers));
- console.log('Selected registers from store:', selectedRegisters);
-
- // Use all registers if no specific selection is available
- const currentSelectedRegisters = selectedRegisters && selectedRegisters.length > 0
- ? selectedRegisters
+ const currentSelectedRegisters = selectedRegisters && selectedRegisters.length > 0
+ ? selectedRegisters
: Object.keys(registers).map(key => parseInt(key, 10));
-
- console.log('Using registers for export:', currentSelectedRegisters);
-
if (currentSelectedRegisters.length === 0) {
Alert.alert('No Data', 'No registers found to export. Please create some schedules first.');
+ setIsSaving(false);
return;
}
-
- await saveScheduleToDevice({
- selectedRegisters: currentSelectedRegisters,
- registers
- });
+ await saveScheduleToDeviceWithName({
+ selectedRegisters: currentSelectedRegisters,
+ registers
+ }, csvFileName);
+ setShowFileNameModal(false);
} catch (error) {
- console.error('Save to device error:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
Alert.alert('Error', `Failed to save schedule to device: ${errorMessage}`);
}
+ setIsSaving(false);
};
const handleShareSchedule = async () => {
@@ -137,22 +149,22 @@ const SettingsScreen: React.FC = () => {
console.log('Starting share schedule...');
console.log('Available registers:', Object.keys(registers));
console.log('Selected registers from store:', selectedRegisters);
-
+
// Use all registers if no specific selection is available
- const currentSelectedRegisters = selectedRegisters && selectedRegisters.length > 0
- ? selectedRegisters
+ const currentSelectedRegisters = selectedRegisters && selectedRegisters.length > 0
+ ? selectedRegisters
: Object.keys(registers).map(key => parseInt(key, 10));
-
+
console.log('Using registers for share:', currentSelectedRegisters);
-
+
if (currentSelectedRegisters.length === 0) {
Alert.alert('No Data', 'No registers found to share. Please create some schedules first.');
return;
}
-
- await shareSchedule({
- selectedRegisters: currentSelectedRegisters,
- registers
+
+ await shareSchedule({
+ selectedRegisters: currentSelectedRegisters,
+ registers
});
} catch (error) {
console.error('Share schedule error:', error);
@@ -164,7 +176,7 @@ const SettingsScreen: React.FC = () => {
const handleImportSchedule = async () => {
try {
console.log('Starting CSV import...');
-
+
if (!registers[activeRegister]) {
Alert.alert('Error', 'No active register found. Please create a register first.');
return;
@@ -173,7 +185,7 @@ const SettingsScreen: React.FC = () => {
// Use raw CSV content instead of parsed data
const csvContent = await pickCSVFileRaw();
console.log('Raw CSV Content received:', csvContent);
-
+
if (!csvContent) {
console.log('No CSV content received (user cancelled or error)');
return;
@@ -238,7 +250,6 @@ const SettingsScreen: React.FC = () => {
{registerInfo.name}
{registerInfo.totalCards} subjects
-
{/* Preferences Section */}
Preferences
@@ -294,6 +305,7 @@ const SettingsScreen: React.FC = () => {
+
{/* Notifications & Alerts Section */}
Notifications & Alerts
@@ -333,16 +345,15 @@ const SettingsScreen: React.FC = () => {
/>
-
{/* Utilities Section */}
Utilities
-
Save Schedule to Device
@@ -350,12 +361,55 @@ const SettingsScreen: React.FC = () => {
+ {/* CSV Filename Modal */}
+ setShowFileNameModal(false)}
+ >
+
+
+ Enter CSV Filename
+ This will be the name of your exported CSV file.
+
+
+ .csv
+
+
+ setShowFileNameModal(false)}
+ disabled={isSaving}
+ >
+ Cancel
+
+
+ {isSaving ? 'Saving...' : 'Save'}
+
+
+
+
+
-
Share Schedule
@@ -366,9 +420,9 @@ const SettingsScreen: React.FC = () => {
-
Import Schedule from CSV
@@ -456,7 +510,6 @@ const SettingsScreen: React.FC = () => {
>
);
};
-
// Styles
const styles = StyleSheet.create({
container: {
@@ -713,5 +766,4 @@ const styles = StyleSheet.create({
color: '#A1A1AA',
},
});
-
export default SettingsScreen;
\ No newline at end of file
diff --git a/src/store/store.ts b/src/store/store.ts
index b0cd34e..427e3ad 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -1,3 +1,5 @@
+// src/store/store.ts
+
import {create} from 'zustand';
import {persist, createJSONStorage} from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
@@ -22,6 +24,7 @@ interface StoreState {
updatedAt: Date | null;
defaultTargetPercentage: number;
selectedRegisters: number[];
+ viewingRegisters: number[]; // New state for global view
setRegisters: (regNo: number, cardsData: CardInterface[]) => void;
updateDate: (date: Date) => void;
setDefaultTargetPercentage: (percentage: number) => void;
@@ -30,14 +33,15 @@ interface StoreState {
changeCopyRegister: (registerId: number) => void;
setActiveRegister: (registerId: number) => void;
setSelectedRegisters: (selectedIds: number[]) => void;
-
+ setViewingRegisters: (selectedIds: number[]) => void; // New action
+
selectedSchedules: string[];
setSelectedSchedules: (schedules: string[]) => void;
// Notification Lead Time (in minutes)
notificationLeadTime: number;
setNotificationLeadTime: (time: number) => void;
-
+
addRegister: (registerId: number, registerName: string) => void;
renameRegister: (registerId: number, registerName: string) => void;
removeRegister: (registerId: number) => void;
@@ -141,26 +145,35 @@ export const useStore = create()(
copyRegister: 0,
updatedAt: null,
defaultTargetPercentage: 75,
- selectedRegisters: [0], // Initialize with default register
+ selectedRegisters: [0],
+ viewingRegisters: [0], // Initialize with the default active register
+
+ setViewingRegisters: (selectedIds: number[]) =>
+ set(() => ({
+ viewingRegisters: selectedIds,
+ })),
changeCopyRegister: (registerId: number) =>
set(() => ({
copyRegister: registerId,
})),
selectedSchedules: ['all'],
- setSelectedSchedules: (schedules: string[]) =>
- set({ selectedSchedules: schedules }),
+ setSelectedSchedules: (schedules: string[]) =>
+ set({ selectedSchedules: schedules }),
- // Lead time for notification (e.g., 10 minutes before)
- notificationLeadTime: 10,
- setNotificationLeadTime: (time: number) =>
- set({ notificationLeadTime: time }),
+ // Lead time for notification (e.g., 10 minutes before)
+ notificationLeadTime: 10,
+ setNotificationLeadTime: (time: number) =>
+ set({ notificationLeadTime: time }),
setActiveRegister: (registerId: number) =>
set(state => ({
activeRegister: registerId,
// If no registers are selected, select the new active register
- selectedRegisters: state.selectedRegisters.length === 0 ? [registerId] : state.selectedRegisters,
+ selectedRegisters:
+ state.selectedRegisters.length === 0
+ ? [registerId]
+ : state.selectedRegisters,
})),
setSelectedRegisters: (selectedIds: number[]) =>
@@ -198,10 +211,11 @@ export const useStore = create()(
...state.registers,
[registerId]: {
...state.registers[registerId],
- cards: state.registers[registerId]?.cards.map(card => ({
- ...card,
- target_percentage: percentage,
- })) || [],
+ cards:
+ state.registers[registerId]?.cards.map(card => ({
+ ...card,
+ target_percentage: percentage,
+ })) || [],
},
},
})),
@@ -267,7 +281,7 @@ export const useStore = create()(
removeRegister: (registerId: number) =>
set(state => {
- const registers = {...state.registers};
+ const registers = { ...state.registers };
if (registerId in registers) {
const keys = Object.keys(registers)
.map(Number)
@@ -282,15 +296,32 @@ export const useStore = create()(
state.activeRegister === registerId ? 0 : state.activeRegister;
// Remove the deleted register from selectedRegisters and ensure valid selection
- let newSelectedRegisters = state.selectedRegisters.filter(id => id !== registerId);
- if (newSelectedRegisters.length === 0 && Object.keys(registers).length > 0) {
+ let newSelectedRegisters = state.selectedRegisters.filter(
+ id => id !== registerId,
+ );
+ if (
+ newSelectedRegisters.length === 0 &&
+ Object.keys(registers).length > 0
+ ) {
newSelectedRegisters = [newActiveRegister];
}
+ // Remove the deleted register from viewingRegisters
+ let newViewingRegisters = state.viewingRegisters.filter(
+ id => id !== registerId,
+ );
+ if (
+ newViewingRegisters.length === 0 &&
+ Object.keys(registers).length > 0
+ ) {
+ newViewingRegisters = [newActiveRegister];
+ }
+
return {
registers,
activeRegister: newActiveRegister,
selectedRegisters: newSelectedRegisters,
+ viewingRegisters: newViewingRegisters,
};
}
return state;
@@ -361,7 +392,12 @@ export const useStore = create()(
},
},
})),
- markAbsentWithDate: (date: Date, cardId: number, registerId: number, timeSlot?: string) =>
+ markAbsentWithDate: (
+ date: Date,
+ cardId: number,
+ registerId: number,
+ timeSlot?: string,
+ ) =>
set(state => {
const register = state.registers[registerId];
const card = register.cards.find(
@@ -402,7 +438,12 @@ export const useStore = create()(
updatedAt: new Date(),
};
}),
- markPresentWithDate: (date: Date, cardId: number, registerId: number, timeSlot?: string) =>
+ markPresentWithDate: (
+ date: Date,
+ cardId: number,
+ registerId: number,
+ timeSlot?: string,
+ ) =>
set(state => {
const register = state.registers[registerId];
const card = register.cards.find(
@@ -679,4 +720,4 @@ export const useStore = create()(
),
);
-export default useStore;
+export default useStore;
\ No newline at end of file
diff --git a/src/utils/exportSchedule.ts b/src/utils/exportSchedule.ts
index ceeeb78..00b83b3 100644
--- a/src/utils/exportSchedule.ts
+++ b/src/utils/exportSchedule.ts
@@ -35,7 +35,7 @@ export class ExportScheduleUtility {
return Object.keys(this.registers).map(key => parseInt(key, 10));
}
- private async exportSelectedRegistersCSV(checkOnly = false, overwrite = false): Promise {
+ private async exportSelectedRegistersCSV(checkOnly = false, overwrite = false, customFileName?: string): Promise {
try {
console.log('Starting export with selectedRegisters:', this.selectedRegisters);
console.log('Available registers:', Object.keys(this.registers));
@@ -67,7 +67,9 @@ export class ExportScheduleUtility {
return null;
}
- if (registerList.length === 1) {
+ if (customFileName && customFileName.trim().length > 0) {
+ fileName = customFileName.endsWith('.csv') ? customFileName : `${customFileName}.csv`;
+ } else if (registerList.length === 1) {
fileName = `TimeTable_${registerList[0].name.replace(/\s+/g, '_')}.csv`;
} else if (registerList.length === this.getAllRegisterIds().length) {
fileName = `TimeTable_Grouped_All.csv`;
@@ -118,8 +120,8 @@ export class ExportScheduleUtility {
}
}
- async saveToDevice(): Promise {
- const result = await this.exportSelectedRegistersCSV(true); // pass checkOnly flag
+ async saveToDevice(customFileName?: string): Promise {
+ const result = await this.exportSelectedRegistersCSV(true, false, customFileName); // pass checkOnly flag
if (result && result.exists) {
Alert.alert(
'File Exists',
@@ -129,7 +131,7 @@ export class ExportScheduleUtility {
{
text: 'Yes',
onPress: async () => {
- await this.exportSelectedRegistersCSV(false, true); // force overwrite
+ await this.exportSelectedRegistersCSV(false, true, customFileName); // force overwrite
ToastAndroid.show(
`Saved to Downloads: ${result.fileName}`,
ToastAndroid.SHORT
@@ -169,6 +171,15 @@ export const saveScheduleToDevice = async ({ selectedRegisters, registers }: Exp
await exportUtil.saveToDevice();
};
+// Overload to support custom filename
+export const saveScheduleToDeviceWithName = async (
+ { selectedRegisters, registers }: ExportUtilityProps,
+ customFileName: string
+): Promise => {
+ const exportUtil = new ExportScheduleUtility({ selectedRegisters, registers });
+ await exportUtil.saveToDevice(customFileName);
+};
+
export const shareSchedule = async ({ selectedRegisters, registers }: ExportUtilityProps): Promise => {
const exportUtil = new ExportScheduleUtility({ selectedRegisters, registers });
await exportUtil.shareSchedule();