Browse Source

Adding prod sche related

master
parent
commit
e9da01b664
7 changed files with 87 additions and 10 deletions
  1. +19
    -0
      src/app/api/scheduling/actions.ts
  2. +2
    -0
      src/app/api/scheduling/index.ts
  3. +52
    -3
      src/components/DetailedSchedule/DetailedScheduleSearchView.tsx
  4. +2
    -3
      src/components/DetailedScheduleDetail/DetailedScheduleDetailView.tsx
  5. +1
    -0
      src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx
  6. +9
    -2
      src/components/DetailedScheduleDetail/ViewByFGDetails.tsx
  7. +2
    -2
      src/components/ScheduleTable/ScheduleTable.tsx

+ 19
- 0
src/app/api/scheduling/actions.ts View File

@@ -177,6 +177,25 @@ export const releaseProdSchedule = cache(async (data: ReleaseProdScheduleReq) =>
return response;
})

export const exportProdSchedule = async (token: string | null) => {
if (!token) throw new Error("No access token found");

const response = await fetch(`${BASE_API_URL}/productionSchedule/export-prod-schedule`, {
method: "POST",
headers: {
"Accept": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"Authorization": `Bearer ${token}`
}
});

if (!response.ok) throw new Error(`Backend error: ${response.status}`);

const arrayBuffer = await response.arrayBuffer();
// Convert to Base64 so Next.js can send it safely over the wire
return Buffer.from(arrayBuffer).toString('base64');
};

export const saveProdScheduleLine = cache(async (data: ReleaseProdScheduleInputs) => {
const response = serverFetchJson<SaveProdScheduleResponse>(
`${BASE_API_URL}/productionSchedule/detail/detailed/save`,


+ 2
- 0
src/app/api/scheduling/index.ts View File

@@ -105,6 +105,8 @@ export interface DetailedProdScheduleLineResult {
stockQty: number; // Warehouse stock quantity
daysLeft: number; // Days remaining before stockout
needNoOfJobOrder: number;
prodQty: number;
outputQty: number;
}

export interface DetailedProdScheduleLineBomMaterialResult {


+ 52
- 3
src/components/DetailedSchedule/DetailedScheduleSearchView.tsx View File

@@ -12,6 +12,7 @@ import {
SearchProdSchedule,
fetchDetailedProdSchedules,
fetchProdSchedules,
exportProdSchedule,
testDetailedSchedule,
} from "@/app/api/scheduling/actions";
import { defaultPagingController } from "../SearchResults/SearchResults";
@@ -21,6 +22,7 @@ import { orderBy, uniqBy, upperFirst } from "lodash";
import { Button, Stack } from "@mui/material";
import isToday from 'dayjs/plugin/isToday';
import useUploadContext from "../UploadProvider/useUploadContext";
import { FileDownload, CalendarMonth } from "@mui/icons-material";
dayjs.extend(isToday);

// may need move to "index" or "actions"
@@ -298,21 +300,68 @@ const DSOverview: React.FC<Props> = ({ type, defaultInputs }) => {
}
}, [inputs])

const exportProdScheduleClick = async () => {
try {
const token = localStorage.getItem("accessToken");
// 1. Get Base64 string from server
const base64String = await exportProdSchedule(token);
// 2. Convert Base64 back to Blob
const byteCharacters = atob(base64String);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
});

// 3. Trigger download (same as before)
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "production_schedule.xlsx";
link.click();
window.URL.revokeObjectURL(url);
} catch (error) {
console.error(error);
alert("Export failed. Check the console for details.");
}
};
return (
<>
<Stack
direction="row"
justifyContent="flex-end"
flexWrap="wrap"
rowGap={2}
spacing={2} // This provides consistent space between buttons
sx={{ mb: 3 }} // Adds some margin below the button group
>
<Button
variant="contained"
variant="outlined" // Outlined variant makes it look distinct from the primary action
color="primary"
startIcon={<CalendarMonth />}
onClick={testDetailedScheduleClick}
// disabled={filteredSchedules.some(ele => arrayToDayjs(ele.scheduleAt).isToday())}
>
{t("Detailed Schedule")}
</Button>
<Button
variant="contained" // Solid button for the "Export" action
color="success" // Green color often signifies a successful action/download
startIcon={<FileDownload />}
onClick={exportProdScheduleClick}
sx={{
boxShadow: 2,
'&:hover': { backgroundColor: 'success.dark', boxShadow: 4 }
}}
>
{t("Export Schedule")}
</Button>
</Stack>
<SearchBox
criteria={searchCriteria}


+ 2
- 3
src/components/DetailedScheduleDetail/DetailedScheduleDetailView.tsx View File

@@ -194,8 +194,7 @@ const DetailedScheduleDetailView: React.FC<Props> = ({
}
}, [scheduleId, setIsUploading, t, router]);
// --------------------------------------------------------------------


const [tempValue, setTempValue] = useState<string | number | null>(null)
const onEditClick = useCallback((rowId: number) => {
const row = formProps.getValues("prodScheduleLines").find(ele => ele.id == rowId)
@@ -298,7 +297,7 @@ const DetailedScheduleDetailView: React.FC<Props> = ({
onClick={onGlobalReleaseClick}
disabled={!scheduleId} // Disable if we don't have a schedule ID
>
{t("放單(自動生成工單)")}
{t("生成工單")}
</Button>
{/* ------------------------------------------- */}



+ 1
- 0
src/components/DetailedScheduleDetail/DetailedScheduleDetailWrapper.tsx View File

@@ -32,6 +32,7 @@ const DetailedScheduleDetailWrapper: React.FC<Props> & SubComponents = async ({
stockQty: line.stockQty || 0,
daysLeft: line.daysLeft || 0,
needNoOfJobOrder: line.needNoOfJobOrder || 0,
outputQty: line.outputQty || 0,
})).sort((a, b) => b.priority - a.priority);
}



+ 9
- 2
src/components/DetailedScheduleDetail/ViewByFGDetails.tsx View File

@@ -108,9 +108,16 @@ const ViewByFGDetails: React.FC<Props> = ({
style: { textAlign: "right" } as any,
renderCell: (row) => <>{row.daysLeft ?? 0}</>,
},
{
field: "outputQty",
label: t("每批次生產數"),
type: "read-only",
style: { textAlign: "right", fontWeight: "bold" } as any,
renderCell: (row) => <>{row.outputQty ?? 0}</>,
},
{
field: "needNoOfJobOrder",
label: t("生產量"),
label: t("生產批次"),
type: "read-only",
style: { textAlign: "right", fontWeight: "bold" } as any,
renderCell: (row) => <>{row.needNoOfJobOrder ?? 0}</>,
@@ -137,7 +144,7 @@ const ViewByFGDetails: React.FC<Props> = ({
isEditable={true}
isEdit={isEdit}
hasCollapse={true}
// Note: onReleaseClick is NOT passed here to hide the row-level "Release" function
onReleaseClick={onReleaseClick}
onEditClick={onEditClick}
handleEditChange={handleEditChange}
onSaveClick={onSaveClick}


+ 2
- 2
src/components/ScheduleTable/ScheduleTable.tsx View File

@@ -227,7 +227,7 @@ function ScheduleTable<T extends ResultWithId>({
return (
<>
<TableRow hover tabIndex={-1} key={row.id}>
{/*isDetailedType(type) && (
{isDetailedType(type) && (
<TableCell>
<IconButton
color="primary"
@@ -241,7 +241,7 @@ function ScheduleTable<T extends ResultWithId>({
<PlayCircleOutlineIcon />
</IconButton>
</TableCell>
)*/}
)}
{(isEditable || hasCollapse) && (
<TableCell>
{editingRowId === row.id ? (


Loading…
Cancel
Save